3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FindAndReplace.h"
22 #include "FontLoader.h"
23 #include "GuiApplication.h"
24 #include "GuiClickableLabel.h"
25 #include "GuiCompleter.h"
26 #include "GuiFontMetrics.h"
27 #include "GuiKeySymbol.h"
29 #include "GuiToolbar.h"
30 #include "GuiWorkArea.h"
31 #include "GuiProgress.h"
32 #include "LayoutBox.h"
36 #include "qt_helpers.h"
37 #include "support/filetools.h"
39 #include "frontends/alert.h"
40 #include "frontends/KeySymbol.h"
42 #include "buffer_funcs.h"
44 #include "BufferList.h"
45 #include "BufferParams.h"
46 #include "BufferView.h"
48 #include "Converter.h"
50 #include "CutAndPaste.h"
52 #include "ErrorList.h"
54 #include "FuncStatus.h"
55 #include "FuncRequest.h"
56 #include "KeySymbol.h"
58 #include "LayoutFile.h"
60 #include "LyXAction.h"
64 #include "Paragraph.h"
65 #include "SpellChecker.h"
72 #include "graphics/PreviewLoader.h"
74 #include "support/convert.h"
75 #include "support/debug.h"
76 #include "support/ExceptionMessage.h"
77 #include "support/FileName.h"
78 #include "support/gettext.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
90 #include <QApplication>
91 #include <QCloseEvent>
92 #include <QDragEnterEvent>
95 #include <QFutureWatcher>
106 #include <QShowEvent>
109 #include <QStackedWidget>
110 #include <QStatusBar>
111 #include <QSvgRenderer>
112 #include <QtConcurrentRun>
115 #include <QWindowStateChangeEvent>
116 #include <QGestureEvent>
117 #include <QPinchGesture>
120 // sync with GuiAlert.cpp
121 #define EXPORT_in_THREAD 1
124 #include "support/bind.h"
128 #ifdef HAVE_SYS_TIME_H
129 # include <sys/time.h>
137 using namespace lyx::support;
141 using support::addExtension;
142 using support::changeExtension;
143 using support::removeExtension;
149 class BackgroundWidget : public QWidget
152 BackgroundWidget(int width, int height)
153 : width_(width), height_(height)
155 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
156 if (!lyxrc.show_banner)
158 /// The text to be written on top of the pixmap
159 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
160 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
161 /// The text to be written on top of the pixmap
162 QString const text = lyx_version ?
163 qt_("version ") + lyx_version : qt_("unknown version");
164 #if QT_VERSION >= 0x050000
165 QString imagedir = "images/";
166 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
167 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
168 if (svgRenderer.isValid()) {
169 splash_ = QPixmap(splashSize());
170 QPainter painter(&splash_);
171 svgRenderer.render(&painter);
172 splash_.setDevicePixelRatio(pixelRatio());
174 splash_ = getPixmap("images/", "banner", "png");
177 splash_ = getPixmap("images/", "banner", "svgz,png");
180 QPainter pain(&splash_);
181 pain.setPen(QColor(0, 0, 0));
182 qreal const fsize = fontSize();
185 qreal locscale = htextsize.toFloat(&ok);
188 QPointF const position = textPosition(false);
189 QPointF const hposition = textPosition(true);
190 QRectF const hrect(hposition, splashSize());
192 "widget pixel ratio: " << pixelRatio() <<
193 " splash pixel ratio: " << splashPixelRatio() <<
194 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Bold);
199 font.setPointSizeF(fsize);
201 pain.drawText(position, text);
202 // The font used to display the version info
203 font.setStyleHint(QFont::SansSerif);
204 font.setWeight(QFont::Normal);
205 font.setPointSizeF(hfsize);
206 // Check how long the logo gets with the current font
207 // and adapt if the font is running wider than what
209 GuiFontMetrics fm(font);
210 // Split the title into lines to measure the longest line
211 // in the current l7n.
212 QStringList titlesegs = htext.split('\n');
214 int hline = fm.maxHeight();
215 for (QString const & seg : titlesegs) {
216 if (fm.width(seg) > wline)
217 wline = fm.width(seg);
219 // The longest line in the reference font (for English)
220 // is 180. Calculate scale factor from that.
221 double const wscale = wline > 0 ? (180.0 / wline) : 1;
222 // Now do the same for the height (necessary for condensed fonts)
223 double const hscale = (34.0 / hline);
224 // take the lower of the two scale factors.
225 double const scale = min(wscale, hscale);
226 // Now rescale. Also consider l7n's offset factor.
227 font.setPointSizeF(hfsize * scale * locscale);
230 pain.drawText(hrect, Qt::AlignLeft, htext);
231 setFocusPolicy(Qt::StrongFocus);
234 void paintEvent(QPaintEvent *) override
236 int const w = width_;
237 int const h = height_;
238 int const x = (width() - w) / 2;
239 int const y = (height() - h) / 2;
241 "widget pixel ratio: " << pixelRatio() <<
242 " splash pixel ratio: " << splashPixelRatio() <<
243 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
245 pain.drawPixmap(x, y, w, h, splash_);
248 void keyPressEvent(QKeyEvent * ev) override
251 setKeySymbol(&sym, ev);
253 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
265 /// Current ratio between physical pixels and device-independent pixels
266 double pixelRatio() const {
267 #if QT_VERSION >= 0x050000
268 return qt_scale_factor * devicePixelRatio();
274 qreal fontSize() const {
275 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
278 QPointF textPosition(bool const heading) const {
279 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
280 : QPointF(width_/2 - 18, height_/2 + 45);
283 QSize splashSize() const {
285 static_cast<unsigned int>(width_ * pixelRatio()),
286 static_cast<unsigned int>(height_ * pixelRatio()));
289 /// Ratio between physical pixels and device-independent pixels of splash image
290 double splashPixelRatio() const {
291 #if QT_VERSION >= 0x050000
292 return splash_.devicePixelRatio();
300 /// Toolbar store providing access to individual toolbars by name.
301 typedef map<string, GuiToolbar *> ToolbarMap;
303 typedef shared_ptr<Dialog> DialogPtr;
308 class GuiView::GuiViewPrivate
311 GuiViewPrivate(GuiViewPrivate const &);
312 void operator=(GuiViewPrivate const &);
314 GuiViewPrivate(GuiView * gv)
315 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
316 layout_(nullptr), autosave_timeout_(5000),
319 // hardcode here the platform specific icon size
320 smallIconSize = 16; // scaling problems
321 normalIconSize = 20; // ok, default if iconsize.png is missing
322 bigIconSize = 26; // better for some math icons
323 hugeIconSize = 32; // better for hires displays
326 // if it exists, use width of iconsize.png as normal size
327 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
328 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
330 QImage image(toqstr(fn.absFileName()));
331 if (image.width() < int(smallIconSize))
332 normalIconSize = smallIconSize;
333 else if (image.width() > int(giantIconSize))
334 normalIconSize = giantIconSize;
336 normalIconSize = image.width();
339 splitter_ = new QSplitter;
340 bg_widget_ = new BackgroundWidget(400, 250);
341 stack_widget_ = new QStackedWidget;
342 stack_widget_->addWidget(bg_widget_);
343 stack_widget_->addWidget(splitter_);
346 // TODO cleanup, remove the singleton, handle multiple Windows?
347 progress_ = ProgressInterface::instance();
348 if (!dynamic_cast<GuiProgress*>(progress_)) {
349 progress_ = new GuiProgress; // TODO who deletes it
350 ProgressInterface::setInstance(progress_);
353 dynamic_cast<GuiProgress*>(progress_),
354 SIGNAL(updateStatusBarMessage(QString const&)),
355 gv, SLOT(updateStatusBarMessage(QString const&)));
357 dynamic_cast<GuiProgress*>(progress_),
358 SIGNAL(clearMessageText()),
359 gv, SLOT(clearMessageText()));
366 delete stack_widget_;
371 stack_widget_->setCurrentWidget(bg_widget_);
372 bg_widget_->setUpdatesEnabled(true);
373 bg_widget_->setFocus();
376 int tabWorkAreaCount()
378 return splitter_->count();
381 TabWorkArea * tabWorkArea(int i)
383 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
386 TabWorkArea * currentTabWorkArea()
388 int areas = tabWorkAreaCount();
390 // The first TabWorkArea is always the first one, if any.
391 return tabWorkArea(0);
393 for (int i = 0; i != areas; ++i) {
394 TabWorkArea * twa = tabWorkArea(i);
395 if (current_main_work_area_ == twa->currentWorkArea())
399 // None has the focus so we just take the first one.
400 return tabWorkArea(0);
403 int countWorkAreasOf(Buffer & buf)
405 int areas = tabWorkAreaCount();
407 for (int i = 0; i != areas; ++i) {
408 TabWorkArea * twa = tabWorkArea(i);
409 if (twa->workArea(buf))
415 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
417 if (processing_thread_watcher_.isRunning()) {
418 // we prefer to cancel this preview in order to keep a snappy
422 processing_thread_watcher_.setFuture(f);
425 QSize iconSize(docstring const & icon_size)
428 if (icon_size == "small")
429 size = smallIconSize;
430 else if (icon_size == "normal")
431 size = normalIconSize;
432 else if (icon_size == "big")
434 else if (icon_size == "huge")
436 else if (icon_size == "giant")
437 size = giantIconSize;
439 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
441 if (size < smallIconSize)
442 size = smallIconSize;
444 return QSize(size, size);
447 QSize iconSize(QString const & icon_size)
449 return iconSize(qstring_to_ucs4(icon_size));
452 string & iconSize(QSize const & qsize)
454 LATTEST(qsize.width() == qsize.height());
456 static string icon_size;
458 unsigned int size = qsize.width();
460 if (size < smallIconSize)
461 size = smallIconSize;
463 if (size == smallIconSize)
465 else if (size == normalIconSize)
466 icon_size = "normal";
467 else if (size == bigIconSize)
469 else if (size == hugeIconSize)
471 else if (size == giantIconSize)
474 icon_size = convert<string>(size);
479 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
482 Buffer * buffer, string const & format);
483 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
484 Buffer * buffer, string const & format);
485 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
488 static Buffer::ExportStatus runAndDestroy(const T& func,
489 Buffer const * orig, Buffer * buffer, string const & format);
491 // TODO syncFunc/previewFunc: use bind
492 bool asyncBufferProcessing(string const & argument,
493 Buffer const * used_buffer,
494 docstring const & msg,
495 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
496 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
497 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
498 bool allow_async, bool use_tmpdir = false);
500 QVector<GuiWorkArea*> guiWorkAreas();
504 GuiWorkArea * current_work_area_;
505 GuiWorkArea * current_main_work_area_;
506 QSplitter * splitter_;
507 QStackedWidget * stack_widget_;
508 BackgroundWidget * bg_widget_;
510 ToolbarMap toolbars_;
511 ProgressInterface* progress_;
512 /// The main layout box.
514 * \warning Don't Delete! The layout box is actually owned by
515 * whichever toolbar contains it. All the GuiView class needs is a
516 * means of accessing it.
518 * FIXME: replace that with a proper model so that we are not limited
519 * to only one dialog.
524 map<string, DialogPtr> dialogs_;
527 QTimer statusbar_timer_;
528 QTimer statusbar_stats_timer_;
529 /// auto-saving of buffers
530 Timeout autosave_timeout_;
533 TocModels toc_models_;
536 QFutureWatcher<docstring> autosave_watcher_;
537 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
539 string last_export_format;
540 string processing_format;
542 static QSet<Buffer const *> busyBuffers;
544 unsigned int smallIconSize;
545 unsigned int normalIconSize;
546 unsigned int bigIconSize;
547 unsigned int hugeIconSize;
548 unsigned int giantIconSize;
550 /// flag against a race condition due to multiclicks, see bug #1119
553 // Timers for statistic updates in buffer
554 /// Current time left to the nearest info update
555 int time_to_update = 1000;
556 ///Basic step for timer in ms. Basically reaction time for short selections
557 int const timer_rate = 500;
558 /// Real stats updates infrequently. First they take long time for big buffers, second
559 /// they are visible for fast-repeat keyboards even for mid documents.
560 int const default_stats_rate = 5000;
561 /// Detection of new selection, so we can react fast
562 bool already_in_selection_ = false;
563 /// Maximum size of "short" selection for which we can update with faster timer_rate
564 int const max_sel_chars = 5000;
568 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
571 GuiView::GuiView(int id)
572 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
573 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
574 char_count_enabled_(true), char_nb_count_enabled_(false),
575 toolbarsMovable_(true), devel_mode_(false)
577 connect(this, SIGNAL(bufferViewChanged()),
578 this, SLOT(onBufferViewChanged()));
580 // GuiToolbars *must* be initialised before the menu bar.
581 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
584 // set ourself as the current view. This is needed for the menu bar
585 // filling, at least for the static special menu item on Mac. Otherwise
586 // they are greyed out.
587 guiApp->setCurrentView(this);
589 // Fill up the menu bar.
590 guiApp->menus().fillMenuBar(menuBar(), this, true);
592 setCentralWidget(d.stack_widget_);
594 // Start autosave timer
595 if (lyxrc.autosave) {
596 // The connection is closed when this is destroyed.
597 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
598 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
599 d.autosave_timeout_.start();
601 connect(&d.statusbar_timer_, SIGNAL(timeout()),
602 this, SLOT(clearMessage()));
603 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
604 this, SLOT(showStats()));
605 d.statusbar_stats_timer_.start(d.timer_rate);
607 // We don't want to keep the window in memory if it is closed.
608 setAttribute(Qt::WA_DeleteOnClose, true);
610 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
611 // QIcon::fromTheme was introduced in Qt 4.6
612 #if (QT_VERSION >= 0x040600)
613 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
614 // since the icon is provided in the application bundle. We use a themed
615 // version when available and use the bundled one as fallback.
616 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
618 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
624 // use tabbed dock area for multiple docks
625 // (such as "source" and "messages")
626 setDockOptions(QMainWindow::ForceTabbedDocks);
629 // use document mode tabs on docks
630 setDocumentMode(true);
634 setAcceptDrops(true);
636 QFontMetrics const fm(statusBar()->fontMetrics());
637 int const iconheight = max(int(d.normalIconSize), fm.height());
638 QSize const iconsize(iconheight, iconheight);
640 // add busy indicator to statusbar
641 search_mode mode = theGuiApp()->imageSearchMode();
642 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
643 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
644 statusBar()->addPermanentWidget(busySVG);
645 // make busy indicator square with 5px margins
646 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
649 QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize);
650 GuiClickableLabel * processStop = new GuiClickableLabel(statusBar());
651 processStop->setPixmap(ps);
652 processStop->setToolTip(qt_("Click here to stop export/output process"));
654 statusBar()->addPermanentWidget(processStop);
656 connect(&d.processing_thread_watcher_, SIGNAL(started()),
657 busySVG, SLOT(show()));
658 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
659 busySVG, SLOT(hide()));
660 connect(&d.processing_thread_watcher_, SIGNAL(started()),
661 processStop, SLOT(show()));
662 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
663 processStop, SLOT(hide()));
664 connect(processStop, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
666 connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
667 connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
669 stat_counts_ = new GuiClickableLabel(statusBar());
670 stat_counts_->setAlignment(Qt::AlignCenter);
671 stat_counts_->setFrameStyle(QFrame::StyledPanel);
672 stat_counts_->hide();
673 statusBar()->addPermanentWidget(stat_counts_);
675 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
677 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
678 // Small size slider for macOS to prevent the status bar from enlarging
679 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
680 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
681 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
683 zoom_slider_->setFixedWidth(fm.width('x') * 15);
685 // Make the defaultZoom center
686 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
687 // Initialize proper zoom value
689 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
690 // Actual zoom value: default zoom + fractional offset
691 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
692 zoom = min(max(zoom, zoom_min_), zoom_max_);
693 zoom_slider_->setValue(zoom);
694 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
696 // Buttons to change zoom stepwise
697 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
698 QSize s(fm.horizontalAdvance('+'), fm.height());
700 QSize s(fm.width('+'), fm.height());
702 zoom_in_ = new GuiClickableLabel(statusBar());
703 zoom_in_->setText("+");
704 zoom_in_->setFixedSize(s);
705 zoom_in_->setAlignment(Qt::AlignCenter);
706 zoom_out_ = new GuiClickableLabel(statusBar());
707 zoom_out_->setText(QString(QChar(0x2212)));
708 zoom_out_->setFixedSize(s);
709 zoom_out_->setAlignment(Qt::AlignCenter);
711 statusBar()->addPermanentWidget(zoom_out_);
712 zoom_out_->setEnabled(currentBufferView());
713 statusBar()->addPermanentWidget(zoom_slider_);
714 zoom_slider_->setEnabled(currentBufferView());
715 zoom_in_->setEnabled(currentBufferView());
716 statusBar()->addPermanentWidget(zoom_in_);
718 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
719 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
720 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
721 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
722 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
724 // QPalette palette = statusBar()->palette();
726 zoom_value_ = new GuiClickableLabel(statusBar());
727 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
728 // zoom_value_->setPalette(palette);
729 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
730 zoom_value_->setFixedHeight(fm.height());
731 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
732 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
734 zoom_value_->setMinimumWidth(fm.width("444\%"));
736 zoom_value_->setAlignment(Qt::AlignCenter);
737 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
738 statusBar()->addPermanentWidget(zoom_value_);
739 zoom_value_->setEnabled(currentBufferView());
741 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
742 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
743 this, SLOT(showStatusBarContextMenu()));
745 // enable pinch to zoom
746 grabGesture(Qt::PinchGesture);
748 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
749 shell_escape_ = new QLabel(statusBar());
750 shell_escape_->setPixmap(shellescape);
751 shell_escape_->setAlignment(Qt::AlignCenter);
752 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
753 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
754 "external commands for this document. "
755 "Right click to change."));
756 SEMenu * menu = new SEMenu(this);
757 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
758 menu, SLOT(showMenu(QPoint)));
759 shell_escape_->hide();
760 statusBar()->addPermanentWidget(shell_escape_);
762 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
763 read_only_ = new QLabel(statusBar());
764 read_only_->setPixmap(readonly);
765 read_only_->setAlignment(Qt::AlignCenter);
767 statusBar()->addPermanentWidget(read_only_);
769 version_control_ = new QLabel(statusBar());
770 version_control_->setAlignment(Qt::AlignCenter);
771 version_control_->setFrameStyle(QFrame::StyledPanel);
772 version_control_->hide();
773 statusBar()->addPermanentWidget(version_control_);
775 statusBar()->setSizeGripEnabled(true);
778 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
779 SLOT(autoSaveThreadFinished()));
781 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
782 SLOT(processingThreadStarted()));
783 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
784 SLOT(processingThreadFinished()));
786 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
787 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
789 // set custom application bars context menu, e.g. tool bar and menu bar
790 setContextMenuPolicy(Qt::CustomContextMenu);
791 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
792 SLOT(toolBarPopup(const QPoint &)));
794 // Forbid too small unresizable window because it can happen
795 // with some window manager under X11.
796 setMinimumSize(300, 200);
798 if (lyxrc.allow_geometry_session) {
799 // Now take care of session management.
804 // no session handling, default to a sane size.
805 setGeometry(50, 50, 690, 510);
808 // clear session data if any.
809 settings.remove("views");
819 void GuiView::disableShellEscape()
821 BufferView * bv = documentBufferView();
824 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
825 bv->buffer().params().shell_escape = false;
826 bv->processUpdateFlags(Update::Force);
830 void GuiView::checkCancelBackground()
832 docstring const ttl = _("Cancel Export?");
833 docstring const msg = _("Do you want to cancel the background export process?");
835 Alert::prompt(ttl, msg, 1, 1,
836 _("&Cancel export"), _("Co&ntinue"));
838 Systemcall::killscript();
839 // stop busy signal immediately so that in the subsequent
840 // "Export canceled" prompt the status bar icons are accurate.
841 Q_EMIT scriptKilled();
845 void GuiView::statsPressed()
848 dispatch(FuncRequest(LFUN_STATISTICS), dr);
851 void GuiView::zoomSliderMoved(int value)
854 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
855 scheduleRedrawWorkAreas();
856 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
860 void GuiView::zoomValueChanged(int value)
862 if (value != lyxrc.currentZoom)
863 zoomSliderMoved(value);
867 void GuiView::zoomInPressed()
870 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
871 scheduleRedrawWorkAreas();
875 void GuiView::zoomOutPressed()
878 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
879 scheduleRedrawWorkAreas();
883 void GuiView::showZoomContextMenu()
885 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
888 menu->exec(QCursor::pos());
892 void GuiView::showStatusBarContextMenu()
894 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
897 menu->exec(QCursor::pos());
901 void GuiView::scheduleRedrawWorkAreas()
903 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
904 TabWorkArea* ta = d.tabWorkArea(i);
905 for (int u = 0; u < ta->count(); u++) {
906 ta->workArea(u)->scheduleRedraw(true);
912 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
914 QVector<GuiWorkArea*> areas;
915 for (int i = 0; i < tabWorkAreaCount(); i++) {
916 TabWorkArea* ta = tabWorkArea(i);
917 for (int u = 0; u < ta->count(); u++) {
918 areas << ta->workArea(u);
924 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
925 string const & format)
927 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
930 case Buffer::ExportSuccess:
931 msg = bformat(_("Successful export to format: %1$s"), fmt);
933 case Buffer::ExportCancel:
934 msg = _("Document export cancelled.");
936 case Buffer::ExportError:
937 case Buffer::ExportNoPathToFormat:
938 case Buffer::ExportTexPathHasSpaces:
939 case Buffer::ExportConverterError:
940 msg = bformat(_("Error while exporting format: %1$s"), fmt);
942 case Buffer::PreviewSuccess:
943 msg = bformat(_("Successful preview of format: %1$s"), fmt);
945 case Buffer::PreviewError:
946 msg = bformat(_("Error while previewing format: %1$s"), fmt);
948 case Buffer::ExportKilled:
949 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
956 void GuiView::processingThreadStarted()
961 void GuiView::processingThreadFinished()
963 QFutureWatcher<Buffer::ExportStatus> const * watcher =
964 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
966 Buffer::ExportStatus const status = watcher->result();
967 handleExportStatus(this, status, d.processing_format);
970 BufferView const * const bv = currentBufferView();
971 if (bv && !bv->buffer().errorList("Export").empty()) {
976 bool const error = (status != Buffer::ExportSuccess &&
977 status != Buffer::PreviewSuccess &&
978 status != Buffer::ExportCancel);
980 ErrorList & el = bv->buffer().errorList(d.last_export_format);
981 // at this point, we do not know if buffer-view or
982 // master-buffer-view was called. If there was an export error,
983 // and the current buffer's error log is empty, we guess that
984 // it must be master-buffer-view that was called so we set
986 errors(d.last_export_format, el.empty());
991 void GuiView::autoSaveThreadFinished()
993 QFutureWatcher<docstring> const * watcher =
994 static_cast<QFutureWatcher<docstring> const *>(sender());
995 message(watcher->result());
1000 void GuiView::saveLayout() const
1003 settings.setValue("zoom_ratio", zoom_ratio_);
1004 settings.setValue("devel_mode", devel_mode_);
1005 settings.beginGroup("views");
1006 settings.beginGroup(QString::number(id_));
1007 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1008 settings.setValue("pos", pos());
1009 settings.setValue("size", size());
1011 settings.setValue("geometry", saveGeometry());
1012 settings.setValue("layout", saveState(0));
1013 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
1014 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
1015 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
1016 settings.setValue("word_count_enabled", word_count_enabled_);
1017 settings.setValue("char_count_enabled", char_count_enabled_);
1018 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1022 void GuiView::saveUISettings() const
1026 // Save the toolbar private states
1027 for (auto const & tb_p : d.toolbars_)
1028 tb_p.second->saveSession(settings);
1029 // Now take care of all other dialogs
1030 for (auto const & dlg_p : d.dialogs_)
1031 dlg_p.second->saveSession(settings);
1035 void GuiView::setCurrentZoom(const int v)
1037 lyxrc.currentZoom = v;
1038 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1039 Q_EMIT currentZoomChanged(v);
1043 bool GuiView::restoreLayout()
1046 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1047 // Actual zoom value: default zoom + fractional offset
1048 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1049 zoom = min(max(zoom, zoom_min_), zoom_max_);
1050 setCurrentZoom(zoom);
1051 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1052 settings.beginGroup("views");
1053 settings.beginGroup(QString::number(id_));
1054 QString const icon_key = "icon_size";
1055 if (!settings.contains(icon_key))
1058 //code below is skipped when when ~/.config/LyX is (re)created
1059 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1061 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1063 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1064 zoom_slider_->setVisible(show_zoom_slider);
1065 zoom_in_->setVisible(show_zoom_slider);
1066 zoom_out_->setVisible(show_zoom_slider);
1068 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1069 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1070 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1071 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1073 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1074 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1075 QSize size = settings.value("size", QSize(690, 510)).toSize();
1079 // Work-around for bug #6034: the window ends up in an undetermined
1080 // state when trying to restore a maximized window when it is
1081 // already maximized.
1082 if (!(windowState() & Qt::WindowMaximized))
1083 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1084 setGeometry(50, 50, 690, 510);
1087 // Make sure layout is correctly oriented.
1088 setLayoutDirection(qApp->layoutDirection());
1090 // Allow the toc and view-source dock widget to be restored if needed.
1092 if ((dialog = findOrBuild("toc", true)))
1093 // see bug 5082. At least setup title and enabled state.
1094 // Visibility will be adjusted by restoreState below.
1095 dialog->prepareView();
1096 if ((dialog = findOrBuild("view-source", true)))
1097 dialog->prepareView();
1098 if ((dialog = findOrBuild("progress", true)))
1099 dialog->prepareView();
1101 if (!restoreState(settings.value("layout").toByteArray(), 0))
1104 // init the toolbars that have not been restored
1105 for (auto const & tb_p : guiApp->toolbars()) {
1106 GuiToolbar * tb = toolbar(tb_p.name);
1107 if (tb && !tb->isRestored())
1108 initToolbar(tb_p.name);
1111 // update lock (all) toolbars positions
1112 updateLockToolbars();
1119 GuiToolbar * GuiView::toolbar(string const & name)
1121 ToolbarMap::iterator it = d.toolbars_.find(name);
1122 if (it != d.toolbars_.end())
1125 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1130 void GuiView::updateLockToolbars()
1132 toolbarsMovable_ = false;
1133 for (ToolbarInfo const & info : guiApp->toolbars()) {
1134 GuiToolbar * tb = toolbar(info.name);
1135 if (tb && tb->isMovable())
1136 toolbarsMovable_ = true;
1138 #if QT_VERSION >= 0x050200
1139 // set unified mac toolbars only when not movable as recommended:
1140 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1141 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1146 void GuiView::constructToolbars()
1148 for (auto const & tb_p : d.toolbars_)
1150 d.toolbars_.clear();
1152 // I don't like doing this here, but the standard toolbar
1153 // destroys this object when it's destroyed itself (vfr)
1154 d.layout_ = new LayoutBox(*this);
1155 d.stack_widget_->addWidget(d.layout_);
1156 d.layout_->move(0,0);
1158 // extracts the toolbars from the backend
1159 for (ToolbarInfo const & inf : guiApp->toolbars())
1160 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1162 DynamicMenuButton::resetIconCache();
1166 void GuiView::initToolbars()
1168 // extracts the toolbars from the backend
1169 for (ToolbarInfo const & inf : guiApp->toolbars())
1170 initToolbar(inf.name);
1174 void GuiView::initToolbar(string const & name)
1176 GuiToolbar * tb = toolbar(name);
1179 int const visibility = guiApp->toolbars().defaultVisibility(name);
1180 bool newline = !(visibility & Toolbars::SAMEROW);
1181 tb->setVisible(false);
1182 tb->setVisibility(visibility);
1184 if (visibility & Toolbars::TOP) {
1186 addToolBarBreak(Qt::TopToolBarArea);
1187 addToolBar(Qt::TopToolBarArea, tb);
1190 if (visibility & Toolbars::BOTTOM) {
1192 addToolBarBreak(Qt::BottomToolBarArea);
1193 addToolBar(Qt::BottomToolBarArea, tb);
1196 if (visibility & Toolbars::LEFT) {
1198 addToolBarBreak(Qt::LeftToolBarArea);
1199 addToolBar(Qt::LeftToolBarArea, tb);
1202 if (visibility & Toolbars::RIGHT) {
1204 addToolBarBreak(Qt::RightToolBarArea);
1205 addToolBar(Qt::RightToolBarArea, tb);
1208 if (visibility & Toolbars::ON)
1209 tb->setVisible(true);
1211 tb->setMovable(true);
1215 TocModels & GuiView::tocModels()
1217 return d.toc_models_;
1221 void GuiView::setFocus()
1223 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1224 QMainWindow::setFocus();
1228 bool GuiView::hasFocus() const
1230 if (currentWorkArea())
1231 return currentWorkArea()->hasFocus();
1232 if (currentMainWorkArea())
1233 return currentMainWorkArea()->hasFocus();
1234 return d.bg_widget_->hasFocus();
1238 void GuiView::focusInEvent(QFocusEvent * e)
1240 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1241 QMainWindow::focusInEvent(e);
1242 // Make sure guiApp points to the correct view.
1243 guiApp->setCurrentView(this);
1244 if (currentWorkArea())
1245 currentWorkArea()->setFocus();
1246 else if (currentMainWorkArea())
1247 currentMainWorkArea()->setFocus();
1249 d.bg_widget_->setFocus();
1253 void GuiView::showEvent(QShowEvent * e)
1255 LYXERR(Debug::GUI, "Passed Geometry "
1256 << size().height() << "x" << size().width()
1257 << "+" << pos().x() << "+" << pos().y());
1259 if (d.splitter_->count() == 0)
1260 // No work area, switch to the background widget.
1264 QMainWindow::showEvent(e);
1268 bool GuiView::closeScheduled()
1275 bool GuiView::prepareAllBuffersForLogout()
1277 Buffer * first = theBufferList().first();
1281 // First, iterate over all buffers and ask the users if unsaved
1282 // changes should be saved.
1283 // We cannot use a for loop as the buffer list cycles.
1286 if (!saveBufferIfNeeded(*b, false))
1288 b = theBufferList().next(b);
1289 } while (b != first);
1291 // Next, save session state
1292 // When a view/window was closed before without quitting LyX, there
1293 // are already entries in the lastOpened list.
1294 theSession().lastOpened().clear();
1301 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1302 ** is responsibility of the container (e.g., dialog)
1304 void GuiView::closeEvent(QCloseEvent * close_event)
1306 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1308 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1309 Alert::warning(_("Exit LyX"),
1310 _("LyX could not be closed because documents are being processed by LyX."));
1311 close_event->setAccepted(false);
1315 // If the user pressed the x (so we didn't call closeView
1316 // programmatically), we want to clear all existing entries.
1318 theSession().lastOpened().clear();
1323 // it can happen that this event arrives without selecting the view,
1324 // e.g. when clicking the close button on a background window.
1326 if (!closeWorkAreaAll()) {
1328 close_event->ignore();
1332 // Make sure that nothing will use this to be closed View.
1333 guiApp->unregisterView(this);
1335 if (isFullScreen()) {
1336 // Switch off fullscreen before closing.
1341 // Make sure the timer time out will not trigger a statusbar update.
1342 d.statusbar_timer_.stop();
1343 d.statusbar_stats_timer_.stop();
1345 // Saving fullscreen requires additional tweaks in the toolbar code.
1346 // It wouldn't also work under linux natively.
1347 if (lyxrc.allow_geometry_session) {
1352 close_event->accept();
1356 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1358 if (event->mimeData()->hasUrls())
1360 /// \todo Ask lyx-devel is this is enough:
1361 /// if (event->mimeData()->hasFormat("text/plain"))
1362 /// event->acceptProposedAction();
1366 void GuiView::dropEvent(QDropEvent * event)
1368 QList<QUrl> files = event->mimeData()->urls();
1369 if (files.isEmpty())
1372 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1373 for (int i = 0; i != files.size(); ++i) {
1374 string const file = os::internal_path(fromqstr(
1375 files.at(i).toLocalFile()));
1379 string const ext = support::getExtension(file);
1380 vector<const Format *> found_formats;
1382 // Find all formats that have the correct extension.
1383 for (const Format * fmt : theConverters().importableFormats())
1384 if (fmt->hasExtension(ext))
1385 found_formats.push_back(fmt);
1388 if (!found_formats.empty()) {
1389 if (found_formats.size() > 1) {
1390 //FIXME: show a dialog to choose the correct importable format
1391 LYXERR(Debug::FILES,
1392 "Multiple importable formats found, selecting first");
1394 string const arg = found_formats[0]->name() + " " + file;
1395 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1398 //FIXME: do we have to explicitly check whether it's a lyx file?
1399 LYXERR(Debug::FILES,
1400 "No formats found, trying to open it as a lyx file");
1401 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1403 // add the functions to the queue
1404 guiApp->addToFuncRequestQueue(cmd);
1407 // now process the collected functions. We perform the events
1408 // asynchronously. This prevents potential problems in case the
1409 // BufferView is closed within an event.
1410 guiApp->processFuncRequestQueueAsync();
1414 void GuiView::message(docstring const & str)
1416 if (ForkedProcess::iAmAChild())
1419 // call is moved to GUI-thread by GuiProgress
1420 d.progress_->appendMessage(toqstr(str));
1424 void GuiView::clearMessageText()
1426 message(docstring());
1430 void GuiView::updateStatusBarMessage(QString const & str)
1432 statusBar()->showMessage(str);
1433 d.statusbar_timer_.stop();
1434 d.statusbar_timer_.start(3000);
1438 void GuiView::clearMessage()
1440 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1441 // the hasFocus function mostly returns false, even if the focus is on
1442 // a workarea in this view.
1446 d.statusbar_timer_.stop();
1449 void GuiView::showStats()
1451 if (!statsEnabled())
1454 d.time_to_update -= d.timer_rate;
1456 BufferView * bv = currentBufferView();
1457 Buffer * buf = bv ? &bv->buffer() : nullptr;
1459 stat_counts_->hide();
1463 Cursor const & cur = bv->cursor();
1465 // we start new selection and need faster update
1466 if (!d.already_in_selection_ && cur.selection())
1467 d.time_to_update = 0;
1469 if (d.time_to_update > 0)
1472 DocIterator from, to;
1473 if (cur.selection()) {
1474 from = cur.selectionBegin();
1475 to = cur.selectionEnd();
1476 d.already_in_selection_ = true;
1478 from = doc_iterator_begin(buf);
1479 to = doc_iterator_end(buf);
1480 d.already_in_selection_ = false;
1483 buf->updateStatistics(from, to);
1486 if (word_count_enabled_) {
1487 int const words = buf->wordCount();
1489 stats << toqstr(bformat(_("%1$d Word"), words));
1491 stats << toqstr(bformat(_("%1$d Words"), words));
1493 int const chars_with_blanks = buf->charCount(true);
1494 if (char_count_enabled_) {
1495 if (chars_with_blanks == 1)
1496 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1498 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1500 if (char_nb_count_enabled_) {
1501 int const chars = buf->charCount(false);
1503 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1505 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1507 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1508 stat_counts_->show();
1510 d.time_to_update = d.default_stats_rate;
1511 // fast updates for small selections
1512 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1513 d.time_to_update = d.timer_rate;
1517 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1519 if (wa != d.current_work_area_
1520 || wa->bufferView().buffer().isInternal())
1522 Buffer const & buf = wa->bufferView().buffer();
1523 // Set the windows title
1524 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1525 if (buf.notifiesExternalModification()) {
1526 title = bformat(_("%1$s (modified externally)"), title);
1527 // If the external modification status has changed, then maybe the status of
1528 // buffer-save has changed too.
1532 title += from_ascii(" - LyX");
1534 setWindowTitle(toqstr(title));
1535 // Sets the path for the window: this is used by OSX to
1536 // allow a context click on the title bar showing a menu
1537 // with the path up to the file
1538 setWindowFilePath(toqstr(buf.absFileName()));
1539 // Tell Qt whether the current document is changed
1540 setWindowModified(!buf.isClean());
1542 if (buf.params().shell_escape)
1543 shell_escape_->show();
1545 shell_escape_->hide();
1547 if (buf.hasReadonlyFlag())
1552 if (buf.lyxvc().inUse()) {
1553 version_control_->show();
1554 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1556 version_control_->hide();
1560 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1562 if (d.current_work_area_)
1563 // disconnect the current work area from all slots
1564 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1566 disconnectBufferView();
1567 connectBufferView(wa->bufferView());
1568 connectBuffer(wa->bufferView().buffer());
1569 d.current_work_area_ = wa;
1570 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1571 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1572 QObject::connect(wa, SIGNAL(busy(bool)),
1573 this, SLOT(setBusy(bool)));
1574 // connection of a signal to a signal
1575 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1576 this, SIGNAL(bufferViewChanged()));
1577 Q_EMIT updateWindowTitle(wa);
1578 Q_EMIT bufferViewChanged();
1582 void GuiView::onBufferViewChanged()
1585 // Buffer-dependent dialogs must be updated. This is done here because
1586 // some dialogs require buffer()->text.
1588 zoom_slider_->setEnabled(currentBufferView());
1589 zoom_value_->setEnabled(currentBufferView());
1590 zoom_in_->setEnabled(currentBufferView());
1591 zoom_out_->setEnabled(currentBufferView());
1595 void GuiView::on_lastWorkAreaRemoved()
1598 // We already are in a close event. Nothing more to do.
1601 if (d.splitter_->count() > 1)
1602 // We have a splitter so don't close anything.
1605 // Reset and updates the dialogs.
1606 Q_EMIT bufferViewChanged();
1611 if (lyxrc.open_buffers_in_tabs)
1612 // Nothing more to do, the window should stay open.
1615 if (guiApp->viewIds().size() > 1) {
1621 // On Mac we also close the last window because the application stay
1622 // resident in memory. On other platforms we don't close the last
1623 // window because this would quit the application.
1629 void GuiView::updateStatusBar()
1631 // let the user see the explicit message
1632 if (d.statusbar_timer_.isActive())
1639 void GuiView::showMessage()
1643 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1644 if (msg.isEmpty()) {
1645 BufferView const * bv = currentBufferView();
1647 msg = toqstr(bv->cursor().currentState(devel_mode_));
1649 msg = qt_("Welcome to LyX!");
1651 statusBar()->showMessage(msg);
1655 bool GuiView::statsEnabled() const
1657 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1661 bool GuiView::event(QEvent * e)
1665 // Useful debug code:
1666 //case QEvent::ActivationChange:
1667 //case QEvent::WindowDeactivate:
1668 //case QEvent::Paint:
1669 //case QEvent::Enter:
1670 //case QEvent::Leave:
1671 //case QEvent::HoverEnter:
1672 //case QEvent::HoverLeave:
1673 //case QEvent::HoverMove:
1674 //case QEvent::StatusTip:
1675 //case QEvent::DragEnter:
1676 //case QEvent::DragLeave:
1677 //case QEvent::Drop:
1680 case QEvent::WindowStateChange: {
1681 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1682 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1683 bool result = QMainWindow::event(e);
1684 bool nfstate = (windowState() & Qt::WindowFullScreen);
1685 if (!ofstate && nfstate) {
1686 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1687 // switch to full-screen state
1688 if (lyxrc.full_screen_statusbar)
1689 statusBar()->hide();
1690 if (lyxrc.full_screen_menubar)
1692 if (lyxrc.full_screen_toolbars) {
1693 for (auto const & tb_p : d.toolbars_)
1694 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1695 tb_p.second->hide();
1697 for (int i = 0; i != d.splitter_->count(); ++i)
1698 d.tabWorkArea(i)->setFullScreen(true);
1699 #if QT_VERSION > 0x050903
1700 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1701 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1703 setContentsMargins(-2, -2, -2, -2);
1705 hideDialogs("prefs", nullptr);
1706 } else if (ofstate && !nfstate) {
1707 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1708 // switch back from full-screen state
1709 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1710 statusBar()->show();
1711 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1713 if (lyxrc.full_screen_toolbars) {
1714 for (auto const & tb_p : d.toolbars_)
1715 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1716 tb_p.second->show();
1719 for (int i = 0; i != d.splitter_->count(); ++i)
1720 d.tabWorkArea(i)->setFullScreen(false);
1721 #if QT_VERSION > 0x050903
1722 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1724 setContentsMargins(0, 0, 0, 0);
1729 case QEvent::WindowActivate: {
1730 GuiView * old_view = guiApp->currentView();
1731 if (this == old_view) {
1733 return QMainWindow::event(e);
1735 if (old_view && old_view->currentBufferView()) {
1736 // save current selection to the selection buffer to allow
1737 // middle-button paste in this window.
1738 cap::saveSelection(old_view->currentBufferView()->cursor());
1740 guiApp->setCurrentView(this);
1741 if (d.current_work_area_)
1742 on_currentWorkAreaChanged(d.current_work_area_);
1746 return QMainWindow::event(e);
1749 case QEvent::ShortcutOverride: {
1751 if (isFullScreen() && menuBar()->isHidden()) {
1752 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1753 // FIXME: we should also try to detect special LyX shortcut such as
1754 // Alt-P and Alt-M. Right now there is a hack in
1755 // GuiWorkArea::processKeySym() that hides again the menubar for
1757 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1759 return QMainWindow::event(e);
1762 return QMainWindow::event(e);
1765 case QEvent::ApplicationPaletteChange: {
1766 // runtime switch from/to dark mode
1768 return QMainWindow::event(e);
1771 case QEvent::Gesture: {
1772 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1773 QGesture *gp = ge->gesture(Qt::PinchGesture);
1775 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1776 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1777 qreal totalScaleFactor = pinch->totalScaleFactor();
1778 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1779 if (pinch->state() == Qt::GestureStarted) {
1780 initialZoom_ = lyxrc.currentZoom;
1781 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1783 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1784 qreal factor = initialZoom_ * totalScaleFactor;
1785 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1786 zoomValueChanged(factor);
1789 return QMainWindow::event(e);
1793 return QMainWindow::event(e);
1797 void GuiView::resetWindowTitle()
1799 setWindowTitle(qt_("LyX"));
1802 bool GuiView::focusNextPrevChild(bool /*next*/)
1809 bool GuiView::busy() const
1815 void GuiView::setBusy(bool busy)
1817 bool const busy_before = busy_ > 0;
1818 busy ? ++busy_ : --busy_;
1819 if ((busy_ > 0) == busy_before)
1820 // busy state didn't change
1824 QApplication::setOverrideCursor(Qt::WaitCursor);
1827 QApplication::restoreOverrideCursor();
1832 void GuiView::resetCommandExecute()
1834 command_execute_ = false;
1839 double GuiView::pixelRatio() const
1841 #if QT_VERSION >= 0x050000
1842 return qt_scale_factor * devicePixelRatio();
1849 GuiWorkArea * GuiView::workArea(int index)
1851 if (TabWorkArea * twa = d.currentTabWorkArea())
1852 if (index < twa->count())
1853 return twa->workArea(index);
1858 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1860 if (currentWorkArea()
1861 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1862 return currentWorkArea();
1863 if (TabWorkArea * twa = d.currentTabWorkArea())
1864 return twa->workArea(buffer);
1869 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1871 // Automatically create a TabWorkArea if there are none yet.
1872 TabWorkArea * tab_widget = d.splitter_->count()
1873 ? d.currentTabWorkArea() : addTabWorkArea();
1874 return tab_widget->addWorkArea(buffer, *this);
1878 TabWorkArea * GuiView::addTabWorkArea()
1880 TabWorkArea * twa = new TabWorkArea;
1881 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1882 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1883 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1884 this, SLOT(on_lastWorkAreaRemoved()));
1886 d.splitter_->addWidget(twa);
1887 d.stack_widget_->setCurrentWidget(d.splitter_);
1892 GuiWorkArea const * GuiView::currentWorkArea() const
1894 return d.current_work_area_;
1898 GuiWorkArea * GuiView::currentWorkArea()
1900 return d.current_work_area_;
1904 GuiWorkArea const * GuiView::currentMainWorkArea() const
1906 if (!d.currentTabWorkArea())
1908 return d.currentTabWorkArea()->currentWorkArea();
1912 GuiWorkArea * GuiView::currentMainWorkArea()
1914 if (!d.currentTabWorkArea())
1916 return d.currentTabWorkArea()->currentWorkArea();
1920 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1922 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1924 d.current_work_area_ = nullptr;
1926 Q_EMIT bufferViewChanged();
1930 // FIXME: I've no clue why this is here and why it accesses
1931 // theGuiApp()->currentView, which might be 0 (bug 6464).
1932 // See also 27525 (vfr).
1933 if (theGuiApp()->currentView() == this
1934 && theGuiApp()->currentView()->currentWorkArea() == wa)
1937 if (currentBufferView())
1938 cap::saveSelection(currentBufferView()->cursor());
1940 theGuiApp()->setCurrentView(this);
1941 d.current_work_area_ = wa;
1943 // We need to reset this now, because it will need to be
1944 // right if the tabWorkArea gets reset in the for loop. We
1945 // will change it back if we aren't in that case.
1946 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1947 d.current_main_work_area_ = wa;
1949 for (int i = 0; i != d.splitter_->count(); ++i) {
1950 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1951 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1952 << ", Current main wa: " << currentMainWorkArea());
1957 d.current_main_work_area_ = old_cmwa;
1959 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1960 on_currentWorkAreaChanged(wa);
1961 BufferView & bv = wa->bufferView();
1962 bv.cursor().fixIfBroken();
1964 wa->setUpdatesEnabled(true);
1965 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1969 void GuiView::removeWorkArea(GuiWorkArea * wa)
1971 LASSERT(wa, return);
1972 if (wa == d.current_work_area_) {
1974 disconnectBufferView();
1975 d.current_work_area_ = nullptr;
1976 d.current_main_work_area_ = nullptr;
1979 bool found_twa = false;
1980 for (int i = 0; i != d.splitter_->count(); ++i) {
1981 TabWorkArea * twa = d.tabWorkArea(i);
1982 if (twa->removeWorkArea(wa)) {
1983 // Found in this tab group, and deleted the GuiWorkArea.
1985 if (twa->count() != 0) {
1986 if (d.current_work_area_ == nullptr)
1987 // This means that we are closing the current GuiWorkArea, so
1988 // switch to the next GuiWorkArea in the found TabWorkArea.
1989 setCurrentWorkArea(twa->currentWorkArea());
1991 // No more WorkAreas in this tab group, so delete it.
1998 // It is not a tabbed work area (i.e., the search work area), so it
1999 // should be deleted by other means.
2000 LASSERT(found_twa, return);
2002 if (d.current_work_area_ == nullptr) {
2003 if (d.splitter_->count() != 0) {
2004 TabWorkArea * twa = d.currentTabWorkArea();
2005 setCurrentWorkArea(twa->currentWorkArea());
2007 // No more work areas, switch to the background widget.
2008 setCurrentWorkArea(nullptr);
2014 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2016 for (int i = 0; i < d.splitter_->count(); ++i)
2017 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2020 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2021 return fr->isVisible() && fr->hasWorkArea(wa);
2025 LayoutBox * GuiView::getLayoutDialog() const
2031 void GuiView::updateLayoutList()
2034 d.layout_->updateContents(false);
2038 void GuiView::updateToolbars()
2040 if (d.current_work_area_) {
2042 if (d.current_work_area_->bufferView().cursor().inMathed()
2043 && !d.current_work_area_->bufferView().cursor().inRegexped())
2044 context |= Toolbars::MATH;
2045 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2046 context |= Toolbars::TABLE;
2047 if (currentBufferView()->buffer().areChangesPresent()
2048 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2049 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2050 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2051 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2052 context |= Toolbars::REVIEW;
2053 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2054 context |= Toolbars::MATHMACROTEMPLATE;
2055 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2056 context |= Toolbars::IPA;
2057 if (command_execute_)
2058 context |= Toolbars::MINIBUFFER;
2059 if (minibuffer_focus_) {
2060 context |= Toolbars::MINIBUFFER_FOCUS;
2061 minibuffer_focus_ = false;
2064 for (auto const & tb_p : d.toolbars_)
2065 tb_p.second->update(context);
2067 for (auto const & tb_p : d.toolbars_)
2068 tb_p.second->update();
2072 void GuiView::refillToolbars()
2074 DynamicMenuButton::resetIconCache();
2075 for (auto const & tb_p : d.toolbars_)
2076 tb_p.second->refill();
2080 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2082 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2083 LASSERT(newBuffer, return);
2085 GuiWorkArea * wa = workArea(*newBuffer);
2086 if (wa == nullptr) {
2088 newBuffer->masterBuffer()->updateBuffer();
2090 wa = addWorkArea(*newBuffer);
2091 // scroll to the position when the BufferView was last closed
2092 if (lyxrc.use_lastfilepos) {
2093 LastFilePosSection::FilePos filepos =
2094 theSession().lastFilePos().load(newBuffer->fileName());
2095 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2098 //Disconnect the old buffer...there's no new one.
2101 connectBuffer(*newBuffer);
2102 connectBufferView(wa->bufferView());
2104 setCurrentWorkArea(wa);
2108 void GuiView::connectBuffer(Buffer & buf)
2110 buf.setGuiDelegate(this);
2114 void GuiView::disconnectBuffer()
2116 if (d.current_work_area_)
2117 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2121 void GuiView::connectBufferView(BufferView & bv)
2123 bv.setGuiDelegate(this);
2127 void GuiView::disconnectBufferView()
2129 if (d.current_work_area_)
2130 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2134 void GuiView::errors(string const & error_type, bool from_master)
2136 BufferView const * const bv = currentBufferView();
2140 ErrorList const & el = from_master ?
2141 bv->buffer().masterBuffer()->errorList(error_type) :
2142 bv->buffer().errorList(error_type);
2147 string err = error_type;
2149 err = "from_master|" + error_type;
2150 showDialog("errorlist", err);
2154 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2156 d.toc_models_.updateItem(toqstr(type), dit);
2160 void GuiView::structureChanged()
2162 // This is called from the Buffer, which has no way to ensure that cursors
2163 // in BufferView remain valid.
2164 if (documentBufferView())
2165 documentBufferView()->cursor().sanitize();
2166 // FIXME: This is slightly expensive, though less than the tocBackend update
2167 // (#9880). This also resets the view in the Toc Widget (#6675).
2168 d.toc_models_.reset(documentBufferView());
2169 // Navigator needs more than a simple update in this case. It needs to be
2171 updateDialog("toc", "");
2175 void GuiView::updateDialog(string const & name, string const & sdata)
2177 if (!isDialogVisible(name))
2180 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2181 if (it == d.dialogs_.end())
2184 Dialog * const dialog = it->second.get();
2185 if (dialog->isVisibleView())
2186 dialog->initialiseParams(sdata);
2190 BufferView * GuiView::documentBufferView()
2192 return currentMainWorkArea()
2193 ? ¤tMainWorkArea()->bufferView()
2198 BufferView const * GuiView::documentBufferView() const
2200 return currentMainWorkArea()
2201 ? ¤tMainWorkArea()->bufferView()
2206 BufferView * GuiView::currentBufferView()
2208 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2212 BufferView const * GuiView::currentBufferView() const
2214 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2218 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2219 Buffer const * orig, Buffer * clone)
2221 bool const success = clone->autoSave();
2223 busyBuffers.remove(orig);
2225 ? _("Automatic save done.")
2226 : _("Automatic save failed!");
2230 void GuiView::autoSave()
2232 LYXERR(Debug::INFO, "Running autoSave()");
2234 Buffer * buffer = documentBufferView()
2235 ? &documentBufferView()->buffer() : nullptr;
2237 resetAutosaveTimers();
2241 GuiViewPrivate::busyBuffers.insert(buffer);
2242 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2243 buffer, buffer->cloneBufferOnly());
2244 d.autosave_watcher_.setFuture(f);
2245 resetAutosaveTimers();
2249 void GuiView::resetAutosaveTimers()
2252 d.autosave_timeout_.restart();
2258 double zoomRatio(FuncRequest const & cmd, double const zr)
2260 if (cmd.argument().empty()) {
2261 if (cmd.action() == LFUN_BUFFER_ZOOM)
2263 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2265 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2268 if (cmd.action() == LFUN_BUFFER_ZOOM)
2269 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2270 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2271 return zr + convert<int>(cmd.argument()) / 100.0;
2272 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2273 return zr - convert<int>(cmd.argument()) / 100.0;
2280 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2283 Buffer * buf = currentBufferView()
2284 ? ¤tBufferView()->buffer() : nullptr;
2285 Buffer * doc_buffer = documentBufferView()
2286 ? &(documentBufferView()->buffer()) : nullptr;
2289 /* In LyX/Mac, when a dialog is open, the menus of the
2290 application can still be accessed without giving focus to
2291 the main window. In this case, we want to disable the menu
2292 entries that are buffer-related.
2293 This code must not be used on Linux and Windows, since it
2294 would disable buffer-related entries when hovering over the
2295 menu (see bug #9574).
2297 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2303 // Check whether we need a buffer
2304 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2305 // no, exit directly
2306 flag.message(from_utf8(N_("Command not allowed with"
2307 "out any document open")));
2308 flag.setEnabled(false);
2312 if (cmd.origin() == FuncRequest::TOC) {
2313 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2314 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2315 flag.setEnabled(false);
2319 switch(cmd.action()) {
2320 case LFUN_BUFFER_IMPORT:
2323 case LFUN_MASTER_BUFFER_EXPORT:
2325 && (doc_buffer->parent() != nullptr
2326 || doc_buffer->hasChildren())
2327 && !d.processing_thread_watcher_.isRunning()
2328 // this launches a dialog, which would be in the wrong Buffer
2329 && !(::lyx::operator==(cmd.argument(), "custom"));
2332 case LFUN_MASTER_BUFFER_UPDATE:
2333 case LFUN_MASTER_BUFFER_VIEW:
2335 && (doc_buffer->parent() != nullptr
2336 || doc_buffer->hasChildren())
2337 && !d.processing_thread_watcher_.isRunning();
2340 case LFUN_BUFFER_UPDATE:
2341 case LFUN_BUFFER_VIEW: {
2342 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2346 string format = to_utf8(cmd.argument());
2347 if (cmd.argument().empty())
2348 format = doc_buffer->params().getDefaultOutputFormat();
2349 enable = doc_buffer->params().isExportable(format, true);
2353 case LFUN_BUFFER_RELOAD:
2354 enable = doc_buffer && !doc_buffer->isUnnamed()
2355 && doc_buffer->fileName().exists()
2356 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2359 case LFUN_BUFFER_RESET_EXPORT:
2360 enable = doc_buffer != nullptr;
2363 case LFUN_BUFFER_CHILD_OPEN:
2364 enable = doc_buffer != nullptr;
2367 case LFUN_MASTER_BUFFER_FORALL: {
2368 if (doc_buffer == nullptr) {
2369 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2373 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2374 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2375 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2380 for (Buffer * buf : doc_buffer->allRelatives()) {
2381 GuiWorkArea * wa = workArea(*buf);
2384 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2385 enable = flag.enabled();
2392 case LFUN_BUFFER_WRITE:
2393 enable = doc_buffer && (doc_buffer->isUnnamed()
2394 || (!doc_buffer->isClean()
2395 || cmd.argument() == "force"));
2398 //FIXME: This LFUN should be moved to GuiApplication.
2399 case LFUN_BUFFER_WRITE_ALL: {
2400 // We enable the command only if there are some modified buffers
2401 Buffer * first = theBufferList().first();
2406 // We cannot use a for loop as the buffer list is a cycle.
2408 if (!b->isClean()) {
2412 b = theBufferList().next(b);
2413 } while (b != first);
2417 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2418 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2421 case LFUN_BUFFER_EXPORT: {
2422 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2426 return doc_buffer->getStatus(cmd, flag);
2429 case LFUN_BUFFER_EXPORT_AS:
2430 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2435 case LFUN_BUFFER_WRITE_AS:
2436 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2437 enable = doc_buffer != nullptr;
2440 case LFUN_EXPORT_CANCEL:
2441 enable = d.processing_thread_watcher_.isRunning();
2444 case LFUN_BUFFER_CLOSE:
2445 case LFUN_VIEW_CLOSE:
2446 enable = doc_buffer != nullptr;
2449 case LFUN_BUFFER_CLOSE_ALL:
2450 enable = theBufferList().last() != theBufferList().first();
2453 case LFUN_BUFFER_CHKTEX: {
2454 // hide if we have no checktex command
2455 if (lyxrc.chktex_command.empty()) {
2456 flag.setUnknown(true);
2460 if (!doc_buffer || !doc_buffer->params().isLatex()
2461 || d.processing_thread_watcher_.isRunning()) {
2462 // grey out, don't hide
2470 case LFUN_VIEW_SPLIT:
2471 if (cmd.getArg(0) == "vertical")
2472 enable = doc_buffer && (d.splitter_->count() == 1 ||
2473 d.splitter_->orientation() == Qt::Vertical);
2475 enable = doc_buffer && (d.splitter_->count() == 1 ||
2476 d.splitter_->orientation() == Qt::Horizontal);
2479 case LFUN_TAB_GROUP_CLOSE:
2480 enable = d.tabWorkAreaCount() > 1;
2483 case LFUN_DEVEL_MODE_TOGGLE:
2484 flag.setOnOff(devel_mode_);
2487 case LFUN_TOOLBAR_SET: {
2488 string const name = cmd.getArg(0);
2489 string const state = cmd.getArg(1);
2490 if (name.empty() || state.empty()) {
2492 docstring const msg =
2493 _("Function toolbar-set requires two arguments!");
2497 if (state != "on" && state != "off" && state != "auto") {
2499 docstring const msg =
2500 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2505 if (GuiToolbar * t = toolbar(name)) {
2506 bool const autovis = t->visibility() & Toolbars::AUTO;
2508 flag.setOnOff(t->isVisible() && !autovis);
2509 else if (state == "off")
2510 flag.setOnOff(!t->isVisible() && !autovis);
2511 else if (state == "auto")
2512 flag.setOnOff(autovis);
2515 docstring const msg =
2516 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2522 case LFUN_TOOLBAR_TOGGLE: {
2523 string const name = cmd.getArg(0);
2524 if (GuiToolbar * t = toolbar(name))
2525 flag.setOnOff(t->isVisible());
2528 docstring const msg =
2529 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2535 case LFUN_TOOLBAR_MOVABLE: {
2536 string const name = cmd.getArg(0);
2537 // use negation since locked == !movable
2539 // toolbar name * locks all toolbars
2540 flag.setOnOff(!toolbarsMovable_);
2541 else if (GuiToolbar * t = toolbar(name))
2542 flag.setOnOff(!(t->isMovable()));
2545 docstring const msg =
2546 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2552 case LFUN_ICON_SIZE:
2553 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2556 case LFUN_DROP_LAYOUTS_CHOICE:
2557 enable = buf != nullptr;
2560 case LFUN_UI_TOGGLE:
2561 if (cmd.argument() == "zoomlevel") {
2562 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2563 } else if (cmd.argument() == "zoomslider") {
2564 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2565 } else if (cmd.argument() == "statistics-w") {
2566 flag.setOnOff(word_count_enabled_);
2567 } else if (cmd.argument() == "statistics-cb") {
2568 flag.setOnOff(char_count_enabled_);
2569 } else if (cmd.argument() == "statistics-c") {
2570 flag.setOnOff(char_nb_count_enabled_);
2572 flag.setOnOff(isFullScreen());
2575 case LFUN_DIALOG_DISCONNECT_INSET:
2578 case LFUN_DIALOG_HIDE:
2579 // FIXME: should we check if the dialog is shown?
2582 case LFUN_DIALOG_TOGGLE:
2583 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2586 case LFUN_DIALOG_SHOW: {
2587 string const name = cmd.getArg(0);
2589 enable = name == "aboutlyx"
2590 || name == "file" //FIXME: should be removed.
2591 || name == "lyxfiles"
2593 || name == "texinfo"
2594 || name == "progress"
2595 || name == "compare";
2596 else if (name == "character" || name == "symbols"
2597 || name == "mathdelimiter" || name == "mathmatrix") {
2598 if (!buf || buf->isReadonly())
2601 Cursor const & cur = currentBufferView()->cursor();
2602 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2605 else if (name == "latexlog")
2606 enable = FileName(doc_buffer->logName()).isReadableFile();
2607 else if (name == "spellchecker")
2608 enable = theSpellChecker()
2609 && !doc_buffer->text().empty();
2610 else if (name == "vclog")
2611 enable = doc_buffer->lyxvc().inUse();
2615 case LFUN_DIALOG_UPDATE: {
2616 string const name = cmd.getArg(0);
2618 enable = name == "prefs";
2622 case LFUN_COMMAND_EXECUTE:
2624 case LFUN_MENU_OPEN:
2625 // Nothing to check.
2628 case LFUN_COMPLETION_INLINE:
2629 if (!d.current_work_area_
2630 || !d.current_work_area_->completer().inlinePossible(
2631 currentBufferView()->cursor()))
2635 case LFUN_COMPLETION_POPUP:
2636 if (!d.current_work_area_
2637 || !d.current_work_area_->completer().popupPossible(
2638 currentBufferView()->cursor()))
2643 if (!d.current_work_area_
2644 || !d.current_work_area_->completer().inlinePossible(
2645 currentBufferView()->cursor()))
2649 case LFUN_COMPLETION_ACCEPT:
2650 if (!d.current_work_area_
2651 || (!d.current_work_area_->completer().popupVisible()
2652 && !d.current_work_area_->completer().inlineVisible()
2653 && !d.current_work_area_->completer().completionAvailable()))
2657 case LFUN_COMPLETION_CANCEL:
2658 if (!d.current_work_area_
2659 || (!d.current_work_area_->completer().popupVisible()
2660 && !d.current_work_area_->completer().inlineVisible()))
2664 case LFUN_BUFFER_ZOOM_OUT:
2665 case LFUN_BUFFER_ZOOM_IN:
2666 case LFUN_BUFFER_ZOOM: {
2667 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2668 if (zoom < zoom_min_) {
2669 docstring const msg =
2670 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2673 } else if (zoom > zoom_max_) {
2674 docstring const msg =
2675 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2679 enable = doc_buffer;
2684 case LFUN_BUFFER_MOVE_NEXT:
2685 case LFUN_BUFFER_MOVE_PREVIOUS:
2686 // we do not cycle when moving
2687 case LFUN_BUFFER_NEXT:
2688 case LFUN_BUFFER_PREVIOUS:
2689 // because we cycle, it doesn't matter whether on first or last
2690 enable = (d.currentTabWorkArea()->count() > 1);
2692 case LFUN_BUFFER_SWITCH:
2693 // toggle on the current buffer, but do not toggle off
2694 // the other ones (is that a good idea?)
2696 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2697 flag.setOnOff(true);
2700 case LFUN_VC_REGISTER:
2701 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2703 case LFUN_VC_RENAME:
2704 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2707 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2709 case LFUN_VC_CHECK_IN:
2710 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2712 case LFUN_VC_CHECK_OUT:
2713 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2715 case LFUN_VC_LOCKING_TOGGLE:
2716 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2717 && doc_buffer->lyxvc().lockingToggleEnabled();
2718 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2720 case LFUN_VC_REVERT:
2721 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2722 && !doc_buffer->hasReadonlyFlag();
2724 case LFUN_VC_UNDO_LAST:
2725 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2727 case LFUN_VC_REPO_UPDATE:
2728 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2730 case LFUN_VC_COMMAND: {
2731 if (cmd.argument().empty())
2733 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2737 case LFUN_VC_COMPARE:
2738 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2741 case LFUN_SERVER_GOTO_FILE_ROW:
2742 case LFUN_LYX_ACTIVATE:
2743 case LFUN_WINDOW_RAISE:
2745 case LFUN_FORWARD_SEARCH:
2746 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2747 doc_buffer && doc_buffer->isSyncTeXenabled();
2750 case LFUN_FILE_INSERT_PLAINTEXT:
2751 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2752 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2755 case LFUN_SPELLING_CONTINUOUSLY:
2756 flag.setOnOff(lyxrc.spellcheck_continuously);
2759 case LFUN_CITATION_OPEN:
2768 flag.setEnabled(false);
2774 static FileName selectTemplateFile()
2776 FileDialog dlg(qt_("Select template file"));
2777 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2778 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2780 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2781 QStringList(qt_("LyX Documents (*.lyx)")));
2783 if (result.first == FileDialog::Later)
2785 if (result.second.isEmpty())
2787 return FileName(fromqstr(result.second));
2791 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2795 Buffer * newBuffer = nullptr;
2797 newBuffer = checkAndLoadLyXFile(filename);
2798 } catch (ExceptionMessage const &) {
2805 message(_("Document not loaded."));
2809 setBuffer(newBuffer);
2810 newBuffer->errors("Parse");
2813 theSession().lastFiles().add(filename);
2814 theSession().writeFile();
2821 void GuiView::openDocument(string const & fname)
2823 string initpath = lyxrc.document_path;
2825 if (documentBufferView()) {
2826 string const trypath = documentBufferView()->buffer().filePath();
2827 // If directory is writeable, use this as default.
2828 if (FileName(trypath).isDirWritable())
2834 if (fname.empty()) {
2835 FileDialog dlg(qt_("Select document to open"));
2836 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2837 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2839 QStringList const filter({
2840 qt_("LyX Documents (*.lyx)"),
2841 qt_("LyX Document Backups (*.lyx~)"),
2842 qt_("All Files (*.*)")
2844 FileDialog::Result result =
2845 dlg.open(toqstr(initpath), filter);
2847 if (result.first == FileDialog::Later)
2850 filename = fromqstr(result.second);
2852 // check selected filename
2853 if (filename.empty()) {
2854 message(_("Canceled."));
2860 // get absolute path of file and add ".lyx" to the filename if
2862 FileName const fullname =
2863 fileSearch(string(), filename, "lyx", support::may_not_exist);
2864 if (!fullname.empty())
2865 filename = fullname.absFileName();
2867 if (!fullname.onlyPath().isDirectory()) {
2868 Alert::warning(_("Invalid filename"),
2869 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2870 from_utf8(fullname.absFileName())));
2874 // if the file doesn't exist and isn't already open (bug 6645),
2875 // let the user create one
2876 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2877 !LyXVC::file_not_found_hook(fullname)) {
2878 // the user specifically chose this name. Believe him.
2879 Buffer * const b = newFile(filename, string(), true);
2885 docstring const disp_fn = makeDisplayPath(filename);
2886 message(bformat(_("Opening document %1$s..."), disp_fn));
2889 Buffer * buf = loadDocument(fullname);
2891 str2 = bformat(_("Document %1$s opened."), disp_fn);
2892 if (buf->lyxvc().inUse())
2893 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2894 " " + _("Version control detected.");
2896 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2901 // FIXME: clean that
2902 static bool import(GuiView * lv, FileName const & filename,
2903 string const & format, ErrorList & errorList)
2905 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2907 string loader_format;
2908 vector<string> loaders = theConverters().loaders();
2909 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2910 for (string const & loader : loaders) {
2911 if (!theConverters().isReachable(format, loader))
2914 string const tofile =
2915 support::changeExtension(filename.absFileName(),
2916 theFormats().extension(loader));
2917 if (theConverters().convert(nullptr, filename, FileName(tofile),
2918 filename, format, loader, errorList) != Converters::SUCCESS)
2920 loader_format = loader;
2923 if (loader_format.empty()) {
2924 frontend::Alert::error(_("Couldn't import file"),
2925 bformat(_("No information for importing the format %1$s."),
2926 translateIfPossible(theFormats().prettyName(format))));
2930 loader_format = format;
2932 if (loader_format == "lyx") {
2933 Buffer * buf = lv->loadDocument(lyxfile);
2937 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2941 bool as_paragraphs = loader_format == "textparagraph";
2942 string filename2 = (loader_format == format) ? filename.absFileName()
2943 : support::changeExtension(filename.absFileName(),
2944 theFormats().extension(loader_format));
2945 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2947 guiApp->setCurrentView(lv);
2948 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2955 void GuiView::importDocument(string const & argument)
2958 string filename = split(argument, format, ' ');
2960 LYXERR(Debug::INFO, format << " file: " << filename);
2962 // need user interaction
2963 if (filename.empty()) {
2964 string initpath = lyxrc.document_path;
2965 if (documentBufferView()) {
2966 string const trypath = documentBufferView()->buffer().filePath();
2967 // If directory is writeable, use this as default.
2968 if (FileName(trypath).isDirWritable())
2972 docstring const text = bformat(_("Select %1$s file to import"),
2973 translateIfPossible(theFormats().prettyName(format)));
2975 FileDialog dlg(toqstr(text));
2976 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2977 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2979 docstring filter = translateIfPossible(theFormats().prettyName(format));
2982 filter += from_utf8(theFormats().extensions(format));
2985 FileDialog::Result result =
2986 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2988 if (result.first == FileDialog::Later)
2991 filename = fromqstr(result.second);
2993 // check selected filename
2994 if (filename.empty())
2995 message(_("Canceled."));
2998 if (filename.empty())
3001 // get absolute path of file
3002 FileName const fullname(support::makeAbsPath(filename));
3004 // Can happen if the user entered a path into the dialog
3006 if (fullname.onlyFileName().empty()) {
3007 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3008 "Aborting import."),
3009 from_utf8(fullname.absFileName()));
3010 frontend::Alert::error(_("File name error"), msg);
3011 message(_("Canceled."));
3016 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3018 // Check if the document already is open
3019 Buffer * buf = theBufferList().getBuffer(lyxfile);
3022 if (!closeBuffer()) {
3023 message(_("Canceled."));
3028 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3030 // if the file exists already, and we didn't do
3031 // -i lyx thefile.lyx, warn
3032 if (lyxfile.exists() && fullname != lyxfile) {
3034 docstring text = bformat(_("The document %1$s already exists.\n\n"
3035 "Do you want to overwrite that document?"), displaypath);
3036 int const ret = Alert::prompt(_("Overwrite document?"),
3037 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3040 message(_("Canceled."));
3045 message(bformat(_("Importing %1$s..."), displaypath));
3046 ErrorList errorList;
3047 if (import(this, fullname, format, errorList))
3048 message(_("imported."));
3050 message(_("file not imported!"));
3052 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3056 void GuiView::newDocument(string const & filename, string templatefile,
3059 FileName initpath(lyxrc.document_path);
3060 if (documentBufferView()) {
3061 FileName const trypath(documentBufferView()->buffer().filePath());
3062 // If directory is writeable, use this as default.
3063 if (trypath.isDirWritable())
3067 if (from_template) {
3068 if (templatefile.empty())
3069 templatefile = selectTemplateFile().absFileName();
3070 if (templatefile.empty())
3075 if (filename.empty())
3076 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3078 b = newFile(filename, templatefile, true);
3083 // If no new document could be created, it is unsure
3084 // whether there is a valid BufferView.
3085 if (currentBufferView())
3086 // Ensure the cursor is correctly positioned on screen.
3087 currentBufferView()->showCursor();
3091 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3093 BufferView * bv = documentBufferView();
3098 FileName filename(to_utf8(fname));
3099 if (filename.empty()) {
3100 // Launch a file browser
3102 string initpath = lyxrc.document_path;
3103 string const trypath = bv->buffer().filePath();
3104 // If directory is writeable, use this as default.
3105 if (FileName(trypath).isDirWritable())
3109 FileDialog dlg(qt_("Select LyX document to insert"));
3110 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3111 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3113 FileDialog::Result result = dlg.open(toqstr(initpath),
3114 QStringList(qt_("LyX Documents (*.lyx)")));
3116 if (result.first == FileDialog::Later)
3120 filename.set(fromqstr(result.second));
3122 // check selected filename
3123 if (filename.empty()) {
3124 // emit message signal.
3125 message(_("Canceled."));
3130 bv->insertLyXFile(filename, ignorelang);
3131 bv->buffer().errors("Parse");
3136 string const GuiView::getTemplatesPath(Buffer & b)
3138 // We start off with the user's templates path
3139 string result = addPath(package().user_support().absFileName(), "templates");
3140 // Check for the document language
3141 string const langcode = b.params().language->code();
3142 string const shortcode = langcode.substr(0, 2);
3143 if (!langcode.empty() && shortcode != "en") {
3144 string subpath = addPath(result, shortcode);
3145 string subpath_long = addPath(result, langcode);
3146 // If we have a subdirectory for the language already,
3148 FileName sp = FileName(subpath);
3149 if (sp.isDirectory())
3151 else if (FileName(subpath_long).isDirectory())
3152 result = subpath_long;
3154 // Ask whether we should create such a subdirectory
3155 docstring const text =
3156 bformat(_("It is suggested to save the template in a subdirectory\n"
3157 "appropriate to the document language (%1$s).\n"
3158 "This subdirectory does not exists yet.\n"
3159 "Do you want to create it?"),
3160 _(b.params().language->display()));
3161 if (Alert::prompt(_("Create Language Directory?"),
3162 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3163 // If the user agreed, we try to create it and report if this failed.
3164 if (!sp.createDirectory(0777))
3165 Alert::error(_("Subdirectory creation failed!"),
3166 _("Could not create subdirectory.\n"
3167 "The template will be saved in the parent directory."));
3173 // Do we have a layout category?
3174 string const cat = b.params().baseClass() ?
3175 b.params().baseClass()->category()
3178 string subpath = addPath(result, cat);
3179 // If we have a subdirectory for the category already,
3181 FileName sp = FileName(subpath);
3182 if (sp.isDirectory())
3185 // Ask whether we should create such a subdirectory
3186 docstring const text =
3187 bformat(_("It is suggested to save the template in a subdirectory\n"
3188 "appropriate to the layout category (%1$s).\n"
3189 "This subdirectory does not exists yet.\n"
3190 "Do you want to create it?"),
3192 if (Alert::prompt(_("Create Category Directory?"),
3193 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3194 // If the user agreed, we try to create it and report if this failed.
3195 if (!sp.createDirectory(0777))
3196 Alert::error(_("Subdirectory creation failed!"),
3197 _("Could not create subdirectory.\n"
3198 "The template will be saved in the parent directory."));
3208 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3210 FileName fname = b.fileName();
3211 FileName const oldname = fname;
3212 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3214 if (!newname.empty()) {
3217 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3219 fname = support::makeAbsPath(to_utf8(newname),
3220 oldname.onlyPath().absFileName());
3222 // Switch to this Buffer.
3225 // No argument? Ask user through dialog.
3227 QString const title = as_template ? qt_("Choose a filename to save template as")
3228 : qt_("Choose a filename to save document as");
3229 FileDialog dlg(title);
3230 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3231 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3233 fname.ensureExtension(".lyx");
3235 string const path = as_template ?
3237 : fname.onlyPath().absFileName();
3238 FileDialog::Result result =
3239 dlg.save(toqstr(path),
3240 QStringList(qt_("LyX Documents (*.lyx)")),
3241 toqstr(fname.onlyFileName()));
3243 if (result.first == FileDialog::Later)
3246 fname.set(fromqstr(result.second));
3251 fname.ensureExtension(".lyx");
3254 // fname is now the new Buffer location.
3256 // if there is already a Buffer open with this name, we do not want
3257 // to have another one. (the second test makes sure we're not just
3258 // trying to overwrite ourselves, which is fine.)
3259 if (theBufferList().exists(fname) && fname != oldname
3260 && theBufferList().getBuffer(fname) != &b) {
3261 docstring const text =
3262 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3263 "Please close it before attempting to overwrite it.\n"
3264 "Do you want to choose a new filename?"),
3265 from_utf8(fname.absFileName()));
3266 int const ret = Alert::prompt(_("Chosen File Already Open"),
3267 text, 0, 1, _("&Rename"), _("&Cancel"));
3269 case 0: return renameBuffer(b, docstring(), kind);
3270 case 1: return false;
3275 bool const existsLocal = fname.exists();
3276 bool const existsInVC = LyXVC::fileInVC(fname);
3277 if (existsLocal || existsInVC) {
3278 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3279 if (kind != LV_WRITE_AS && existsInVC) {
3280 // renaming to a name that is already in VC
3282 docstring text = bformat(_("The document %1$s "
3283 "is already registered.\n\n"
3284 "Do you want to choose a new name?"),
3286 docstring const title = (kind == LV_VC_RENAME) ?
3287 _("Rename document?") : _("Copy document?");
3288 docstring const button = (kind == LV_VC_RENAME) ?
3289 _("&Rename") : _("&Copy");
3290 int const ret = Alert::prompt(title, text, 0, 1,
3291 button, _("&Cancel"));
3293 case 0: return renameBuffer(b, docstring(), kind);
3294 case 1: return false;
3299 docstring text = bformat(_("The document %1$s "
3300 "already exists.\n\n"
3301 "Do you want to overwrite that document?"),
3303 int const ret = Alert::prompt(_("Overwrite document?"),
3304 text, 0, 2, _("&Overwrite"),
3305 _("&Rename"), _("&Cancel"));
3308 case 1: return renameBuffer(b, docstring(), kind);
3309 case 2: return false;
3315 case LV_VC_RENAME: {
3316 string msg = b.lyxvc().rename(fname);
3319 message(from_utf8(msg));
3323 string msg = b.lyxvc().copy(fname);
3326 message(from_utf8(msg));
3330 case LV_WRITE_AS_TEMPLATE:
3333 // LyXVC created the file already in case of LV_VC_RENAME or
3334 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3335 // relative paths of included stuff right if we moved e.g. from
3336 // /a/b.lyx to /a/c/b.lyx.
3338 bool const saved = saveBuffer(b, fname);
3345 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3347 FileName fname = b.fileName();
3349 FileDialog dlg(qt_("Choose a filename to export the document as"));
3350 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3353 QString const anyformat = qt_("Guess from extension (*.*)");
3356 vector<Format const *> export_formats;
3357 for (Format const & f : theFormats())
3358 if (f.documentFormat())
3359 export_formats.push_back(&f);
3360 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3361 map<QString, string> fmap;
3364 for (Format const * f : export_formats) {
3365 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3366 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3368 from_ascii(f->extension())));
3369 types << loc_filter;
3370 fmap[loc_filter] = f->name();
3371 if (from_ascii(f->name()) == iformat) {
3372 filter = loc_filter;
3373 ext = f->extension();
3376 string ofname = fname.onlyFileName();
3378 ofname = support::changeExtension(ofname, ext);
3379 FileDialog::Result result =
3380 dlg.save(toqstr(fname.onlyPath().absFileName()),
3384 if (result.first != FileDialog::Chosen)
3388 fname.set(fromqstr(result.second));
3389 if (filter == anyformat)
3390 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3392 fmt_name = fmap[filter];
3393 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3394 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3396 if (fmt_name.empty() || fname.empty())
3399 fname.ensureExtension(theFormats().extension(fmt_name));
3401 // fname is now the new Buffer location.
3402 if (fname.exists()) {
3403 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3404 docstring text = bformat(_("The document %1$s already "
3405 "exists.\n\nDo you want to "
3406 "overwrite that document?"),
3408 int const ret = Alert::prompt(_("Overwrite document?"),
3409 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3412 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3413 case 2: return false;
3417 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3420 return dr.dispatched();
3424 bool GuiView::saveBuffer(Buffer & b)
3426 return saveBuffer(b, FileName());
3430 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3432 if (workArea(b) && workArea(b)->inDialogMode())
3435 if (fn.empty() && b.isUnnamed())
3436 return renameBuffer(b, docstring());
3438 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3440 theSession().lastFiles().add(b.fileName());
3441 theSession().writeFile();
3445 // Switch to this Buffer.
3448 // FIXME: we don't tell the user *WHY* the save failed !!
3449 docstring const file = makeDisplayPath(b.absFileName(), 30);
3450 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3451 "Do you want to rename the document and "
3452 "try again?"), file);
3453 int const ret = Alert::prompt(_("Rename and save?"),
3454 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3457 if (!renameBuffer(b, docstring()))
3466 return saveBuffer(b, fn);
3470 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3472 return closeWorkArea(wa, false);
3476 // We only want to close the buffer if it is not visible in other workareas
3477 // of the same view, nor in other views, and if this is not a child
3478 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3480 Buffer & buf = wa->bufferView().buffer();
3482 bool last_wa = d.countWorkAreasOf(buf) == 1
3483 && !inOtherView(buf) && !buf.parent();
3485 bool close_buffer = last_wa;
3488 if (lyxrc.close_buffer_with_last_view == "yes")
3490 else if (lyxrc.close_buffer_with_last_view == "no")
3491 close_buffer = false;
3494 if (buf.isUnnamed())
3495 file = from_utf8(buf.fileName().onlyFileName());
3497 file = buf.fileName().displayName(30);
3498 docstring const text = bformat(
3499 _("Last view on document %1$s is being closed.\n"
3500 "Would you like to close or hide the document?\n"
3502 "Hidden documents can be displayed back through\n"
3503 "the menu: View->Hidden->...\n"
3505 "To remove this question, set your preference in:\n"
3506 " Tools->Preferences->Look&Feel->UserInterface\n"
3508 int ret = Alert::prompt(_("Close or hide document?"),
3509 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3512 close_buffer = (ret == 0);
3516 return closeWorkArea(wa, close_buffer);
3520 bool GuiView::closeBuffer()
3522 GuiWorkArea * wa = currentMainWorkArea();
3523 // coverity complained about this
3524 // it seems unnecessary, but perhaps is worth the check
3525 LASSERT(wa, return false);
3527 setCurrentWorkArea(wa);
3528 Buffer & buf = wa->bufferView().buffer();
3529 return closeWorkArea(wa, !buf.parent());
3533 void GuiView::writeSession() const {
3534 GuiWorkArea const * active_wa = currentMainWorkArea();
3535 for (int i = 0; i < d.splitter_->count(); ++i) {
3536 TabWorkArea * twa = d.tabWorkArea(i);
3537 for (int j = 0; j < twa->count(); ++j) {
3538 GuiWorkArea * wa = twa->workArea(j);
3539 Buffer & buf = wa->bufferView().buffer();
3540 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3546 bool GuiView::closeBufferAll()
3549 for (auto & buf : theBufferList()) {
3550 if (!saveBufferIfNeeded(*buf, false)) {
3551 // Closing has been cancelled, so abort.
3556 // Close the workareas in all other views
3557 QList<int> const ids = guiApp->viewIds();
3558 for (int i = 0; i != ids.size(); ++i) {
3559 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3563 // Close our own workareas
3564 if (!closeWorkAreaAll())
3571 bool GuiView::closeWorkAreaAll()
3573 setCurrentWorkArea(currentMainWorkArea());
3575 // We might be in a situation that there is still a tabWorkArea, but
3576 // there are no tabs anymore. This can happen when we get here after a
3577 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3578 // many TabWorkArea's have no documents anymore.
3581 // We have to call count() each time, because it can happen that
3582 // more than one splitter will disappear in one iteration (bug 5998).
3583 while (d.splitter_->count() > empty_twa) {
3584 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3586 if (twa->count() == 0)
3589 setCurrentWorkArea(twa->currentWorkArea());
3590 if (!closeTabWorkArea(twa))
3598 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3603 Buffer & buf = wa->bufferView().buffer();
3605 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3606 Alert::warning(_("Close document"),
3607 _("Document could not be closed because it is being processed by LyX."));
3612 return closeBuffer(buf);
3614 if (!inMultiTabs(wa))
3615 if (!saveBufferIfNeeded(buf, true))
3623 bool GuiView::closeBuffer(Buffer & buf)
3625 bool success = true;
3626 for (Buffer * child_buf : buf.getChildren()) {
3627 if (theBufferList().isOthersChild(&buf, child_buf)) {
3628 child_buf->setParent(nullptr);
3632 // FIXME: should we look in other tabworkareas?
3633 // ANSWER: I don't think so. I've tested, and if the child is
3634 // open in some other window, it closes without a problem.
3635 GuiWorkArea * child_wa = workArea(*child_buf);
3638 // If we are in a close_event all children will be closed in some time,
3639 // so no need to do it here. This will ensure that the children end up
3640 // in the session file in the correct order. If we close the master
3641 // buffer, we can close or release the child buffers here too.
3643 success = closeWorkArea(child_wa, true);
3647 // In this case the child buffer is open but hidden.
3648 // Even in this case, children can be dirty (e.g.,
3649 // after a label change in the master, see #11405).
3650 // Therefore, check this
3651 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3652 // If we are in a close_event all children will be closed in some time,
3653 // so no need to do it here. This will ensure that the children end up
3654 // in the session file in the correct order. If we close the master
3655 // buffer, we can close or release the child buffers here too.
3658 // Save dirty buffers also if closing_!
3659 if (saveBufferIfNeeded(*child_buf, false)) {
3660 child_buf->removeAutosaveFile();
3661 theBufferList().release(child_buf);
3663 // Saving of dirty children has been cancelled.
3664 // Cancel the whole process.
3671 // goto bookmark to update bookmark pit.
3672 // FIXME: we should update only the bookmarks related to this buffer!
3673 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3674 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3675 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3676 guiApp->gotoBookmark(i, false, false);
3678 if (saveBufferIfNeeded(buf, false)) {
3679 buf.removeAutosaveFile();
3680 theBufferList().release(&buf);
3684 // open all children again to avoid a crash because of dangling
3685 // pointers (bug 6603)
3691 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3693 while (twa == d.currentTabWorkArea()) {
3694 twa->setCurrentIndex(twa->count() - 1);
3696 GuiWorkArea * wa = twa->currentWorkArea();
3697 Buffer & b = wa->bufferView().buffer();
3699 // We only want to close the buffer if the same buffer is not visible
3700 // in another view, and if this is not a child and if we are closing
3701 // a view (not a tabgroup).
3702 bool const close_buffer =
3703 !inOtherView(b) && !b.parent() && closing_;
3705 if (!closeWorkArea(wa, close_buffer))
3712 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3714 if (buf.isClean() || buf.paragraphs().empty())
3717 // Switch to this Buffer.
3723 if (buf.isUnnamed()) {
3724 file = from_utf8(buf.fileName().onlyFileName());
3727 FileName filename = buf.fileName();
3729 file = filename.displayName(30);
3730 exists = filename.exists();
3733 // Bring this window to top before asking questions.
3738 if (hiding && buf.isUnnamed()) {
3739 docstring const text = bformat(_("The document %1$s has not been "
3740 "saved yet.\n\nDo you want to save "
3741 "the document?"), file);
3742 ret = Alert::prompt(_("Save new document?"),
3743 text, 0, 1, _("&Save"), _("&Cancel"));
3747 docstring const text = exists ?
3748 bformat(_("The document %1$s has unsaved changes."
3749 "\n\nDo you want to save the document or "
3750 "discard the changes?"), file) :
3751 bformat(_("The document %1$s has not been saved yet."
3752 "\n\nDo you want to save the document or "
3753 "discard it entirely?"), file);
3754 docstring const title = exists ?
3755 _("Save changed document?") : _("Save document?");
3756 ret = Alert::prompt(title, text, 0, 2,
3757 _("&Save"), _("&Discard"), _("&Cancel"));
3762 if (!saveBuffer(buf))
3766 // If we crash after this we could have no autosave file
3767 // but I guess this is really improbable (Jug).
3768 // Sometimes improbable things happen:
3769 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3770 // buf.removeAutosaveFile();
3772 // revert all changes
3783 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3785 Buffer & buf = wa->bufferView().buffer();
3787 for (int i = 0; i != d.splitter_->count(); ++i) {
3788 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3789 if (wa_ && wa_ != wa)
3792 return inOtherView(buf);
3796 bool GuiView::inOtherView(Buffer & buf)
3798 QList<int> const ids = guiApp->viewIds();
3800 for (int i = 0; i != ids.size(); ++i) {
3804 if (guiApp->view(ids[i]).workArea(buf))
3811 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3813 if (!documentBufferView())
3816 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3817 Buffer * const curbuf = &documentBufferView()->buffer();
3818 int nwa = twa->count();
3819 for (int i = 0; i < nwa; ++i) {
3820 if (&workArea(i)->bufferView().buffer() == curbuf) {
3822 if (np == NEXTBUFFER)
3823 next_index = (i == nwa - 1 ? 0 : i + 1);
3825 next_index = (i == 0 ? nwa - 1 : i - 1);
3827 twa->moveTab(i, next_index);
3829 setBuffer(&workArea(next_index)->bufferView().buffer());
3837 /// make sure the document is saved
3838 static bool ensureBufferClean(Buffer * buffer)
3840 LASSERT(buffer, return false);
3841 if (buffer->isClean() && !buffer->isUnnamed())
3844 docstring const file = buffer->fileName().displayName(30);
3847 if (!buffer->isUnnamed()) {
3848 text = bformat(_("The document %1$s has unsaved "
3849 "changes.\n\nDo you want to save "
3850 "the document?"), file);
3851 title = _("Save changed document?");
3854 text = bformat(_("The document %1$s has not been "
3855 "saved yet.\n\nDo you want to save "
3856 "the document?"), file);
3857 title = _("Save new document?");
3859 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3862 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3864 return buffer->isClean() && !buffer->isUnnamed();
3868 bool GuiView::reloadBuffer(Buffer & buf)
3870 currentBufferView()->cursor().reset();
3871 Buffer::ReadStatus status = buf.reload();
3872 return status == Buffer::ReadSuccess;
3876 void GuiView::checkExternallyModifiedBuffers()
3878 for (Buffer * buf : theBufferList()) {
3879 if (buf->fileName().exists() && buf->isChecksumModified()) {
3880 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3881 " Reload now? Any local changes will be lost."),
3882 from_utf8(buf->absFileName()));
3883 int const ret = Alert::prompt(_("Reload externally changed document?"),
3884 text, 0, 1, _("&Reload"), _("&Cancel"));
3892 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3894 Buffer * buffer = documentBufferView()
3895 ? &(documentBufferView()->buffer()) : nullptr;
3897 switch (cmd.action()) {
3898 case LFUN_VC_REGISTER:
3899 if (!buffer || !ensureBufferClean(buffer))
3901 if (!buffer->lyxvc().inUse()) {
3902 if (buffer->lyxvc().registrer()) {
3903 reloadBuffer(*buffer);
3904 dr.clearMessageUpdate();
3909 case LFUN_VC_RENAME:
3910 case LFUN_VC_COPY: {
3911 if (!buffer || !ensureBufferClean(buffer))
3913 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3914 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3915 // Some changes are not yet committed.
3916 // We test here and not in getStatus(), since
3917 // this test is expensive.
3919 LyXVC::CommandResult ret =
3920 buffer->lyxvc().checkIn(log);
3922 if (ret == LyXVC::ErrorCommand ||
3923 ret == LyXVC::VCSuccess)
3924 reloadBuffer(*buffer);
3925 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3926 frontend::Alert::error(
3927 _("Revision control error."),
3928 _("Document could not be checked in."));
3932 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3933 LV_VC_RENAME : LV_VC_COPY;
3934 renameBuffer(*buffer, cmd.argument(), kind);
3939 case LFUN_VC_CHECK_IN:
3940 if (!buffer || !ensureBufferClean(buffer))
3942 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3944 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3946 // Only skip reloading if the checkin was cancelled or
3947 // an error occurred before the real checkin VCS command
3948 // was executed, since the VCS might have changed the
3949 // file even if it could not checkin successfully.
3950 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3951 reloadBuffer(*buffer);
3955 case LFUN_VC_CHECK_OUT:
3956 if (!buffer || !ensureBufferClean(buffer))
3958 if (buffer->lyxvc().inUse()) {
3959 dr.setMessage(buffer->lyxvc().checkOut());
3960 reloadBuffer(*buffer);
3964 case LFUN_VC_LOCKING_TOGGLE:
3965 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3967 if (buffer->lyxvc().inUse()) {
3968 string res = buffer->lyxvc().lockingToggle();
3970 frontend::Alert::error(_("Revision control error."),
3971 _("Error when setting the locking property."));
3974 reloadBuffer(*buffer);
3979 case LFUN_VC_REVERT:
3982 if (buffer->lyxvc().revert()) {
3983 reloadBuffer(*buffer);
3984 dr.clearMessageUpdate();
3988 case LFUN_VC_UNDO_LAST:
3991 buffer->lyxvc().undoLast();
3992 reloadBuffer(*buffer);
3993 dr.clearMessageUpdate();
3996 case LFUN_VC_REPO_UPDATE:
3999 if (ensureBufferClean(buffer)) {
4000 dr.setMessage(buffer->lyxvc().repoUpdate());
4001 checkExternallyModifiedBuffers();
4005 case LFUN_VC_COMMAND: {
4006 string flag = cmd.getArg(0);
4007 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4010 if (contains(flag, 'M')) {
4011 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4014 string path = cmd.getArg(1);
4015 if (contains(path, "$$p") && buffer)
4016 path = subst(path, "$$p", buffer->filePath());
4017 LYXERR(Debug::LYXVC, "Directory: " << path);
4019 if (!pp.isReadableDirectory()) {
4020 lyxerr << _("Directory is not accessible.") << endl;
4023 support::PathChanger p(pp);
4025 string command = cmd.getArg(2);
4026 if (command.empty())
4029 command = subst(command, "$$i", buffer->absFileName());
4030 command = subst(command, "$$p", buffer->filePath());
4032 command = subst(command, "$$m", to_utf8(message));
4033 LYXERR(Debug::LYXVC, "Command: " << command);
4035 one.startscript(Systemcall::Wait, command);
4039 if (contains(flag, 'I'))
4040 buffer->markDirty();
4041 if (contains(flag, 'R'))
4042 reloadBuffer(*buffer);
4047 case LFUN_VC_COMPARE: {
4048 if (cmd.argument().empty()) {
4049 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4055 string rev1 = cmd.getArg(0);
4059 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4062 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4063 f2 = buffer->absFileName();
4065 string rev2 = cmd.getArg(1);
4069 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4073 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4074 f1 << "\n" << f2 << "\n" );
4075 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4076 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4086 void GuiView::openChildDocument(string const & fname)
4088 LASSERT(documentBufferView(), return);
4089 Buffer & buffer = documentBufferView()->buffer();
4090 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4091 documentBufferView()->saveBookmark(false);
4092 Buffer * child = nullptr;
4093 if (theBufferList().exists(filename)) {
4094 child = theBufferList().getBuffer(filename);
4097 message(bformat(_("Opening child document %1$s..."),
4098 makeDisplayPath(filename.absFileName())));
4099 child = loadDocument(filename, false);
4101 // Set the parent name of the child document.
4102 // This makes insertion of citations and references in the child work,
4103 // when the target is in the parent or another child document.
4105 child->setParent(&buffer);
4109 bool GuiView::goToFileRow(string const & argument)
4113 size_t i = argument.find_last_of(' ');
4114 if (i != string::npos) {
4115 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4116 istringstream is(argument.substr(i + 1));
4121 if (i == string::npos) {
4122 LYXERR0("Wrong argument: " << argument);
4125 Buffer * buf = nullptr;
4126 string const realtmp = package().temp_dir().realPath();
4127 // We have to use os::path_prefix_is() here, instead of
4128 // simply prefixIs(), because the file name comes from
4129 // an external application and may need case adjustment.
4130 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4131 buf = theBufferList().getBufferFromTmp(file_name, true);
4132 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4133 << (buf ? " success" : " failed"));
4135 // Must replace extension of the file to be .lyx
4136 // and get full path
4137 FileName const s = fileSearch(string(),
4138 support::changeExtension(file_name, ".lyx"), "lyx");
4139 // Either change buffer or load the file
4140 if (theBufferList().exists(s))
4141 buf = theBufferList().getBuffer(s);
4142 else if (s.exists()) {
4143 buf = loadDocument(s);
4148 _("File does not exist: %1$s"),
4149 makeDisplayPath(file_name)));
4155 _("No buffer for file: %1$s."),
4156 makeDisplayPath(file_name))
4161 bool success = documentBufferView()->setCursorFromRow(row);
4163 LYXERR(Debug::OUTFILE,
4164 "setCursorFromRow: invalid position for row " << row);
4165 frontend::Alert::error(_("Inverse Search Failed"),
4166 _("Invalid position requested by inverse search.\n"
4167 "You may need to update the viewed document."));
4173 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4175 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4176 menu->exec(QCursor::pos());
4181 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4182 Buffer const * orig, Buffer * clone, string const & format)
4184 Buffer::ExportStatus const status = func(format);
4186 // the cloning operation will have produced a clone of the entire set of
4187 // documents, starting from the master. so we must delete those.
4188 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4190 busyBuffers.remove(orig);
4195 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4196 Buffer const * orig, Buffer * clone, string const & format)
4198 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4200 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4204 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4205 Buffer const * orig, Buffer * clone, string const & format)
4207 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4209 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4213 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4214 Buffer const * orig, Buffer * clone, string const & format)
4216 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4218 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4222 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4223 Buffer const * used_buffer,
4224 docstring const & msg,
4225 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4226 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4227 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4228 bool allow_async, bool use_tmpdir)
4233 string format = argument;
4235 format = used_buffer->params().getDefaultOutputFormat();
4236 processing_format = format;
4238 progress_->clearMessages();
4241 #if EXPORT_in_THREAD
4243 GuiViewPrivate::busyBuffers.insert(used_buffer);
4244 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4245 if (!cloned_buffer) {
4246 Alert::error(_("Export Error"),
4247 _("Error cloning the Buffer."));
4250 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4255 setPreviewFuture(f);
4256 last_export_format = used_buffer->params().bufferFormat();
4259 // We are asynchronous, so we don't know here anything about the success
4262 Buffer::ExportStatus status;
4264 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4265 } else if (previewFunc) {
4266 status = (used_buffer->*previewFunc)(format);
4269 handleExportStatus(gv_, status, format);
4271 return (status == Buffer::ExportSuccess
4272 || status == Buffer::PreviewSuccess);
4276 Buffer::ExportStatus status;
4278 status = (used_buffer->*syncFunc)(format, true);
4279 } else if (previewFunc) {
4280 status = (used_buffer->*previewFunc)(format);
4283 handleExportStatus(gv_, status, format);
4285 return (status == Buffer::ExportSuccess
4286 || status == Buffer::PreviewSuccess);
4290 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4292 BufferView * bv = currentBufferView();
4293 LASSERT(bv, return);
4295 // Let the current BufferView dispatch its own actions.
4296 bv->dispatch(cmd, dr);
4297 if (dr.dispatched()) {
4298 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4299 updateDialog("document", "");
4303 // Try with the document BufferView dispatch if any.
4304 BufferView * doc_bv = documentBufferView();
4305 if (doc_bv && doc_bv != bv) {
4306 doc_bv->dispatch(cmd, dr);
4307 if (dr.dispatched()) {
4308 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4309 updateDialog("document", "");
4314 // Then let the current Cursor dispatch its own actions.
4315 bv->cursor().dispatch(cmd);
4317 // update completion. We do it here and not in
4318 // processKeySym to avoid another redraw just for a
4319 // changed inline completion
4320 if (cmd.origin() == FuncRequest::KEYBOARD) {
4321 if (cmd.action() == LFUN_SELF_INSERT
4322 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4323 updateCompletion(bv->cursor(), true, true);
4324 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4325 updateCompletion(bv->cursor(), false, true);
4327 updateCompletion(bv->cursor(), false, false);
4330 dr = bv->cursor().result();
4334 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4336 BufferView * bv = currentBufferView();
4337 // By default we won't need any update.
4338 dr.screenUpdate(Update::None);
4339 // assume cmd will be dispatched
4340 dr.dispatched(true);
4342 Buffer * doc_buffer = documentBufferView()
4343 ? &(documentBufferView()->buffer()) : nullptr;
4345 if (cmd.origin() == FuncRequest::TOC) {
4346 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4347 toc->doDispatch(bv->cursor(), cmd, dr);
4351 string const argument = to_utf8(cmd.argument());
4353 switch(cmd.action()) {
4354 case LFUN_BUFFER_CHILD_OPEN:
4355 openChildDocument(to_utf8(cmd.argument()));
4358 case LFUN_BUFFER_IMPORT:
4359 importDocument(to_utf8(cmd.argument()));
4362 case LFUN_MASTER_BUFFER_EXPORT:
4364 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4366 case LFUN_BUFFER_EXPORT: {
4369 // GCC only sees strfwd.h when building merged
4370 if (::lyx::operator==(cmd.argument(), "custom")) {
4371 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4372 // so the following test should not be needed.
4373 // In principle, we could try to switch to such a view...
4374 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4375 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4379 string const dest = cmd.getArg(1);
4380 FileName target_dir;
4381 if (!dest.empty() && FileName::isAbsolute(dest))
4382 target_dir = FileName(support::onlyPath(dest));
4384 target_dir = doc_buffer->fileName().onlyPath();
4386 string const format = (argument.empty() || argument == "default") ?
4387 doc_buffer->params().getDefaultOutputFormat() : argument;
4389 if ((dest.empty() && doc_buffer->isUnnamed())
4390 || !target_dir.isDirWritable()) {
4391 exportBufferAs(*doc_buffer, from_utf8(format));
4394 /* TODO/Review: Is it a problem to also export the children?
4395 See the update_unincluded flag */
4396 d.asyncBufferProcessing(format,
4399 &GuiViewPrivate::exportAndDestroy,
4401 nullptr, cmd.allowAsync());
4402 // TODO Inform user about success
4406 case LFUN_BUFFER_EXPORT_AS: {
4407 LASSERT(doc_buffer, break);
4408 docstring f = cmd.argument();
4410 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4411 exportBufferAs(*doc_buffer, f);
4415 case LFUN_BUFFER_UPDATE: {
4416 d.asyncBufferProcessing(argument,
4419 &GuiViewPrivate::compileAndDestroy,
4421 nullptr, cmd.allowAsync(), true);
4424 case LFUN_BUFFER_VIEW: {
4425 d.asyncBufferProcessing(argument,
4427 _("Previewing ..."),
4428 &GuiViewPrivate::previewAndDestroy,
4430 &Buffer::preview, cmd.allowAsync());
4433 case LFUN_MASTER_BUFFER_UPDATE: {
4434 d.asyncBufferProcessing(argument,
4435 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4437 &GuiViewPrivate::compileAndDestroy,
4439 nullptr, cmd.allowAsync(), true);
4442 case LFUN_MASTER_BUFFER_VIEW: {
4443 d.asyncBufferProcessing(argument,
4444 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4446 &GuiViewPrivate::previewAndDestroy,
4447 nullptr, &Buffer::preview, cmd.allowAsync());
4450 case LFUN_EXPORT_CANCEL: {
4451 Systemcall::killscript();
4452 Q_EMIT scriptKilled();
4455 case LFUN_BUFFER_SWITCH: {
4456 string const file_name = to_utf8(cmd.argument());
4457 if (!FileName::isAbsolute(file_name)) {
4459 dr.setMessage(_("Absolute filename expected."));
4463 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4466 dr.setMessage(_("Document not loaded"));
4470 // Do we open or switch to the buffer in this view ?
4471 if (workArea(*buffer)
4472 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4477 // Look for the buffer in other views
4478 QList<int> const ids = guiApp->viewIds();
4480 for (; i != ids.size(); ++i) {
4481 GuiView & gv = guiApp->view(ids[i]);
4482 if (gv.workArea(*buffer)) {
4484 gv.activateWindow();
4486 gv.setBuffer(buffer);
4491 // If necessary, open a new window as a last resort
4492 if (i == ids.size()) {
4493 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4499 case LFUN_BUFFER_NEXT:
4500 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4503 case LFUN_BUFFER_MOVE_NEXT:
4504 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4507 case LFUN_BUFFER_PREVIOUS:
4508 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4511 case LFUN_BUFFER_MOVE_PREVIOUS:
4512 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4515 case LFUN_BUFFER_CHKTEX:
4516 LASSERT(doc_buffer, break);
4517 doc_buffer->runChktex();
4520 case LFUN_COMMAND_EXECUTE: {
4521 command_execute_ = true;
4522 minibuffer_focus_ = true;
4525 case LFUN_DROP_LAYOUTS_CHOICE:
4526 d.layout_->showPopup();
4529 case LFUN_MENU_OPEN:
4530 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4531 menu->exec(QCursor::pos());
4534 case LFUN_FILE_INSERT: {
4535 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4536 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4537 dr.forceBufferUpdate();
4538 dr.screenUpdate(Update::Force);
4543 case LFUN_FILE_INSERT_PLAINTEXT:
4544 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4545 string const fname = to_utf8(cmd.argument());
4546 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4547 dr.setMessage(_("Absolute filename expected."));
4551 FileName filename(fname);
4552 if (fname.empty()) {
4553 FileDialog dlg(qt_("Select file to insert"));
4555 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4556 QStringList(qt_("All Files (*)")));
4558 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4559 dr.setMessage(_("Canceled."));
4563 filename.set(fromqstr(result.second));
4567 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4568 bv->dispatch(new_cmd, dr);
4573 case LFUN_BUFFER_RELOAD: {
4574 LASSERT(doc_buffer, break);
4577 bool drop = (cmd.argument() == "dump");
4580 if (!drop && !doc_buffer->isClean()) {
4581 docstring const file =
4582 makeDisplayPath(doc_buffer->absFileName(), 20);
4583 if (doc_buffer->notifiesExternalModification()) {
4584 docstring text = _("The current version will be lost. "
4585 "Are you sure you want to load the version on disk "
4586 "of the document %1$s?");
4587 ret = Alert::prompt(_("Reload saved document?"),
4588 bformat(text, file), 1, 1,
4589 _("&Reload"), _("&Cancel"));
4591 docstring text = _("Any changes will be lost. "
4592 "Are you sure you want to revert to the saved version "
4593 "of the document %1$s?");
4594 ret = Alert::prompt(_("Revert to saved document?"),
4595 bformat(text, file), 1, 1,
4596 _("&Revert"), _("&Cancel"));
4601 doc_buffer->markClean();
4602 reloadBuffer(*doc_buffer);
4603 dr.forceBufferUpdate();
4608 case LFUN_BUFFER_RESET_EXPORT:
4609 LASSERT(doc_buffer, break);
4610 doc_buffer->requireFreshStart(true);
4611 dr.setMessage(_("Buffer export reset."));
4614 case LFUN_BUFFER_WRITE:
4615 LASSERT(doc_buffer, break);
4616 saveBuffer(*doc_buffer);
4619 case LFUN_BUFFER_WRITE_AS:
4620 LASSERT(doc_buffer, break);
4621 renameBuffer(*doc_buffer, cmd.argument());
4624 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4625 LASSERT(doc_buffer, break);
4626 renameBuffer(*doc_buffer, cmd.argument(),
4627 LV_WRITE_AS_TEMPLATE);
4630 case LFUN_BUFFER_WRITE_ALL: {
4631 Buffer * first = theBufferList().first();
4634 message(_("Saving all documents..."));
4635 // We cannot use a for loop as the buffer list cycles.
4638 if (!b->isClean()) {
4640 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4642 b = theBufferList().next(b);
4643 } while (b != first);
4644 dr.setMessage(_("All documents saved."));
4648 case LFUN_MASTER_BUFFER_FORALL: {
4652 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4653 funcToRun.allowAsync(false);
4655 for (Buffer const * buf : doc_buffer->allRelatives()) {
4656 // Switch to other buffer view and resend cmd
4657 lyx::dispatch(FuncRequest(
4658 LFUN_BUFFER_SWITCH, buf->absFileName()));
4659 lyx::dispatch(funcToRun);
4662 lyx::dispatch(FuncRequest(
4663 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4667 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4668 LASSERT(doc_buffer, break);
4669 doc_buffer->clearExternalModification();
4672 case LFUN_BUFFER_CLOSE:
4676 case LFUN_BUFFER_CLOSE_ALL:
4680 case LFUN_DEVEL_MODE_TOGGLE:
4681 devel_mode_ = !devel_mode_;
4683 dr.setMessage(_("Developer mode is now enabled."));
4685 dr.setMessage(_("Developer mode is now disabled."));
4688 case LFUN_TOOLBAR_SET: {
4689 string const name = cmd.getArg(0);
4690 string const state = cmd.getArg(1);
4691 if (GuiToolbar * t = toolbar(name))
4696 case LFUN_TOOLBAR_TOGGLE: {
4697 string const name = cmd.getArg(0);
4698 if (GuiToolbar * t = toolbar(name))
4703 case LFUN_TOOLBAR_MOVABLE: {
4704 string const name = cmd.getArg(0);
4706 // toggle (all) toolbars movablility
4707 toolbarsMovable_ = !toolbarsMovable_;
4708 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4709 GuiToolbar * tb = toolbar(ti.name);
4710 if (tb && tb->isMovable() != toolbarsMovable_)
4711 // toggle toolbar movablity if it does not fit lock
4712 // (all) toolbars positions state silent = true, since
4713 // status bar notifications are slow
4716 if (toolbarsMovable_)
4717 dr.setMessage(_("Toolbars unlocked."));
4719 dr.setMessage(_("Toolbars locked."));
4720 } else if (GuiToolbar * tb = toolbar(name))
4721 // toggle current toolbar movablity
4723 // update lock (all) toolbars positions
4724 updateLockToolbars();
4728 case LFUN_ICON_SIZE: {
4729 QSize size = d.iconSize(cmd.argument());
4731 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4732 size.width(), size.height()));
4736 case LFUN_DIALOG_UPDATE: {
4737 string const name = to_utf8(cmd.argument());
4738 if (name == "prefs" || name == "document")
4739 updateDialog(name, string());
4740 else if (name == "paragraph")
4741 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4742 else if (currentBufferView()) {
4743 Inset * inset = currentBufferView()->editedInset(name);
4744 // Can only update a dialog connected to an existing inset
4746 // FIXME: get rid of this indirection; GuiView ask the inset
4747 // if he is kind enough to update itself...
4748 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4749 //FIXME: pass DispatchResult here?
4750 inset->dispatch(currentBufferView()->cursor(), fr);
4756 case LFUN_DIALOG_TOGGLE: {
4757 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4758 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4759 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4763 case LFUN_DIALOG_DISCONNECT_INSET:
4764 disconnectDialog(to_utf8(cmd.argument()));
4767 case LFUN_DIALOG_HIDE: {
4768 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4772 case LFUN_DIALOG_SHOW: {
4773 string const name = cmd.getArg(0);
4774 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4776 if (name == "latexlog") {
4777 // getStatus checks that
4778 LASSERT(doc_buffer, break);
4779 Buffer::LogType type;
4780 string const logfile = doc_buffer->logName(&type);
4782 case Buffer::latexlog:
4785 case Buffer::buildlog:
4786 sdata = "literate ";
4789 sdata += Lexer::quoteString(logfile);
4790 showDialog("log", sdata);
4791 } else if (name == "vclog") {
4792 // getStatus checks that
4793 LASSERT(doc_buffer, break);
4794 string const sdata2 = "vc " +
4795 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4796 showDialog("log", sdata2);
4797 } else if (name == "symbols") {
4798 sdata = bv->cursor().getEncoding()->name();
4800 showDialog("symbols", sdata);
4801 } else if (name == "findreplace") {
4802 sdata = to_utf8(bv->cursor().selectionAsString(false));
4803 showDialog(name, sdata);
4805 } else if (name == "prefs" && isFullScreen()) {
4806 lfunUiToggle("fullscreen");
4807 showDialog("prefs", sdata);
4809 showDialog(name, sdata);
4814 dr.setMessage(cmd.argument());
4817 case LFUN_UI_TOGGLE: {
4818 string arg = cmd.getArg(0);
4819 if (!lfunUiToggle(arg)) {
4820 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4821 dr.setMessage(bformat(msg, from_utf8(arg)));
4823 // Make sure the keyboard focus stays in the work area.
4828 case LFUN_VIEW_SPLIT: {
4829 LASSERT(doc_buffer, break);
4830 string const orientation = cmd.getArg(0);
4831 d.splitter_->setOrientation(orientation == "vertical"
4832 ? Qt::Vertical : Qt::Horizontal);
4833 TabWorkArea * twa = addTabWorkArea();
4834 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4835 setCurrentWorkArea(wa);
4838 case LFUN_TAB_GROUP_CLOSE:
4839 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4840 closeTabWorkArea(twa);
4841 d.current_work_area_ = nullptr;
4842 twa = d.currentTabWorkArea();
4843 // Switch to the next GuiWorkArea in the found TabWorkArea.
4845 // Make sure the work area is up to date.
4846 setCurrentWorkArea(twa->currentWorkArea());
4848 setCurrentWorkArea(nullptr);
4853 case LFUN_VIEW_CLOSE:
4854 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4855 closeWorkArea(twa->currentWorkArea());
4856 d.current_work_area_ = nullptr;
4857 twa = d.currentTabWorkArea();
4858 // Switch to the next GuiWorkArea in the found TabWorkArea.
4860 // Make sure the work area is up to date.
4861 setCurrentWorkArea(twa->currentWorkArea());
4863 setCurrentWorkArea(nullptr);
4868 case LFUN_COMPLETION_INLINE:
4869 if (d.current_work_area_)
4870 d.current_work_area_->completer().showInline();
4873 case LFUN_COMPLETION_POPUP:
4874 if (d.current_work_area_)
4875 d.current_work_area_->completer().showPopup();
4880 if (d.current_work_area_)
4881 d.current_work_area_->completer().tab();
4884 case LFUN_COMPLETION_CANCEL:
4885 if (d.current_work_area_) {
4886 if (d.current_work_area_->completer().popupVisible())
4887 d.current_work_area_->completer().hidePopup();
4889 d.current_work_area_->completer().hideInline();
4893 case LFUN_COMPLETION_ACCEPT:
4894 if (d.current_work_area_)
4895 d.current_work_area_->completer().activate();
4898 case LFUN_BUFFER_ZOOM_IN:
4899 case LFUN_BUFFER_ZOOM_OUT:
4900 case LFUN_BUFFER_ZOOM: {
4901 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4903 // Actual zoom value: default zoom + fractional extra value
4904 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4905 zoom = min(max(zoom, zoom_min_), zoom_max_);
4907 setCurrentZoom(zoom);
4909 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4910 lyxrc.currentZoom, lyxrc.defaultZoom));
4912 guiApp->fontLoader().update();
4913 // Regenerate instant previews
4914 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4915 && doc_buffer && doc_buffer->loader())
4916 doc_buffer->loader()->refreshPreviews();
4917 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4921 case LFUN_VC_REGISTER:
4922 case LFUN_VC_RENAME:
4924 case LFUN_VC_CHECK_IN:
4925 case LFUN_VC_CHECK_OUT:
4926 case LFUN_VC_REPO_UPDATE:
4927 case LFUN_VC_LOCKING_TOGGLE:
4928 case LFUN_VC_REVERT:
4929 case LFUN_VC_UNDO_LAST:
4930 case LFUN_VC_COMMAND:
4931 case LFUN_VC_COMPARE:
4932 dispatchVC(cmd, dr);
4935 case LFUN_SERVER_GOTO_FILE_ROW:
4936 if(goToFileRow(to_utf8(cmd.argument())))
4937 dr.screenUpdate(Update::Force | Update::FitCursor);
4940 case LFUN_LYX_ACTIVATE:
4944 case LFUN_WINDOW_RAISE:
4950 case LFUN_FORWARD_SEARCH: {
4951 // it seems safe to assume we have a document buffer, since
4952 // getStatus wants one.
4953 LASSERT(doc_buffer, break);
4954 Buffer const * doc_master = doc_buffer->masterBuffer();
4955 FileName const path(doc_master->temppath());
4956 string const texname = doc_master->isChild(doc_buffer)
4957 ? DocFileName(changeExtension(
4958 doc_buffer->absFileName(),
4959 "tex")).mangledFileName()
4960 : doc_buffer->latexName();
4961 string const fulltexname =
4962 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4963 string const mastername =
4964 removeExtension(doc_master->latexName());
4965 FileName const dviname(addName(path.absFileName(),
4966 addExtension(mastername, "dvi")));
4967 FileName const pdfname(addName(path.absFileName(),
4968 addExtension(mastername, "pdf")));
4969 bool const have_dvi = dviname.exists();
4970 bool const have_pdf = pdfname.exists();
4971 if (!have_dvi && !have_pdf) {
4972 dr.setMessage(_("Please, preview the document first."));
4975 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4976 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4977 string outname = dviname.onlyFileName();
4978 string command = lyxrc.forward_search_dvi;
4979 if ((!goto_dvi || goto_pdf) &&
4980 pdfname.lastModified() > dviname.lastModified()) {
4981 outname = pdfname.onlyFileName();
4982 command = lyxrc.forward_search_pdf;
4985 DocIterator cur = bv->cursor();
4986 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4987 LYXERR(Debug::ACTION, "Forward search: row:" << row
4989 if (row == -1 || command.empty()) {
4990 dr.setMessage(_("Couldn't proceed."));
4993 string texrow = convert<string>(row);
4995 command = subst(command, "$$n", texrow);
4996 command = subst(command, "$$f", fulltexname);
4997 command = subst(command, "$$t", texname);
4998 command = subst(command, "$$o", outname);
5000 volatile PathChanger p(path);
5002 one.startscript(Systemcall::DontWait, command);
5006 case LFUN_SPELLING_CONTINUOUSLY:
5007 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5008 dr.screenUpdate(Update::Force);
5011 case LFUN_CITATION_OPEN: {
5013 if (theFormats().getFormat("pdf"))
5014 pdfv = theFormats().getFormat("pdf")->viewer();
5015 if (theFormats().getFormat("ps"))
5016 psv = theFormats().getFormat("ps")->viewer();
5017 frontend::showTarget(argument, pdfv, psv);
5022 // The LFUN must be for one of BufferView, Buffer or Cursor;
5024 dispatchToBufferView(cmd, dr);
5028 // Need to update bv because many LFUNs here might have destroyed it
5029 bv = currentBufferView();
5031 // Clear non-empty selections
5032 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5034 Cursor & cur = bv->cursor();
5035 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5036 cur.clearSelection();
5042 bool GuiView::lfunUiToggle(string const & ui_component)
5044 if (ui_component == "scrollbar") {
5045 // hide() is of no help
5046 if (d.current_work_area_->verticalScrollBarPolicy() ==
5047 Qt::ScrollBarAlwaysOff)
5049 d.current_work_area_->setVerticalScrollBarPolicy(
5050 Qt::ScrollBarAsNeeded);
5052 d.current_work_area_->setVerticalScrollBarPolicy(
5053 Qt::ScrollBarAlwaysOff);
5054 } else if (ui_component == "statusbar") {
5055 statusBar()->setVisible(!statusBar()->isVisible());
5056 } else if (ui_component == "menubar") {
5057 menuBar()->setVisible(!menuBar()->isVisible());
5058 } else if (ui_component == "zoomlevel") {
5059 zoom_value_->setVisible(!zoom_value_->isVisible());
5060 } else if (ui_component == "zoomslider") {
5061 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5062 zoom_in_->setVisible(zoom_slider_->isVisible());
5063 zoom_out_->setVisible(zoom_slider_->isVisible());
5064 } else if (ui_component == "statistics-w") {
5065 word_count_enabled_ = !word_count_enabled_;
5068 } else if (ui_component == "statistics-cb") {
5069 char_count_enabled_ = !char_count_enabled_;
5072 } else if (ui_component == "statistics-c") {
5073 char_nb_count_enabled_ = !char_nb_count_enabled_;
5076 } else if (ui_component == "frame") {
5077 int const l = contentsMargins().left();
5079 //are the frames in default state?
5080 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5082 #if QT_VERSION > 0x050903
5083 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5085 setContentsMargins(-2, -2, -2, -2);
5087 #if QT_VERSION > 0x050903
5088 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5090 setContentsMargins(0, 0, 0, 0);
5093 if (ui_component == "fullscreen") {
5097 stat_counts_->setVisible(statsEnabled());
5102 void GuiView::toggleFullScreen()
5104 setWindowState(windowState() ^ Qt::WindowFullScreen);
5108 Buffer const * GuiView::updateInset(Inset const * inset)
5113 Buffer const * inset_buffer = &(inset->buffer());
5115 for (int i = 0; i != d.splitter_->count(); ++i) {
5116 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5119 Buffer const * buffer = &(wa->bufferView().buffer());
5120 if (inset_buffer == buffer)
5121 wa->scheduleRedraw(true);
5123 return inset_buffer;
5127 void GuiView::restartCaret()
5129 /* When we move around, or type, it's nice to be able to see
5130 * the caret immediately after the keypress.
5132 if (d.current_work_area_)
5133 d.current_work_area_->startBlinkingCaret();
5135 // Take this occasion to update the other GUI elements.
5141 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5143 if (d.current_work_area_)
5144 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5149 // This list should be kept in sync with the list of insets in
5150 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5151 // dialog should have the same name as the inset.
5152 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5153 // docs in LyXAction.cpp.
5155 char const * const dialognames[] = {
5157 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5158 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5159 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5160 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5161 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5162 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5163 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5164 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5166 char const * const * const end_dialognames =
5167 dialognames + (sizeof(dialognames) / sizeof(char *));
5171 cmpCStr(char const * name) : name_(name) {}
5172 bool operator()(char const * other) {
5173 return strcmp(other, name_) == 0;
5180 bool isValidName(string const & name)
5182 return find_if(dialognames, end_dialognames,
5183 cmpCStr(name.c_str())) != end_dialognames;
5189 void GuiView::resetDialogs()
5191 // Make sure that no LFUN uses any GuiView.
5192 guiApp->setCurrentView(nullptr);
5196 constructToolbars();
5197 guiApp->menus().fillMenuBar(menuBar(), this, false);
5198 d.layout_->updateContents(true);
5199 // Now update controls with current buffer.
5200 guiApp->setCurrentView(this);
5206 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5208 for (QObject * child: widget->children()) {
5209 if (child->inherits("QGroupBox")) {
5210 QGroupBox * box = (QGroupBox*) child;
5213 flatGroupBoxes(child, flag);
5219 Dialog * GuiView::find(string const & name, bool hide_it) const
5221 if (!isValidName(name))
5224 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5226 if (it != d.dialogs_.end()) {
5228 it->second->hideView();
5229 return it->second.get();
5235 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5237 Dialog * dialog = find(name, hide_it);
5238 if (dialog != nullptr)
5241 dialog = build(name);
5242 d.dialogs_[name].reset(dialog);
5243 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5244 // Force a uniform style for group boxes
5245 // On Mac non-flat works better, on Linux flat is standard
5246 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5248 if (lyxrc.allow_geometry_session)
5249 dialog->restoreSession();
5256 void GuiView::showDialog(string const & name, string const & sdata,
5259 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5263 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5269 const string name = fromqstr(qname);
5270 const string sdata = fromqstr(qdata);
5274 Dialog * dialog = findOrBuild(name, false);
5276 bool const visible = dialog->isVisibleView();
5277 dialog->showData(sdata);
5278 if (currentBufferView())
5279 currentBufferView()->editInset(name, inset);
5280 // We only set the focus to the new dialog if it was not yet
5281 // visible in order not to change the existing previous behaviour
5283 // activateWindow is needed for floating dockviews
5284 dialog->asQWidget()->raise();
5285 dialog->asQWidget()->activateWindow();
5286 if (dialog->wantInitialFocus())
5287 dialog->asQWidget()->setFocus();
5291 catch (ExceptionMessage const &) {
5299 bool GuiView::isDialogVisible(string const & name) const
5301 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5302 if (it == d.dialogs_.end())
5304 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5308 void GuiView::hideDialog(string const & name, Inset * inset)
5310 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5311 if (it == d.dialogs_.end())
5315 if (!currentBufferView())
5317 if (inset != currentBufferView()->editedInset(name))
5321 Dialog * const dialog = it->second.get();
5322 if (dialog->isVisibleView())
5324 if (currentBufferView())
5325 currentBufferView()->editInset(name, nullptr);
5329 void GuiView::disconnectDialog(string const & name)
5331 if (!isValidName(name))
5333 if (currentBufferView())
5334 currentBufferView()->editInset(name, nullptr);
5338 void GuiView::hideAll() const
5340 for(auto const & dlg_p : d.dialogs_)
5341 dlg_p.second->hideView();
5345 void GuiView::updateDialogs()
5347 for(auto const & dlg_p : d.dialogs_) {
5348 Dialog * dialog = dlg_p.second.get();
5350 if (dialog->needBufferOpen() && !documentBufferView())
5351 hideDialog(fromqstr(dialog->name()), nullptr);
5352 else if (dialog->isVisibleView())
5353 dialog->checkStatus();
5361 Dialog * GuiView::build(string const & name)
5363 return createDialog(*this, name);
5367 SEMenu::SEMenu(QWidget * parent)
5369 QAction * action = addAction(qt_("Disable Shell Escape"));
5370 connect(action, SIGNAL(triggered()),
5371 parent, SLOT(disableShellEscape()));
5375 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5377 if (event->button() == Qt::LeftButton) {
5382 } // namespace frontend
5385 #include "moc_GuiView.cpp"