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 "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiClickableLabel.h"
23 #include "GuiCommandBuffer.h"
24 #include "GuiCompleter.h"
25 #include "GuiKeySymbol.h"
27 #include "GuiToolbar.h"
28 #include "GuiWorkArea.h"
29 #include "GuiProgress.h"
30 #include "LayoutBox.h"
34 #include "qt_helpers.h"
35 #include "support/filetools.h"
37 #include "frontends/alert.h"
38 #include "frontends/KeySymbol.h"
40 #include "buffer_funcs.h"
42 #include "BufferList.h"
43 #include "BufferParams.h"
44 #include "BufferView.h"
46 #include "Converter.h"
48 #include "CutAndPaste.h"
50 #include "ErrorList.h"
52 #include "FuncStatus.h"
53 #include "FuncRequest.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
67 #include "TextClass.h"
72 #include "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/filetools.h"
77 #include "support/gettext.h"
78 #include "support/filetools.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>
93 #include <QDesktopWidget>
94 #include <QDragEnterEvent>
97 #include <QFutureWatcher>
108 #include <QPushButton>
109 #include <QScrollBar>
111 #include <QShowEvent>
113 #include <QStackedWidget>
114 #include <QStatusBar>
115 #include <QSvgRenderer>
116 #include <QtConcurrentRun>
121 #include <QWindowStateChangeEvent>
124 // sync with GuiAlert.cpp
125 #define EXPORT_in_THREAD 1
128 #include "support/bind.h"
132 #ifdef HAVE_SYS_TIME_H
133 # include <sys/time.h>
141 using namespace lyx::support;
145 using support::addExtension;
146 using support::changeExtension;
147 using support::removeExtension;
153 class BackgroundWidget : public QWidget
156 BackgroundWidget(int width, int height)
157 : width_(width), height_(height)
159 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
160 if (!lyxrc.show_banner)
162 /// The text to be written on top of the pixmap
163 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
164 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
165 /// The text to be written on top of the pixmap
166 QString const text = lyx_version ?
167 qt_("version ") + lyx_version : qt_("unknown version");
168 #if QT_VERSION >= 0x050000
169 QString imagedir = "images/";
170 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
171 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
172 if (svgRenderer.isValid()) {
173 splash_ = QPixmap(splashSize());
174 QPainter painter(&splash_);
175 svgRenderer.render(&painter);
176 splash_.setDevicePixelRatio(pixelRatio());
178 splash_ = getPixmap("images/", "banner", "png");
181 splash_ = getPixmap("images/", "banner", "svgz,png");
184 QPainter pain(&splash_);
185 pain.setPen(QColor(0, 0, 0));
186 qreal const fsize = fontSize();
189 qreal locscale = htextsize.toFloat(&ok);
192 QPointF const position = textPosition(false);
193 QPointF const hposition = textPosition(true);
194 QRectF const hrect(hposition, splashSize());
196 "widget pixel ratio: " << pixelRatio() <<
197 " splash pixel ratio: " << splashPixelRatio() <<
198 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
200 // The font used to display the version info
201 font.setStyleHint(QFont::SansSerif);
202 font.setWeight(QFont::Bold);
203 font.setPointSizeF(fsize);
205 pain.drawText(position, text);
206 // The font used to display the version info
207 font.setStyleHint(QFont::SansSerif);
208 font.setWeight(QFont::Normal);
209 font.setPointSizeF(hfsize);
210 // Check how long the logo gets with the current font
211 // and adapt if the font is running wider than what
213 QFontMetrics fm(font);
214 // Split the title into lines to measure the longest line
215 // in the current l7n.
216 QStringList titlesegs = htext.split('\n');
218 int hline = fm.height();
219 QStringList::const_iterator sit;
220 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
221 if (fm.width(*sit) > wline)
222 wline = fm.width(*sit);
224 // The longest line in the reference font (for English)
225 // is 180. Calculate scale factor from that.
226 double const wscale = wline > 0 ? (180.0 / wline) : 1;
227 // Now do the same for the height (necessary for condensed fonts)
228 double const hscale = (34.0 / hline);
229 // take the lower of the two scale factors.
230 double const scale = min(wscale, hscale);
231 // Now rescale. Also consider l7n's offset factor.
232 font.setPointSizeF(hfsize * scale * locscale);
235 pain.drawText(hrect, Qt::AlignLeft, htext);
236 setFocusPolicy(Qt::StrongFocus);
239 void paintEvent(QPaintEvent *)
241 int const w = width_;
242 int const h = height_;
243 int const x = (width() - w) / 2;
244 int const y = (height() - h) / 2;
246 "widget pixel ratio: " << pixelRatio() <<
247 " splash pixel ratio: " << splashPixelRatio() <<
248 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
250 pain.drawPixmap(x, y, w, h, splash_);
253 void keyPressEvent(QKeyEvent * ev)
256 setKeySymbol(&sym, ev);
258 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
270 /// Current ratio between physical pixels and device-independent pixels
271 double pixelRatio() const {
272 #if QT_VERSION >= 0x050000
273 return qt_scale_factor * devicePixelRatio();
279 qreal fontSize() const {
280 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
283 QPointF textPosition(bool const heading) const {
284 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
285 : QPointF(width_/2 - 18, height_/2 + 45);
288 QSize splashSize() const {
290 static_cast<unsigned int>(width_ * pixelRatio()),
291 static_cast<unsigned int>(height_ * pixelRatio()));
294 /// Ratio between physical pixels and device-independent pixels of splash image
295 double splashPixelRatio() const {
296 #if QT_VERSION >= 0x050000
297 return splash_.devicePixelRatio();
305 /// Toolbar store providing access to individual toolbars by name.
306 typedef map<string, GuiToolbar *> ToolbarMap;
308 typedef shared_ptr<Dialog> DialogPtr;
313 class GuiView::GuiViewPrivate
316 GuiViewPrivate(GuiViewPrivate const &);
317 void operator=(GuiViewPrivate const &);
319 GuiViewPrivate(GuiView * gv)
320 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
321 layout_(nullptr), autosave_timeout_(5000),
324 // hardcode here the platform specific icon size
325 smallIconSize = 16; // scaling problems
326 normalIconSize = 20; // ok, default if iconsize.png is missing
327 bigIconSize = 26; // better for some math icons
328 hugeIconSize = 32; // better for hires displays
331 // if it exists, use width of iconsize.png as normal size
332 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
333 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
335 QImage image(toqstr(fn.absFileName()));
336 if (image.width() < int(smallIconSize))
337 normalIconSize = smallIconSize;
338 else if (image.width() > int(giantIconSize))
339 normalIconSize = giantIconSize;
341 normalIconSize = image.width();
344 splitter_ = new QSplitter;
345 bg_widget_ = new BackgroundWidget(400, 250);
346 stack_widget_ = new QStackedWidget;
347 stack_widget_->addWidget(bg_widget_);
348 stack_widget_->addWidget(splitter_);
351 // TODO cleanup, remove the singleton, handle multiple Windows?
352 progress_ = ProgressInterface::instance();
353 if (!dynamic_cast<GuiProgress*>(progress_)) {
354 progress_ = new GuiProgress; // TODO who deletes it
355 ProgressInterface::setInstance(progress_);
358 dynamic_cast<GuiProgress*>(progress_),
359 SIGNAL(updateStatusBarMessage(QString const&)),
360 gv, SLOT(updateStatusBarMessage(QString const&)));
362 dynamic_cast<GuiProgress*>(progress_),
363 SIGNAL(clearMessageText()),
364 gv, SLOT(clearMessageText()));
371 delete stack_widget_;
376 stack_widget_->setCurrentWidget(bg_widget_);
377 bg_widget_->setUpdatesEnabled(true);
378 bg_widget_->setFocus();
381 int tabWorkAreaCount()
383 return splitter_->count();
386 TabWorkArea * tabWorkArea(int i)
388 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
391 TabWorkArea * currentTabWorkArea()
393 int areas = tabWorkAreaCount();
395 // The first TabWorkArea is always the first one, if any.
396 return tabWorkArea(0);
398 for (int i = 0; i != areas; ++i) {
399 TabWorkArea * twa = tabWorkArea(i);
400 if (current_main_work_area_ == twa->currentWorkArea())
404 // None has the focus so we just take the first one.
405 return tabWorkArea(0);
408 int countWorkAreasOf(Buffer & buf)
410 int areas = tabWorkAreaCount();
412 for (int i = 0; i != areas; ++i) {
413 TabWorkArea * twa = tabWorkArea(i);
414 if (twa->workArea(buf))
420 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
422 if (processing_thread_watcher_.isRunning()) {
423 // we prefer to cancel this preview in order to keep a snappy
427 processing_thread_watcher_.setFuture(f);
430 QSize iconSize(docstring const & icon_size)
433 if (icon_size == "small")
434 size = smallIconSize;
435 else if (icon_size == "normal")
436 size = normalIconSize;
437 else if (icon_size == "big")
439 else if (icon_size == "huge")
441 else if (icon_size == "giant")
442 size = giantIconSize;
444 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
446 if (size < smallIconSize)
447 size = smallIconSize;
449 return QSize(size, size);
452 QSize iconSize(QString const & icon_size)
454 return iconSize(qstring_to_ucs4(icon_size));
457 string & iconSize(QSize const & qsize)
459 LATTEST(qsize.width() == qsize.height());
461 static string icon_size;
463 unsigned int size = qsize.width();
465 if (size < smallIconSize)
466 size = smallIconSize;
468 if (size == smallIconSize)
470 else if (size == normalIconSize)
471 icon_size = "normal";
472 else if (size == bigIconSize)
474 else if (size == hugeIconSize)
476 else if (size == giantIconSize)
479 icon_size = convert<string>(size);
484 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
485 Buffer * buffer, string const & format);
486 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
487 Buffer * buffer, string const & format);
488 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
489 Buffer * buffer, string const & format);
490 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
493 static Buffer::ExportStatus runAndDestroy(const T& func,
494 Buffer const * orig, Buffer * buffer, string const & format);
496 // TODO syncFunc/previewFunc: use bind
497 bool asyncBufferProcessing(string const & argument,
498 Buffer const * used_buffer,
499 docstring const & msg,
500 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
501 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
502 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
505 QVector<GuiWorkArea*> guiWorkAreas();
509 GuiWorkArea * current_work_area_;
510 GuiWorkArea * current_main_work_area_;
511 QSplitter * splitter_;
512 QStackedWidget * stack_widget_;
513 BackgroundWidget * bg_widget_;
515 ToolbarMap toolbars_;
516 ProgressInterface* progress_;
517 /// The main layout box.
519 * \warning Don't Delete! The layout box is actually owned by
520 * whichever toolbar contains it. All the GuiView class needs is a
521 * means of accessing it.
523 * FIXME: replace that with a proper model so that we are not limited
524 * to only one dialog.
529 map<string, DialogPtr> dialogs_;
532 QTimer statusbar_timer_;
533 /// auto-saving of buffers
534 Timeout autosave_timeout_;
537 TocModels toc_models_;
540 QFutureWatcher<docstring> autosave_watcher_;
541 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
543 string last_export_format;
544 string processing_format;
546 static QSet<Buffer const *> busyBuffers;
548 unsigned int smallIconSize;
549 unsigned int normalIconSize;
550 unsigned int bigIconSize;
551 unsigned int hugeIconSize;
552 unsigned int giantIconSize;
554 /// flag against a race condition due to multiclicks, see bug #1119
558 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
561 GuiView::GuiView(int id)
562 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
563 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
566 connect(this, SIGNAL(bufferViewChanged()),
567 this, SLOT(onBufferViewChanged()));
569 // GuiToolbars *must* be initialised before the menu bar.
570 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
573 // set ourself as the current view. This is needed for the menu bar
574 // filling, at least for the static special menu item on Mac. Otherwise
575 // they are greyed out.
576 guiApp->setCurrentView(this);
578 // Fill up the menu bar.
579 guiApp->menus().fillMenuBar(menuBar(), this, true);
581 setCentralWidget(d.stack_widget_);
583 // Start autosave timer
584 if (lyxrc.autosave) {
585 // The connection is closed when this is destroyed.
586 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
587 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
588 d.autosave_timeout_.start();
590 connect(&d.statusbar_timer_, SIGNAL(timeout()),
591 this, SLOT(clearMessage()));
593 // We don't want to keep the window in memory if it is closed.
594 setAttribute(Qt::WA_DeleteOnClose, true);
596 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
597 // QIcon::fromTheme was introduced in Qt 4.6
598 #if (QT_VERSION >= 0x040600)
599 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
600 // since the icon is provided in the application bundle. We use a themed
601 // version when available and use the bundled one as fallback.
602 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
604 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
610 // use tabbed dock area for multiple docks
611 // (such as "source" and "messages")
612 setDockOptions(QMainWindow::ForceTabbedDocks);
615 setAcceptDrops(true);
617 // add busy indicator to statusbar
618 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
619 statusBar()->addPermanentWidget(busylabel);
620 search_mode mode = theGuiApp()->imageSearchMode();
621 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
622 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
623 busylabel->setMovie(busyanim);
627 connect(&d.processing_thread_watcher_, SIGNAL(started()),
628 busylabel, SLOT(show()));
629 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
630 busylabel, SLOT(hide()));
631 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
633 QFontMetrics const fm(statusBar()->fontMetrics());
634 int const iconheight = max(int(d.normalIconSize), fm.height());
635 QSize const iconsize(iconheight, iconheight);
637 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
638 shell_escape_ = new QLabel(statusBar());
639 shell_escape_->setPixmap(shellescape);
640 shell_escape_->setScaledContents(true);
641 shell_escape_->setAlignment(Qt::AlignCenter);
642 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
643 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
644 "external commands for this document. "
645 "Right click to change."));
646 SEMenu * menu = new SEMenu(this);
647 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
648 menu, SLOT(showMenu(QPoint)));
649 shell_escape_->hide();
650 statusBar()->addPermanentWidget(shell_escape_);
652 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
653 read_only_ = new QLabel(statusBar());
654 read_only_->setPixmap(readonly);
655 read_only_->setScaledContents(true);
656 read_only_->setAlignment(Qt::AlignCenter);
658 statusBar()->addPermanentWidget(read_only_);
660 version_control_ = new QLabel(statusBar());
661 version_control_->setAlignment(Qt::AlignCenter);
662 version_control_->setFrameStyle(QFrame::StyledPanel);
663 version_control_->hide();
664 statusBar()->addPermanentWidget(version_control_);
666 statusBar()->setSizeGripEnabled(true);
669 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
670 SLOT(autoSaveThreadFinished()));
672 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
673 SLOT(processingThreadStarted()));
674 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
675 SLOT(processingThreadFinished()));
677 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
678 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
680 // Forbid too small unresizable window because it can happen
681 // with some window manager under X11.
682 setMinimumSize(300, 200);
684 if (lyxrc.allow_geometry_session) {
685 // Now take care of session management.
690 // no session handling, default to a sane size.
691 setGeometry(50, 50, 690, 510);
694 // clear session data if any.
696 settings.remove("views");
706 void GuiView::disableShellEscape()
708 BufferView * bv = documentBufferView();
711 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
712 bv->buffer().params().shell_escape = false;
713 bv->processUpdateFlags(Update::Force);
717 void GuiView::checkCancelBackground()
719 docstring const ttl = _("Cancel Export?");
720 docstring const msg = _("Do you want to cancel the background export process?");
722 Alert::prompt(ttl, msg, 1, 1,
723 _("&Cancel export"), _("Co&ntinue"));
725 Systemcall::killscript();
729 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
731 QVector<GuiWorkArea*> areas;
732 for (int i = 0; i < tabWorkAreaCount(); i++) {
733 TabWorkArea* ta = tabWorkArea(i);
734 for (int u = 0; u < ta->count(); u++) {
735 areas << ta->workArea(u);
741 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
742 string const & format)
744 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
747 case Buffer::ExportSuccess:
748 msg = bformat(_("Successful export to format: %1$s"), fmt);
750 case Buffer::ExportCancel:
751 msg = _("Document export cancelled.");
753 case Buffer::ExportError:
754 case Buffer::ExportNoPathToFormat:
755 case Buffer::ExportTexPathHasSpaces:
756 case Buffer::ExportConverterError:
757 msg = bformat(_("Error while exporting format: %1$s"), fmt);
759 case Buffer::PreviewSuccess:
760 msg = bformat(_("Successful preview of format: %1$s"), fmt);
762 case Buffer::PreviewError:
763 msg = bformat(_("Error while previewing format: %1$s"), fmt);
765 case Buffer::ExportKilled:
766 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
773 void GuiView::processingThreadStarted()
778 void GuiView::processingThreadFinished()
780 QFutureWatcher<Buffer::ExportStatus> const * watcher =
781 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
783 Buffer::ExportStatus const status = watcher->result();
784 handleExportStatus(this, status, d.processing_format);
787 BufferView const * const bv = currentBufferView();
788 if (bv && !bv->buffer().errorList("Export").empty()) {
793 bool const error = (status != Buffer::ExportSuccess &&
794 status != Buffer::PreviewSuccess &&
795 status != Buffer::ExportCancel);
797 ErrorList & el = bv->buffer().errorList(d.last_export_format);
798 // at this point, we do not know if buffer-view or
799 // master-buffer-view was called. If there was an export error,
800 // and the current buffer's error log is empty, we guess that
801 // it must be master-buffer-view that was called so we set
803 errors(d.last_export_format, el.empty());
808 void GuiView::autoSaveThreadFinished()
810 QFutureWatcher<docstring> const * watcher =
811 static_cast<QFutureWatcher<docstring> const *>(sender());
812 message(watcher->result());
817 void GuiView::saveLayout() const
820 settings.setValue("zoom_ratio", zoom_ratio_);
821 settings.setValue("devel_mode", devel_mode_);
822 settings.beginGroup("views");
823 settings.beginGroup(QString::number(id_));
824 #if defined(Q_WS_X11) || defined(QPA_XCB)
825 settings.setValue("pos", pos());
826 settings.setValue("size", size());
828 settings.setValue("geometry", saveGeometry());
830 settings.setValue("layout", saveState(0));
831 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
835 void GuiView::saveUISettings() const
839 // Save the toolbar private states
840 ToolbarMap::iterator end = d.toolbars_.end();
841 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
842 it->second->saveSession(settings);
843 // Now take care of all other dialogs
844 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
845 for (; it!= d.dialogs_.end(); ++it)
846 it->second->saveSession(settings);
850 bool GuiView::restoreLayout()
853 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
854 // Actual zoom value: default zoom + fractional offset
855 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
856 if (zoom < static_cast<int>(zoom_min_))
858 lyxrc.currentZoom = zoom;
859 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
860 settings.beginGroup("views");
861 settings.beginGroup(QString::number(id_));
862 QString const icon_key = "icon_size";
863 if (!settings.contains(icon_key))
866 //code below is skipped when when ~/.config/LyX is (re)created
867 setIconSize(d.iconSize(settings.value(icon_key).toString()));
869 #if defined(Q_WS_X11) || defined(QPA_XCB)
870 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
871 QSize size = settings.value("size", QSize(690, 510)).toSize();
875 // Work-around for bug #6034: the window ends up in an undetermined
876 // state when trying to restore a maximized window when it is
877 // already maximized.
878 if (!(windowState() & Qt::WindowMaximized))
879 if (!restoreGeometry(settings.value("geometry").toByteArray()))
880 setGeometry(50, 50, 690, 510);
882 // Make sure layout is correctly oriented.
883 setLayoutDirection(qApp->layoutDirection());
885 // Allow the toc and view-source dock widget to be restored if needed.
887 if ((dialog = findOrBuild("toc", true)))
888 // see bug 5082. At least setup title and enabled state.
889 // Visibility will be adjusted by restoreState below.
890 dialog->prepareView();
891 if ((dialog = findOrBuild("view-source", true)))
892 dialog->prepareView();
893 if ((dialog = findOrBuild("progress", true)))
894 dialog->prepareView();
896 if (!restoreState(settings.value("layout").toByteArray(), 0))
899 // init the toolbars that have not been restored
900 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
901 Toolbars::Infos::iterator end = guiApp->toolbars().end();
902 for (; cit != end; ++cit) {
903 GuiToolbar * tb = toolbar(cit->name);
904 if (tb && !tb->isRestored())
905 initToolbar(cit->name);
908 // update lock (all) toolbars positions
909 updateLockToolbars();
916 GuiToolbar * GuiView::toolbar(string const & name)
918 ToolbarMap::iterator it = d.toolbars_.find(name);
919 if (it != d.toolbars_.end())
922 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
927 void GuiView::updateLockToolbars()
929 toolbarsMovable_ = false;
930 for (ToolbarInfo const & info : guiApp->toolbars()) {
931 GuiToolbar * tb = toolbar(info.name);
932 if (tb && tb->isMovable())
933 toolbarsMovable_ = true;
938 void GuiView::constructToolbars()
940 ToolbarMap::iterator it = d.toolbars_.begin();
941 for (; it != d.toolbars_.end(); ++it)
945 // I don't like doing this here, but the standard toolbar
946 // destroys this object when it's destroyed itself (vfr)
947 d.layout_ = new LayoutBox(*this);
948 d.stack_widget_->addWidget(d.layout_);
949 d.layout_->move(0,0);
951 // extracts the toolbars from the backend
952 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
953 Toolbars::Infos::iterator end = guiApp->toolbars().end();
954 for (; cit != end; ++cit)
955 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
959 void GuiView::initToolbars()
961 // extracts the toolbars from the backend
962 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
963 Toolbars::Infos::iterator end = guiApp->toolbars().end();
964 for (; cit != end; ++cit)
965 initToolbar(cit->name);
969 void GuiView::initToolbar(string const & name)
971 GuiToolbar * tb = toolbar(name);
974 int const visibility = guiApp->toolbars().defaultVisibility(name);
975 bool newline = !(visibility & Toolbars::SAMEROW);
976 tb->setVisible(false);
977 tb->setVisibility(visibility);
979 if (visibility & Toolbars::TOP) {
981 addToolBarBreak(Qt::TopToolBarArea);
982 addToolBar(Qt::TopToolBarArea, tb);
985 if (visibility & Toolbars::BOTTOM) {
987 addToolBarBreak(Qt::BottomToolBarArea);
988 addToolBar(Qt::BottomToolBarArea, tb);
991 if (visibility & Toolbars::LEFT) {
993 addToolBarBreak(Qt::LeftToolBarArea);
994 addToolBar(Qt::LeftToolBarArea, tb);
997 if (visibility & Toolbars::RIGHT) {
999 addToolBarBreak(Qt::RightToolBarArea);
1000 addToolBar(Qt::RightToolBarArea, tb);
1003 if (visibility & Toolbars::ON)
1004 tb->setVisible(true);
1006 tb->setMovable(true);
1010 TocModels & GuiView::tocModels()
1012 return d.toc_models_;
1016 void GuiView::setFocus()
1018 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1019 QMainWindow::setFocus();
1023 bool GuiView::hasFocus() const
1025 if (currentWorkArea())
1026 return currentWorkArea()->hasFocus();
1027 if (currentMainWorkArea())
1028 return currentMainWorkArea()->hasFocus();
1029 return d.bg_widget_->hasFocus();
1033 void GuiView::focusInEvent(QFocusEvent * e)
1035 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1036 QMainWindow::focusInEvent(e);
1037 // Make sure guiApp points to the correct view.
1038 guiApp->setCurrentView(this);
1039 if (currentWorkArea())
1040 currentWorkArea()->setFocus();
1041 else if (currentMainWorkArea())
1042 currentMainWorkArea()->setFocus();
1044 d.bg_widget_->setFocus();
1048 void GuiView::showEvent(QShowEvent * e)
1050 LYXERR(Debug::GUI, "Passed Geometry "
1051 << size().height() << "x" << size().width()
1052 << "+" << pos().x() << "+" << pos().y());
1054 if (d.splitter_->count() == 0)
1055 // No work area, switch to the background widget.
1059 QMainWindow::showEvent(e);
1063 bool GuiView::closeScheduled()
1070 bool GuiView::prepareAllBuffersForLogout()
1072 Buffer * first = theBufferList().first();
1076 // First, iterate over all buffers and ask the users if unsaved
1077 // changes should be saved.
1078 // We cannot use a for loop as the buffer list cycles.
1081 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1083 b = theBufferList().next(b);
1084 } while (b != first);
1086 // Next, save session state
1087 // When a view/window was closed before without quitting LyX, there
1088 // are already entries in the lastOpened list.
1089 theSession().lastOpened().clear();
1096 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1097 ** is responsibility of the container (e.g., dialog)
1099 void GuiView::closeEvent(QCloseEvent * close_event)
1101 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1103 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1104 Alert::warning(_("Exit LyX"),
1105 _("LyX could not be closed because documents are being processed by LyX."));
1106 close_event->setAccepted(false);
1110 // If the user pressed the x (so we didn't call closeView
1111 // programmatically), we want to clear all existing entries.
1113 theSession().lastOpened().clear();
1118 // it can happen that this event arrives without selecting the view,
1119 // e.g. when clicking the close button on a background window.
1121 if (!closeWorkAreaAll()) {
1123 close_event->ignore();
1127 // Make sure that nothing will use this to be closed View.
1128 guiApp->unregisterView(this);
1130 if (isFullScreen()) {
1131 // Switch off fullscreen before closing.
1136 // Make sure the timer time out will not trigger a statusbar update.
1137 d.statusbar_timer_.stop();
1139 // Saving fullscreen requires additional tweaks in the toolbar code.
1140 // It wouldn't also work under linux natively.
1141 if (lyxrc.allow_geometry_session) {
1146 close_event->accept();
1150 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1152 if (event->mimeData()->hasUrls())
1154 /// \todo Ask lyx-devel is this is enough:
1155 /// if (event->mimeData()->hasFormat("text/plain"))
1156 /// event->acceptProposedAction();
1160 void GuiView::dropEvent(QDropEvent * event)
1162 QList<QUrl> files = event->mimeData()->urls();
1163 if (files.isEmpty())
1166 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1167 for (int i = 0; i != files.size(); ++i) {
1168 string const file = os::internal_path(fromqstr(
1169 files.at(i).toLocalFile()));
1173 string const ext = support::getExtension(file);
1174 vector<const Format *> found_formats;
1176 // Find all formats that have the correct extension.
1177 vector<const Format *> const & import_formats
1178 = theConverters().importableFormats();
1179 vector<const Format *>::const_iterator it = import_formats.begin();
1180 for (; it != import_formats.end(); ++it)
1181 if ((*it)->hasExtension(ext))
1182 found_formats.push_back(*it);
1185 if (found_formats.size() >= 1) {
1186 if (found_formats.size() > 1) {
1187 //FIXME: show a dialog to choose the correct importable format
1188 LYXERR(Debug::FILES,
1189 "Multiple importable formats found, selecting first");
1191 string const arg = found_formats[0]->name() + " " + file;
1192 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1195 //FIXME: do we have to explicitly check whether it's a lyx file?
1196 LYXERR(Debug::FILES,
1197 "No formats found, trying to open it as a lyx file");
1198 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1200 // add the functions to the queue
1201 guiApp->addToFuncRequestQueue(cmd);
1204 // now process the collected functions. We perform the events
1205 // asynchronously. This prevents potential problems in case the
1206 // BufferView is closed within an event.
1207 guiApp->processFuncRequestQueueAsync();
1211 void GuiView::message(docstring const & str)
1213 if (ForkedProcess::iAmAChild())
1216 // call is moved to GUI-thread by GuiProgress
1217 d.progress_->appendMessage(toqstr(str));
1221 void GuiView::clearMessageText()
1223 message(docstring());
1227 void GuiView::updateStatusBarMessage(QString const & str)
1229 statusBar()->showMessage(str);
1230 d.statusbar_timer_.stop();
1231 d.statusbar_timer_.start(3000);
1235 void GuiView::clearMessage()
1237 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1238 // the hasFocus function mostly returns false, even if the focus is on
1239 // a workarea in this view.
1243 d.statusbar_timer_.stop();
1247 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1249 if (wa != d.current_work_area_
1250 || wa->bufferView().buffer().isInternal())
1252 Buffer const & buf = wa->bufferView().buffer();
1253 // Set the windows title
1254 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1255 if (buf.notifiesExternalModification()) {
1256 title = bformat(_("%1$s (modified externally)"), title);
1257 // If the external modification status has changed, then maybe the status of
1258 // buffer-save has changed too.
1262 title += from_ascii(" - LyX");
1264 setWindowTitle(toqstr(title));
1265 // Sets the path for the window: this is used by OSX to
1266 // allow a context click on the title bar showing a menu
1267 // with the path up to the file
1268 setWindowFilePath(toqstr(buf.absFileName()));
1269 // Tell Qt whether the current document is changed
1270 setWindowModified(!buf.isClean());
1272 if (buf.params().shell_escape)
1273 shell_escape_->show();
1275 shell_escape_->hide();
1277 if (buf.hasReadonlyFlag())
1282 if (buf.lyxvc().inUse()) {
1283 version_control_->show();
1284 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1286 version_control_->hide();
1290 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1292 if (d.current_work_area_)
1293 // disconnect the current work area from all slots
1294 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1296 disconnectBufferView();
1297 connectBufferView(wa->bufferView());
1298 connectBuffer(wa->bufferView().buffer());
1299 d.current_work_area_ = wa;
1300 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1301 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1302 QObject::connect(wa, SIGNAL(busy(bool)),
1303 this, SLOT(setBusy(bool)));
1304 // connection of a signal to a signal
1305 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1306 this, SIGNAL(bufferViewChanged()));
1307 Q_EMIT updateWindowTitle(wa);
1308 Q_EMIT bufferViewChanged();
1312 void GuiView::onBufferViewChanged()
1315 // Buffer-dependent dialogs must be updated. This is done here because
1316 // some dialogs require buffer()->text.
1321 void GuiView::on_lastWorkAreaRemoved()
1324 // We already are in a close event. Nothing more to do.
1327 if (d.splitter_->count() > 1)
1328 // We have a splitter so don't close anything.
1331 // Reset and updates the dialogs.
1332 Q_EMIT bufferViewChanged();
1337 if (lyxrc.open_buffers_in_tabs)
1338 // Nothing more to do, the window should stay open.
1341 if (guiApp->viewIds().size() > 1) {
1347 // On Mac we also close the last window because the application stay
1348 // resident in memory. On other platforms we don't close the last
1349 // window because this would quit the application.
1355 void GuiView::updateStatusBar()
1357 // let the user see the explicit message
1358 if (d.statusbar_timer_.isActive())
1365 void GuiView::showMessage()
1369 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1370 if (msg.isEmpty()) {
1371 BufferView const * bv = currentBufferView();
1373 msg = toqstr(bv->cursor().currentState(devel_mode_));
1375 msg = qt_("Welcome to LyX!");
1377 statusBar()->showMessage(msg);
1381 bool GuiView::event(QEvent * e)
1385 // Useful debug code:
1386 //case QEvent::ActivationChange:
1387 //case QEvent::WindowDeactivate:
1388 //case QEvent::Paint:
1389 //case QEvent::Enter:
1390 //case QEvent::Leave:
1391 //case QEvent::HoverEnter:
1392 //case QEvent::HoverLeave:
1393 //case QEvent::HoverMove:
1394 //case QEvent::StatusTip:
1395 //case QEvent::DragEnter:
1396 //case QEvent::DragLeave:
1397 //case QEvent::Drop:
1400 case QEvent::WindowStateChange: {
1401 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1402 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1403 bool result = QMainWindow::event(e);
1404 bool nfstate = (windowState() & Qt::WindowFullScreen);
1405 if (!ofstate && nfstate) {
1406 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1407 // switch to full-screen state
1408 if (lyxrc.full_screen_statusbar)
1409 statusBar()->hide();
1410 if (lyxrc.full_screen_menubar)
1412 if (lyxrc.full_screen_toolbars) {
1413 ToolbarMap::iterator end = d.toolbars_.end();
1414 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1415 if (it->second->isVisibiltyOn() && it->second->isVisible())
1418 for (int i = 0; i != d.splitter_->count(); ++i)
1419 d.tabWorkArea(i)->setFullScreen(true);
1420 setContentsMargins(-2, -2, -2, -2);
1422 hideDialogs("prefs", nullptr);
1423 } else if (ofstate && !nfstate) {
1424 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1425 // switch back from full-screen state
1426 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1427 statusBar()->show();
1428 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1430 if (lyxrc.full_screen_toolbars) {
1431 ToolbarMap::iterator end = d.toolbars_.end();
1432 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1433 if (it->second->isVisibiltyOn() && !it->second->isVisible())
1437 for (int i = 0; i != d.splitter_->count(); ++i)
1438 d.tabWorkArea(i)->setFullScreen(false);
1439 setContentsMargins(0, 0, 0, 0);
1443 case QEvent::WindowActivate: {
1444 GuiView * old_view = guiApp->currentView();
1445 if (this == old_view) {
1447 return QMainWindow::event(e);
1449 if (old_view && old_view->currentBufferView()) {
1450 // save current selection to the selection buffer to allow
1451 // middle-button paste in this window.
1452 cap::saveSelection(old_view->currentBufferView()->cursor());
1454 guiApp->setCurrentView(this);
1455 if (d.current_work_area_)
1456 on_currentWorkAreaChanged(d.current_work_area_);
1460 return QMainWindow::event(e);
1463 case QEvent::ShortcutOverride: {
1465 if (isFullScreen() && menuBar()->isHidden()) {
1466 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1467 // FIXME: we should also try to detect special LyX shortcut such as
1468 // Alt-P and Alt-M. Right now there is a hack in
1469 // GuiWorkArea::processKeySym() that hides again the menubar for
1471 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1473 return QMainWindow::event(e);
1476 return QMainWindow::event(e);
1480 return QMainWindow::event(e);
1484 void GuiView::resetWindowTitle()
1486 setWindowTitle(qt_("LyX"));
1489 bool GuiView::focusNextPrevChild(bool /*next*/)
1496 bool GuiView::busy() const
1502 void GuiView::setBusy(bool busy)
1504 bool const busy_before = busy_ > 0;
1505 busy ? ++busy_ : --busy_;
1506 if ((busy_ > 0) == busy_before)
1507 // busy state didn't change
1511 QApplication::setOverrideCursor(Qt::WaitCursor);
1514 QApplication::restoreOverrideCursor();
1519 void GuiView::resetCommandExecute()
1521 command_execute_ = false;
1526 double GuiView::pixelRatio() const
1528 #if QT_VERSION >= 0x050000
1529 return qt_scale_factor * devicePixelRatio();
1536 GuiWorkArea * GuiView::workArea(int index)
1538 if (TabWorkArea * twa = d.currentTabWorkArea())
1539 if (index < twa->count())
1540 return twa->workArea(index);
1545 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1547 if (currentWorkArea()
1548 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1549 return currentWorkArea();
1550 if (TabWorkArea * twa = d.currentTabWorkArea())
1551 return twa->workArea(buffer);
1556 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1558 // Automatically create a TabWorkArea if there are none yet.
1559 TabWorkArea * tab_widget = d.splitter_->count()
1560 ? d.currentTabWorkArea() : addTabWorkArea();
1561 return tab_widget->addWorkArea(buffer, *this);
1565 TabWorkArea * GuiView::addTabWorkArea()
1567 TabWorkArea * twa = new TabWorkArea;
1568 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1569 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1570 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1571 this, SLOT(on_lastWorkAreaRemoved()));
1573 d.splitter_->addWidget(twa);
1574 d.stack_widget_->setCurrentWidget(d.splitter_);
1579 GuiWorkArea const * GuiView::currentWorkArea() const
1581 return d.current_work_area_;
1585 GuiWorkArea * GuiView::currentWorkArea()
1587 return d.current_work_area_;
1591 GuiWorkArea const * GuiView::currentMainWorkArea() const
1593 if (!d.currentTabWorkArea())
1595 return d.currentTabWorkArea()->currentWorkArea();
1599 GuiWorkArea * GuiView::currentMainWorkArea()
1601 if (!d.currentTabWorkArea())
1603 return d.currentTabWorkArea()->currentWorkArea();
1607 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1609 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1611 d.current_work_area_ = nullptr;
1613 Q_EMIT bufferViewChanged();
1617 // FIXME: I've no clue why this is here and why it accesses
1618 // theGuiApp()->currentView, which might be 0 (bug 6464).
1619 // See also 27525 (vfr).
1620 if (theGuiApp()->currentView() == this
1621 && theGuiApp()->currentView()->currentWorkArea() == wa)
1624 if (currentBufferView())
1625 cap::saveSelection(currentBufferView()->cursor());
1627 theGuiApp()->setCurrentView(this);
1628 d.current_work_area_ = wa;
1630 // We need to reset this now, because it will need to be
1631 // right if the tabWorkArea gets reset in the for loop. We
1632 // will change it back if we aren't in that case.
1633 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1634 d.current_main_work_area_ = wa;
1636 for (int i = 0; i != d.splitter_->count(); ++i) {
1637 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1638 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1639 << ", Current main wa: " << currentMainWorkArea());
1644 d.current_main_work_area_ = old_cmwa;
1646 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1647 on_currentWorkAreaChanged(wa);
1648 BufferView & bv = wa->bufferView();
1649 bv.cursor().fixIfBroken();
1651 wa->setUpdatesEnabled(true);
1652 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1656 void GuiView::removeWorkArea(GuiWorkArea * wa)
1658 LASSERT(wa, return);
1659 if (wa == d.current_work_area_) {
1661 disconnectBufferView();
1662 d.current_work_area_ = nullptr;
1663 d.current_main_work_area_ = nullptr;
1666 bool found_twa = false;
1667 for (int i = 0; i != d.splitter_->count(); ++i) {
1668 TabWorkArea * twa = d.tabWorkArea(i);
1669 if (twa->removeWorkArea(wa)) {
1670 // Found in this tab group, and deleted the GuiWorkArea.
1672 if (twa->count() != 0) {
1673 if (d.current_work_area_ == nullptr)
1674 // This means that we are closing the current GuiWorkArea, so
1675 // switch to the next GuiWorkArea in the found TabWorkArea.
1676 setCurrentWorkArea(twa->currentWorkArea());
1678 // No more WorkAreas in this tab group, so delete it.
1685 // It is not a tabbed work area (i.e., the search work area), so it
1686 // should be deleted by other means.
1687 LASSERT(found_twa, return);
1689 if (d.current_work_area_ == nullptr) {
1690 if (d.splitter_->count() != 0) {
1691 TabWorkArea * twa = d.currentTabWorkArea();
1692 setCurrentWorkArea(twa->currentWorkArea());
1694 // No more work areas, switch to the background widget.
1695 setCurrentWorkArea(nullptr);
1701 LayoutBox * GuiView::getLayoutDialog() const
1707 void GuiView::updateLayoutList()
1710 d.layout_->updateContents(false);
1714 void GuiView::updateToolbars()
1716 ToolbarMap::iterator end = d.toolbars_.end();
1717 if (d.current_work_area_) {
1719 if (d.current_work_area_->bufferView().cursor().inMathed()
1720 && !d.current_work_area_->bufferView().cursor().inRegexped())
1721 context |= Toolbars::MATH;
1722 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1723 context |= Toolbars::TABLE;
1724 if (currentBufferView()->buffer().areChangesPresent()
1725 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1726 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1727 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1728 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1729 context |= Toolbars::REVIEW;
1730 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1731 context |= Toolbars::MATHMACROTEMPLATE;
1732 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1733 context |= Toolbars::IPA;
1734 if (command_execute_)
1735 context |= Toolbars::MINIBUFFER;
1736 if (minibuffer_focus_) {
1737 context |= Toolbars::MINIBUFFER_FOCUS;
1738 minibuffer_focus_ = false;
1741 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1742 it->second->update(context);
1744 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1745 it->second->update();
1749 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1751 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1752 LASSERT(newBuffer, return);
1754 GuiWorkArea * wa = workArea(*newBuffer);
1755 if (wa == nullptr) {
1757 newBuffer->masterBuffer()->updateBuffer();
1759 wa = addWorkArea(*newBuffer);
1760 // scroll to the position when the BufferView was last closed
1761 if (lyxrc.use_lastfilepos) {
1762 LastFilePosSection::FilePos filepos =
1763 theSession().lastFilePos().load(newBuffer->fileName());
1764 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1767 //Disconnect the old buffer...there's no new one.
1770 connectBuffer(*newBuffer);
1771 connectBufferView(wa->bufferView());
1773 setCurrentWorkArea(wa);
1777 void GuiView::connectBuffer(Buffer & buf)
1779 buf.setGuiDelegate(this);
1783 void GuiView::disconnectBuffer()
1785 if (d.current_work_area_)
1786 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1790 void GuiView::connectBufferView(BufferView & bv)
1792 bv.setGuiDelegate(this);
1796 void GuiView::disconnectBufferView()
1798 if (d.current_work_area_)
1799 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1803 void GuiView::errors(string const & error_type, bool from_master)
1805 BufferView const * const bv = currentBufferView();
1809 ErrorList const & el = from_master ?
1810 bv->buffer().masterBuffer()->errorList(error_type) :
1811 bv->buffer().errorList(error_type);
1816 string err = error_type;
1818 err = "from_master|" + error_type;
1819 showDialog("errorlist", err);
1823 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1825 d.toc_models_.updateItem(toqstr(type), dit);
1829 void GuiView::structureChanged()
1831 // This is called from the Buffer, which has no way to ensure that cursors
1832 // in BufferView remain valid.
1833 if (documentBufferView())
1834 documentBufferView()->cursor().sanitize();
1835 // FIXME: This is slightly expensive, though less than the tocBackend update
1836 // (#9880). This also resets the view in the Toc Widget (#6675).
1837 d.toc_models_.reset(documentBufferView());
1838 // Navigator needs more than a simple update in this case. It needs to be
1840 updateDialog("toc", "");
1844 void GuiView::updateDialog(string const & name, string const & sdata)
1846 if (!isDialogVisible(name))
1849 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1850 if (it == d.dialogs_.end())
1853 Dialog * const dialog = it->second.get();
1854 if (dialog->isVisibleView())
1855 dialog->initialiseParams(sdata);
1859 BufferView * GuiView::documentBufferView()
1861 return currentMainWorkArea()
1862 ? ¤tMainWorkArea()->bufferView()
1867 BufferView const * GuiView::documentBufferView() const
1869 return currentMainWorkArea()
1870 ? ¤tMainWorkArea()->bufferView()
1875 BufferView * GuiView::currentBufferView()
1877 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1881 BufferView const * GuiView::currentBufferView() const
1883 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1887 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1888 Buffer const * orig, Buffer * clone)
1890 bool const success = clone->autoSave();
1892 busyBuffers.remove(orig);
1894 ? _("Automatic save done.")
1895 : _("Automatic save failed!");
1899 void GuiView::autoSave()
1901 LYXERR(Debug::INFO, "Running autoSave()");
1903 Buffer * buffer = documentBufferView()
1904 ? &documentBufferView()->buffer() : nullptr;
1906 resetAutosaveTimers();
1910 GuiViewPrivate::busyBuffers.insert(buffer);
1911 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1912 buffer, buffer->cloneBufferOnly());
1913 d.autosave_watcher_.setFuture(f);
1914 resetAutosaveTimers();
1918 void GuiView::resetAutosaveTimers()
1921 d.autosave_timeout_.restart();
1925 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1928 Buffer * buf = currentBufferView()
1929 ? ¤tBufferView()->buffer() : nullptr;
1930 Buffer * doc_buffer = documentBufferView()
1931 ? &(documentBufferView()->buffer()) : nullptr;
1934 /* In LyX/Mac, when a dialog is open, the menus of the
1935 application can still be accessed without giving focus to
1936 the main window. In this case, we want to disable the menu
1937 entries that are buffer-related.
1938 This code must not be used on Linux and Windows, since it
1939 would disable buffer-related entries when hovering over the
1940 menu (see bug #9574).
1942 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1948 // Check whether we need a buffer
1949 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1950 // no, exit directly
1951 flag.message(from_utf8(N_("Command not allowed with"
1952 "out any document open")));
1953 flag.setEnabled(false);
1957 if (cmd.origin() == FuncRequest::TOC) {
1958 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1959 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1960 flag.setEnabled(false);
1964 switch(cmd.action()) {
1965 case LFUN_BUFFER_IMPORT:
1968 case LFUN_MASTER_BUFFER_EXPORT:
1970 && (doc_buffer->parent() != nullptr
1971 || doc_buffer->hasChildren())
1972 && !d.processing_thread_watcher_.isRunning()
1973 // this launches a dialog, which would be in the wrong Buffer
1974 && !(::lyx::operator==(cmd.argument(), "custom"));
1977 case LFUN_MASTER_BUFFER_UPDATE:
1978 case LFUN_MASTER_BUFFER_VIEW:
1980 && (doc_buffer->parent() != nullptr
1981 || doc_buffer->hasChildren())
1982 && !d.processing_thread_watcher_.isRunning();
1985 case LFUN_BUFFER_UPDATE:
1986 case LFUN_BUFFER_VIEW: {
1987 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1991 string format = to_utf8(cmd.argument());
1992 if (cmd.argument().empty())
1993 format = doc_buffer->params().getDefaultOutputFormat();
1994 enable = doc_buffer->params().isExportable(format, true);
1998 case LFUN_BUFFER_RELOAD:
1999 enable = doc_buffer && !doc_buffer->isUnnamed()
2000 && doc_buffer->fileName().exists()
2001 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2004 case LFUN_BUFFER_RESET_EXPORT:
2005 enable = doc_buffer != nullptr;
2008 case LFUN_BUFFER_CHILD_OPEN:
2009 enable = doc_buffer != nullptr;
2012 case LFUN_MASTER_BUFFER_FORALL: {
2013 if (doc_buffer == nullptr) {
2014 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2018 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2019 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2020 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2025 for (Buffer * buf : doc_buffer->allRelatives()) {
2026 GuiWorkArea * wa = workArea(*buf);
2029 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2030 enable = flag.enabled();
2037 case LFUN_BUFFER_WRITE:
2038 enable = doc_buffer && (doc_buffer->isUnnamed()
2039 || (!doc_buffer->isClean()
2040 || cmd.argument() == "force"));
2043 //FIXME: This LFUN should be moved to GuiApplication.
2044 case LFUN_BUFFER_WRITE_ALL: {
2045 // We enable the command only if there are some modified buffers
2046 Buffer * first = theBufferList().first();
2051 // We cannot use a for loop as the buffer list is a cycle.
2053 if (!b->isClean()) {
2057 b = theBufferList().next(b);
2058 } while (b != first);
2062 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2063 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2066 case LFUN_BUFFER_EXPORT: {
2067 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2071 return doc_buffer->getStatus(cmd, flag);
2074 case LFUN_BUFFER_EXPORT_AS:
2075 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2080 case LFUN_BUFFER_WRITE_AS:
2081 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2082 enable = doc_buffer != nullptr;
2085 case LFUN_EXPORT_CANCEL:
2086 enable = d.processing_thread_watcher_.isRunning();
2089 case LFUN_BUFFER_CLOSE:
2090 case LFUN_VIEW_CLOSE:
2091 enable = doc_buffer != nullptr;
2094 case LFUN_BUFFER_CLOSE_ALL:
2095 enable = theBufferList().last() != theBufferList().first();
2098 case LFUN_BUFFER_CHKTEX: {
2099 // hide if we have no checktex command
2100 if (lyxrc.chktex_command.empty()) {
2101 flag.setUnknown(true);
2105 if (!doc_buffer || !doc_buffer->params().isLatex()
2106 || d.processing_thread_watcher_.isRunning()) {
2107 // grey out, don't hide
2115 case LFUN_VIEW_SPLIT:
2116 if (cmd.getArg(0) == "vertical")
2117 enable = doc_buffer && (d.splitter_->count() == 1 ||
2118 d.splitter_->orientation() == Qt::Vertical);
2120 enable = doc_buffer && (d.splitter_->count() == 1 ||
2121 d.splitter_->orientation() == Qt::Horizontal);
2124 case LFUN_TAB_GROUP_CLOSE:
2125 enable = d.tabWorkAreaCount() > 1;
2128 case LFUN_DEVEL_MODE_TOGGLE:
2129 flag.setOnOff(devel_mode_);
2132 case LFUN_TOOLBAR_TOGGLE: {
2133 string const name = cmd.getArg(0);
2134 if (GuiToolbar * t = toolbar(name))
2135 flag.setOnOff(t->isVisible());
2138 docstring const msg =
2139 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2145 case LFUN_TOOLBAR_MOVABLE: {
2146 string const name = cmd.getArg(0);
2147 // use negation since locked == !movable
2149 // toolbar name * locks all toolbars
2150 flag.setOnOff(!toolbarsMovable_);
2151 else if (GuiToolbar * t = toolbar(name))
2152 flag.setOnOff(!(t->isMovable()));
2155 docstring const msg =
2156 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2162 case LFUN_ICON_SIZE:
2163 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2166 case LFUN_DROP_LAYOUTS_CHOICE:
2167 enable = buf != nullptr;
2170 case LFUN_UI_TOGGLE:
2171 flag.setOnOff(isFullScreen());
2174 case LFUN_DIALOG_DISCONNECT_INSET:
2177 case LFUN_DIALOG_HIDE:
2178 // FIXME: should we check if the dialog is shown?
2181 case LFUN_DIALOG_TOGGLE:
2182 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2185 case LFUN_DIALOG_SHOW: {
2186 string const name = cmd.getArg(0);
2188 enable = name == "aboutlyx"
2189 || name == "file" //FIXME: should be removed.
2190 || name == "lyxfiles"
2192 || name == "texinfo"
2193 || name == "progress"
2194 || name == "compare";
2195 else if (name == "character" || name == "symbols"
2196 || name == "mathdelimiter" || name == "mathmatrix") {
2197 if (!buf || buf->isReadonly())
2200 Cursor const & cur = currentBufferView()->cursor();
2201 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2204 else if (name == "latexlog")
2205 enable = FileName(doc_buffer->logName()).isReadableFile();
2206 else if (name == "spellchecker")
2207 enable = theSpellChecker()
2208 && !doc_buffer->isReadonly()
2209 && !doc_buffer->text().empty();
2210 else if (name == "vclog")
2211 enable = doc_buffer->lyxvc().inUse();
2215 case LFUN_DIALOG_UPDATE: {
2216 string const name = cmd.getArg(0);
2218 enable = name == "prefs";
2222 case LFUN_COMMAND_EXECUTE:
2224 case LFUN_MENU_OPEN:
2225 // Nothing to check.
2228 case LFUN_COMPLETION_INLINE:
2229 if (!d.current_work_area_
2230 || !d.current_work_area_->completer().inlinePossible(
2231 currentBufferView()->cursor()))
2235 case LFUN_COMPLETION_POPUP:
2236 if (!d.current_work_area_
2237 || !d.current_work_area_->completer().popupPossible(
2238 currentBufferView()->cursor()))
2243 if (!d.current_work_area_
2244 || !d.current_work_area_->completer().inlinePossible(
2245 currentBufferView()->cursor()))
2249 case LFUN_COMPLETION_ACCEPT:
2250 if (!d.current_work_area_
2251 || (!d.current_work_area_->completer().popupVisible()
2252 && !d.current_work_area_->completer().inlineVisible()
2253 && !d.current_work_area_->completer().completionAvailable()))
2257 case LFUN_COMPLETION_CANCEL:
2258 if (!d.current_work_area_
2259 || (!d.current_work_area_->completer().popupVisible()
2260 && !d.current_work_area_->completer().inlineVisible()))
2264 case LFUN_BUFFER_ZOOM_OUT:
2265 case LFUN_BUFFER_ZOOM_IN: {
2266 // only diff between these two is that the default for ZOOM_OUT
2268 bool const neg_zoom =
2269 convert<int>(cmd.argument()) < 0 ||
2270 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2271 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2272 docstring const msg =
2273 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2277 enable = doc_buffer;
2281 case LFUN_BUFFER_ZOOM: {
2282 bool const less_than_min_zoom =
2283 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2284 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2285 docstring const msg =
2286 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2291 enable = doc_buffer;
2295 case LFUN_BUFFER_MOVE_NEXT:
2296 case LFUN_BUFFER_MOVE_PREVIOUS:
2297 // we do not cycle when moving
2298 case LFUN_BUFFER_NEXT:
2299 case LFUN_BUFFER_PREVIOUS:
2300 // because we cycle, it doesn't matter whether on first or last
2301 enable = (d.currentTabWorkArea()->count() > 1);
2303 case LFUN_BUFFER_SWITCH:
2304 // toggle on the current buffer, but do not toggle off
2305 // the other ones (is that a good idea?)
2307 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2308 flag.setOnOff(true);
2311 case LFUN_VC_REGISTER:
2312 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2314 case LFUN_VC_RENAME:
2315 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2318 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2320 case LFUN_VC_CHECK_IN:
2321 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2323 case LFUN_VC_CHECK_OUT:
2324 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2326 case LFUN_VC_LOCKING_TOGGLE:
2327 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2328 && doc_buffer->lyxvc().lockingToggleEnabled();
2329 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2331 case LFUN_VC_REVERT:
2332 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2333 && !doc_buffer->hasReadonlyFlag();
2335 case LFUN_VC_UNDO_LAST:
2336 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2338 case LFUN_VC_REPO_UPDATE:
2339 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2341 case LFUN_VC_COMMAND: {
2342 if (cmd.argument().empty())
2344 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2348 case LFUN_VC_COMPARE:
2349 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2352 case LFUN_SERVER_GOTO_FILE_ROW:
2353 case LFUN_LYX_ACTIVATE:
2354 case LFUN_WINDOW_RAISE:
2356 case LFUN_FORWARD_SEARCH:
2357 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2360 case LFUN_FILE_INSERT_PLAINTEXT:
2361 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2362 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2365 case LFUN_SPELLING_CONTINUOUSLY:
2366 flag.setOnOff(lyxrc.spellcheck_continuously);
2369 case LFUN_CITATION_OPEN:
2378 flag.setEnabled(false);
2384 static FileName selectTemplateFile()
2386 FileDialog dlg(qt_("Select template file"));
2387 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2388 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2390 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2391 QStringList(qt_("LyX Documents (*.lyx)")));
2393 if (result.first == FileDialog::Later)
2395 if (result.second.isEmpty())
2397 return FileName(fromqstr(result.second));
2401 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2405 Buffer * newBuffer = nullptr;
2407 newBuffer = checkAndLoadLyXFile(filename);
2408 } catch (ExceptionMessage const & e) {
2415 message(_("Document not loaded."));
2419 setBuffer(newBuffer);
2420 newBuffer->errors("Parse");
2423 theSession().lastFiles().add(filename);
2424 theSession().writeFile();
2431 void GuiView::openDocument(string const & fname)
2433 string initpath = lyxrc.document_path;
2435 if (documentBufferView()) {
2436 string const trypath = documentBufferView()->buffer().filePath();
2437 // If directory is writeable, use this as default.
2438 if (FileName(trypath).isDirWritable())
2444 if (fname.empty()) {
2445 FileDialog dlg(qt_("Select document to open"));
2446 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2447 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2449 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2450 FileDialog::Result result =
2451 dlg.open(toqstr(initpath), filter);
2453 if (result.first == FileDialog::Later)
2456 filename = fromqstr(result.second);
2458 // check selected filename
2459 if (filename.empty()) {
2460 message(_("Canceled."));
2466 // get absolute path of file and add ".lyx" to the filename if
2468 FileName const fullname =
2469 fileSearch(string(), filename, "lyx", support::may_not_exist);
2470 if (!fullname.empty())
2471 filename = fullname.absFileName();
2473 if (!fullname.onlyPath().isDirectory()) {
2474 Alert::warning(_("Invalid filename"),
2475 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2476 from_utf8(fullname.absFileName())));
2480 // if the file doesn't exist and isn't already open (bug 6645),
2481 // let the user create one
2482 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2483 !LyXVC::file_not_found_hook(fullname)) {
2484 // the user specifically chose this name. Believe him.
2485 Buffer * const b = newFile(filename, string(), true);
2491 docstring const disp_fn = makeDisplayPath(filename);
2492 message(bformat(_("Opening document %1$s..."), disp_fn));
2495 Buffer * buf = loadDocument(fullname);
2497 str2 = bformat(_("Document %1$s opened."), disp_fn);
2498 if (buf->lyxvc().inUse())
2499 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2500 " " + _("Version control detected.");
2502 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2507 // FIXME: clean that
2508 static bool import(GuiView * lv, FileName const & filename,
2509 string const & format, ErrorList & errorList)
2511 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2513 string loader_format;
2514 vector<string> loaders = theConverters().loaders();
2515 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2516 vector<string>::const_iterator it = loaders.begin();
2517 vector<string>::const_iterator en = loaders.end();
2518 for (; it != en; ++it) {
2519 if (!theConverters().isReachable(format, *it))
2522 string const tofile =
2523 support::changeExtension(filename.absFileName(),
2524 theFormats().extension(*it));
2525 if (theConverters().convert(nullptr, filename, FileName(tofile),
2526 filename, format, *it, errorList) != Converters::SUCCESS)
2528 loader_format = *it;
2531 if (loader_format.empty()) {
2532 frontend::Alert::error(_("Couldn't import file"),
2533 bformat(_("No information for importing the format %1$s."),
2534 translateIfPossible(theFormats().prettyName(format))));
2538 loader_format = format;
2540 if (loader_format == "lyx") {
2541 Buffer * buf = lv->loadDocument(lyxfile);
2545 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2549 bool as_paragraphs = loader_format == "textparagraph";
2550 string filename2 = (loader_format == format) ? filename.absFileName()
2551 : support::changeExtension(filename.absFileName(),
2552 theFormats().extension(loader_format));
2553 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2555 guiApp->setCurrentView(lv);
2556 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2563 void GuiView::importDocument(string const & argument)
2566 string filename = split(argument, format, ' ');
2568 LYXERR(Debug::INFO, format << " file: " << filename);
2570 // need user interaction
2571 if (filename.empty()) {
2572 string initpath = lyxrc.document_path;
2573 if (documentBufferView()) {
2574 string const trypath = documentBufferView()->buffer().filePath();
2575 // If directory is writeable, use this as default.
2576 if (FileName(trypath).isDirWritable())
2580 docstring const text = bformat(_("Select %1$s file to import"),
2581 translateIfPossible(theFormats().prettyName(format)));
2583 FileDialog dlg(toqstr(text));
2584 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2585 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2587 docstring filter = translateIfPossible(theFormats().prettyName(format));
2590 filter += from_utf8(theFormats().extensions(format));
2593 FileDialog::Result result =
2594 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2596 if (result.first == FileDialog::Later)
2599 filename = fromqstr(result.second);
2601 // check selected filename
2602 if (filename.empty())
2603 message(_("Canceled."));
2606 if (filename.empty())
2609 // get absolute path of file
2610 FileName const fullname(support::makeAbsPath(filename));
2612 // Can happen if the user entered a path into the dialog
2614 if (fullname.onlyFileName().empty()) {
2615 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2616 "Aborting import."),
2617 from_utf8(fullname.absFileName()));
2618 frontend::Alert::error(_("File name error"), msg);
2619 message(_("Canceled."));
2624 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2626 // Check if the document already is open
2627 Buffer * buf = theBufferList().getBuffer(lyxfile);
2630 if (!closeBuffer()) {
2631 message(_("Canceled."));
2636 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2638 // if the file exists already, and we didn't do
2639 // -i lyx thefile.lyx, warn
2640 if (lyxfile.exists() && fullname != lyxfile) {
2642 docstring text = bformat(_("The document %1$s already exists.\n\n"
2643 "Do you want to overwrite that document?"), displaypath);
2644 int const ret = Alert::prompt(_("Overwrite document?"),
2645 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2648 message(_("Canceled."));
2653 message(bformat(_("Importing %1$s..."), displaypath));
2654 ErrorList errorList;
2655 if (import(this, fullname, format, errorList))
2656 message(_("imported."));
2658 message(_("file not imported!"));
2660 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2664 void GuiView::newDocument(string const & filename, string templatefile,
2667 FileName initpath(lyxrc.document_path);
2668 if (documentBufferView()) {
2669 FileName const trypath(documentBufferView()->buffer().filePath());
2670 // If directory is writeable, use this as default.
2671 if (trypath.isDirWritable())
2675 if (from_template) {
2676 if (templatefile.empty())
2677 templatefile = selectTemplateFile().absFileName();
2678 if (templatefile.empty())
2683 if (filename.empty())
2684 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2686 b = newFile(filename, templatefile, true);
2691 // If no new document could be created, it is unsure
2692 // whether there is a valid BufferView.
2693 if (currentBufferView())
2694 // Ensure the cursor is correctly positioned on screen.
2695 currentBufferView()->showCursor();
2699 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2701 BufferView * bv = documentBufferView();
2706 FileName filename(to_utf8(fname));
2707 if (filename.empty()) {
2708 // Launch a file browser
2710 string initpath = lyxrc.document_path;
2711 string const trypath = bv->buffer().filePath();
2712 // If directory is writeable, use this as default.
2713 if (FileName(trypath).isDirWritable())
2717 FileDialog dlg(qt_("Select LyX document to insert"));
2718 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2719 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2721 FileDialog::Result result = dlg.open(toqstr(initpath),
2722 QStringList(qt_("LyX Documents (*.lyx)")));
2724 if (result.first == FileDialog::Later)
2728 filename.set(fromqstr(result.second));
2730 // check selected filename
2731 if (filename.empty()) {
2732 // emit message signal.
2733 message(_("Canceled."));
2738 bv->insertLyXFile(filename, ignorelang);
2739 bv->buffer().errors("Parse");
2743 string const GuiView::getTemplatesPath(Buffer & b)
2745 // We start off with the user's templates path
2746 string result = addPath(package().user_support().absFileName(), "templates");
2747 // Check for the document language
2748 string const langcode = b.params().language->code();
2749 string const shortcode = langcode.substr(0, 2);
2750 if (!langcode.empty() && shortcode != "en") {
2751 string subpath = addPath(result, shortcode);
2752 string subpath_long = addPath(result, langcode);
2753 // If we have a subdirectory for the language already,
2755 FileName sp = FileName(subpath);
2756 if (sp.isDirectory())
2758 else if (FileName(subpath_long).isDirectory())
2759 result = subpath_long;
2761 // Ask whether we should create such a subdirectory
2762 docstring const text =
2763 bformat(_("It is suggested to save the template in a subdirectory\n"
2764 "appropriate to the document language (%1$s).\n"
2765 "This subdirectory does not exists yet.\n"
2766 "Do you want to create it?"),
2767 _(b.params().language->display()));
2768 if (Alert::prompt(_("Create Language Directory?"),
2769 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2770 // If the user agreed, we try to create it and report if this failed.
2771 if (!sp.createDirectory(0777))
2772 Alert::error(_("Subdirectory creation failed!"),
2773 _("Could not create subdirectory.\n"
2774 "The template will be saved in the parent directory."));
2780 // Do we have a layout category?
2781 string const cat = b.params().baseClass() ?
2782 b.params().baseClass()->category()
2785 string subpath = addPath(result, cat);
2786 // If we have a subdirectory for the category already,
2788 FileName sp = FileName(subpath);
2789 if (sp.isDirectory())
2792 // Ask whether we should create such a subdirectory
2793 docstring const text =
2794 bformat(_("It is suggested to save the template in a subdirectory\n"
2795 "appropriate to the layout category (%1$s).\n"
2796 "This subdirectory does not exists yet.\n"
2797 "Do you want to create it?"),
2799 if (Alert::prompt(_("Create Category Directory?"),
2800 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2801 // If the user agreed, we try to create it and report if this failed.
2802 if (!sp.createDirectory(0777))
2803 Alert::error(_("Subdirectory creation failed!"),
2804 _("Could not create subdirectory.\n"
2805 "The template will be saved in the parent directory."));
2815 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2817 FileName fname = b.fileName();
2818 FileName const oldname = fname;
2819 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2821 if (!newname.empty()) {
2824 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2826 fname = support::makeAbsPath(to_utf8(newname),
2827 oldname.onlyPath().absFileName());
2829 // Switch to this Buffer.
2832 // No argument? Ask user through dialog.
2834 QString const title = as_template ? qt_("Choose a filename to save template as")
2835 : qt_("Choose a filename to save document as");
2836 FileDialog dlg(title);
2837 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2838 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2840 if (!isLyXFileName(fname.absFileName()))
2841 fname.changeExtension(".lyx");
2843 string const path = as_template ?
2845 : fname.onlyPath().absFileName();
2846 FileDialog::Result result =
2847 dlg.save(toqstr(path),
2848 QStringList(qt_("LyX Documents (*.lyx)")),
2849 toqstr(fname.onlyFileName()));
2851 if (result.first == FileDialog::Later)
2854 fname.set(fromqstr(result.second));
2859 if (!isLyXFileName(fname.absFileName()))
2860 fname.changeExtension(".lyx");
2863 // fname is now the new Buffer location.
2865 // if there is already a Buffer open with this name, we do not want
2866 // to have another one. (the second test makes sure we're not just
2867 // trying to overwrite ourselves, which is fine.)
2868 if (theBufferList().exists(fname) && fname != oldname
2869 && theBufferList().getBuffer(fname) != &b) {
2870 docstring const text =
2871 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2872 "Please close it before attempting to overwrite it.\n"
2873 "Do you want to choose a new filename?"),
2874 from_utf8(fname.absFileName()));
2875 int const ret = Alert::prompt(_("Chosen File Already Open"),
2876 text, 0, 1, _("&Rename"), _("&Cancel"));
2878 case 0: return renameBuffer(b, docstring(), kind);
2879 case 1: return false;
2884 bool const existsLocal = fname.exists();
2885 bool const existsInVC = LyXVC::fileInVC(fname);
2886 if (existsLocal || existsInVC) {
2887 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2888 if (kind != LV_WRITE_AS && existsInVC) {
2889 // renaming to a name that is already in VC
2891 docstring text = bformat(_("The document %1$s "
2892 "is already registered.\n\n"
2893 "Do you want to choose a new name?"),
2895 docstring const title = (kind == LV_VC_RENAME) ?
2896 _("Rename document?") : _("Copy document?");
2897 docstring const button = (kind == LV_VC_RENAME) ?
2898 _("&Rename") : _("&Copy");
2899 int const ret = Alert::prompt(title, text, 0, 1,
2900 button, _("&Cancel"));
2902 case 0: return renameBuffer(b, docstring(), kind);
2903 case 1: return false;
2908 docstring text = bformat(_("The document %1$s "
2909 "already exists.\n\n"
2910 "Do you want to overwrite that document?"),
2912 int const ret = Alert::prompt(_("Overwrite document?"),
2913 text, 0, 2, _("&Overwrite"),
2914 _("&Rename"), _("&Cancel"));
2917 case 1: return renameBuffer(b, docstring(), kind);
2918 case 2: return false;
2924 case LV_VC_RENAME: {
2925 string msg = b.lyxvc().rename(fname);
2928 message(from_utf8(msg));
2932 string msg = b.lyxvc().copy(fname);
2935 message(from_utf8(msg));
2939 case LV_WRITE_AS_TEMPLATE:
2942 // LyXVC created the file already in case of LV_VC_RENAME or
2943 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2944 // relative paths of included stuff right if we moved e.g. from
2945 // /a/b.lyx to /a/c/b.lyx.
2947 bool const saved = saveBuffer(b, fname);
2954 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2956 FileName fname = b.fileName();
2958 FileDialog dlg(qt_("Choose a filename to export the document as"));
2959 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2962 QString const anyformat = qt_("Guess from extension (*.*)");
2965 vector<Format const *> export_formats;
2966 for (Format const & f : theFormats())
2967 if (f.documentFormat())
2968 export_formats.push_back(&f);
2969 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2970 map<QString, string> fmap;
2973 for (Format const * f : export_formats) {
2974 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2975 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2977 from_ascii(f->extension())));
2978 types << loc_filter;
2979 fmap[loc_filter] = f->name();
2980 if (from_ascii(f->name()) == iformat) {
2981 filter = loc_filter;
2982 ext = f->extension();
2985 string ofname = fname.onlyFileName();
2987 ofname = support::changeExtension(ofname, ext);
2988 FileDialog::Result result =
2989 dlg.save(toqstr(fname.onlyPath().absFileName()),
2993 if (result.first != FileDialog::Chosen)
2997 fname.set(fromqstr(result.second));
2998 if (filter == anyformat)
2999 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3001 fmt_name = fmap[filter];
3002 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3003 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3005 if (fmt_name.empty() || fname.empty())
3008 // fname is now the new Buffer location.
3009 if (FileName(fname).exists()) {
3010 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3011 docstring text = bformat(_("The document %1$s already "
3012 "exists.\n\nDo you want to "
3013 "overwrite that document?"),
3015 int const ret = Alert::prompt(_("Overwrite document?"),
3016 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3019 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3020 case 2: return false;
3024 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3027 return dr.dispatched();
3031 bool GuiView::saveBuffer(Buffer & b)
3033 return saveBuffer(b, FileName());
3037 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3039 if (workArea(b) && workArea(b)->inDialogMode())
3042 if (fn.empty() && b.isUnnamed())
3043 return renameBuffer(b, docstring());
3045 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3047 theSession().lastFiles().add(b.fileName());
3048 theSession().writeFile();
3052 // Switch to this Buffer.
3055 // FIXME: we don't tell the user *WHY* the save failed !!
3056 docstring const file = makeDisplayPath(b.absFileName(), 30);
3057 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3058 "Do you want to rename the document and "
3059 "try again?"), file);
3060 int const ret = Alert::prompt(_("Rename and save?"),
3061 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3064 if (!renameBuffer(b, docstring()))
3073 return saveBuffer(b, fn);
3077 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3079 return closeWorkArea(wa, false);
3083 // We only want to close the buffer if it is not visible in other workareas
3084 // of the same view, nor in other views, and if this is not a child
3085 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3087 Buffer & buf = wa->bufferView().buffer();
3089 bool last_wa = d.countWorkAreasOf(buf) == 1
3090 && !inOtherView(buf) && !buf.parent();
3092 bool close_buffer = last_wa;
3095 if (lyxrc.close_buffer_with_last_view == "yes")
3097 else if (lyxrc.close_buffer_with_last_view == "no")
3098 close_buffer = false;
3101 if (buf.isUnnamed())
3102 file = from_utf8(buf.fileName().onlyFileName());
3104 file = buf.fileName().displayName(30);
3105 docstring const text = bformat(
3106 _("Last view on document %1$s is being closed.\n"
3107 "Would you like to close or hide the document?\n"
3109 "Hidden documents can be displayed back through\n"
3110 "the menu: View->Hidden->...\n"
3112 "To remove this question, set your preference in:\n"
3113 " Tools->Preferences->Look&Feel->UserInterface\n"
3115 int ret = Alert::prompt(_("Close or hide document?"),
3116 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3119 close_buffer = (ret == 0);
3123 return closeWorkArea(wa, close_buffer);
3127 bool GuiView::closeBuffer()
3129 GuiWorkArea * wa = currentMainWorkArea();
3130 // coverity complained about this
3131 // it seems unnecessary, but perhaps is worth the check
3132 LASSERT(wa, return false);
3134 setCurrentWorkArea(wa);
3135 Buffer & buf = wa->bufferView().buffer();
3136 return closeWorkArea(wa, !buf.parent());
3140 void GuiView::writeSession() const {
3141 GuiWorkArea const * active_wa = currentMainWorkArea();
3142 for (int i = 0; i < d.splitter_->count(); ++i) {
3143 TabWorkArea * twa = d.tabWorkArea(i);
3144 for (int j = 0; j < twa->count(); ++j) {
3145 GuiWorkArea * wa = twa->workArea(j);
3146 Buffer & buf = wa->bufferView().buffer();
3147 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3153 bool GuiView::closeBufferAll()
3156 for (auto & buf : theBufferList()) {
3157 if (!saveBufferIfNeeded(*buf, false)) {
3158 // Closing has been cancelled, so abort.
3163 // Close the workareas in all other views
3164 QList<int> const ids = guiApp->viewIds();
3165 for (int i = 0; i != ids.size(); ++i) {
3166 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3170 // Close our own workareas
3171 if (!closeWorkAreaAll())
3178 bool GuiView::closeWorkAreaAll()
3180 setCurrentWorkArea(currentMainWorkArea());
3182 // We might be in a situation that there is still a tabWorkArea, but
3183 // there are no tabs anymore. This can happen when we get here after a
3184 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3185 // many TabWorkArea's have no documents anymore.
3188 // We have to call count() each time, because it can happen that
3189 // more than one splitter will disappear in one iteration (bug 5998).
3190 while (d.splitter_->count() > empty_twa) {
3191 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3193 if (twa->count() == 0)
3196 setCurrentWorkArea(twa->currentWorkArea());
3197 if (!closeTabWorkArea(twa))
3205 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3210 Buffer & buf = wa->bufferView().buffer();
3212 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3213 Alert::warning(_("Close document"),
3214 _("Document could not be closed because it is being processed by LyX."));
3219 return closeBuffer(buf);
3221 if (!inMultiTabs(wa))
3222 if (!saveBufferIfNeeded(buf, true))
3230 bool GuiView::closeBuffer(Buffer & buf)
3232 bool success = true;
3233 ListOfBuffers clist = buf.getChildren();
3234 ListOfBuffers::const_iterator it = clist.begin();
3235 ListOfBuffers::const_iterator const bend = clist.end();
3236 for (; it != bend; ++it) {
3237 Buffer * child_buf = *it;
3238 if (theBufferList().isOthersChild(&buf, child_buf)) {
3239 child_buf->setParent(nullptr);
3243 // FIXME: should we look in other tabworkareas?
3244 // ANSWER: I don't think so. I've tested, and if the child is
3245 // open in some other window, it closes without a problem.
3246 GuiWorkArea * child_wa = workArea(*child_buf);
3249 // If we are in a close_event all children will be closed in some time,
3250 // so no need to do it here. This will ensure that the children end up
3251 // in the session file in the correct order. If we close the master
3252 // buffer, we can close or release the child buffers here too.
3254 success = closeWorkArea(child_wa, true);
3258 // In this case the child buffer is open but hidden.
3259 // Even in this case, children can be dirty (e.g.,
3260 // after a label change in the master, see #11405).
3261 // Therefore, check this
3262 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3263 // If we are in a close_event all children will be closed in some time,
3264 // so no need to do it here. This will ensure that the children end up
3265 // in the session file in the correct order. If we close the master
3266 // buffer, we can close or release the child buffers here too.
3269 // Save dirty buffers also if closing_!
3270 if (saveBufferIfNeeded(*child_buf, false)) {
3271 child_buf->removeAutosaveFile();
3272 theBufferList().release(child_buf);
3274 // Saving of dirty children has been cancelled.
3275 // Cancel the whole process.
3282 // goto bookmark to update bookmark pit.
3283 // FIXME: we should update only the bookmarks related to this buffer!
3284 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3285 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3286 guiApp->gotoBookmark(i + 1, false, false);
3288 if (saveBufferIfNeeded(buf, false)) {
3289 buf.removeAutosaveFile();
3290 theBufferList().release(&buf);
3294 // open all children again to avoid a crash because of dangling
3295 // pointers (bug 6603)
3301 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3303 while (twa == d.currentTabWorkArea()) {
3304 twa->setCurrentIndex(twa->count() - 1);
3306 GuiWorkArea * wa = twa->currentWorkArea();
3307 Buffer & b = wa->bufferView().buffer();
3309 // We only want to close the buffer if the same buffer is not visible
3310 // in another view, and if this is not a child and if we are closing
3311 // a view (not a tabgroup).
3312 bool const close_buffer =
3313 !inOtherView(b) && !b.parent() && closing_;
3315 if (!closeWorkArea(wa, close_buffer))
3322 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3324 if (buf.isClean() || buf.paragraphs().empty())
3327 // Switch to this Buffer.
3333 if (buf.isUnnamed()) {
3334 file = from_utf8(buf.fileName().onlyFileName());
3337 FileName filename = buf.fileName();
3339 file = filename.displayName(30);
3340 exists = filename.exists();
3343 // Bring this window to top before asking questions.
3348 if (hiding && buf.isUnnamed()) {
3349 docstring const text = bformat(_("The document %1$s has not been "
3350 "saved yet.\n\nDo you want to save "
3351 "the document?"), file);
3352 ret = Alert::prompt(_("Save new document?"),
3353 text, 0, 1, _("&Save"), _("&Cancel"));
3357 docstring const text = exists ?
3358 bformat(_("The document %1$s has unsaved changes."
3359 "\n\nDo you want to save the document or "
3360 "discard the changes?"), file) :
3361 bformat(_("The document %1$s has not been saved yet."
3362 "\n\nDo you want to save the document or "
3363 "discard it entirely?"), file);
3364 docstring const title = exists ?
3365 _("Save changed document?") : _("Save document?");
3366 ret = Alert::prompt(title, text, 0, 2,
3367 _("&Save"), _("&Discard"), _("&Cancel"));
3372 if (!saveBuffer(buf))
3376 // If we crash after this we could have no autosave file
3377 // but I guess this is really improbable (Jug).
3378 // Sometimes improbable things happen:
3379 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3380 // buf.removeAutosaveFile();
3382 // revert all changes
3393 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3395 Buffer & buf = wa->bufferView().buffer();
3397 for (int i = 0; i != d.splitter_->count(); ++i) {
3398 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3399 if (wa_ && wa_ != wa)
3402 return inOtherView(buf);
3406 bool GuiView::inOtherView(Buffer & buf)
3408 QList<int> const ids = guiApp->viewIds();
3410 for (int i = 0; i != ids.size(); ++i) {
3414 if (guiApp->view(ids[i]).workArea(buf))
3421 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3423 if (!documentBufferView())
3426 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3427 Buffer * const curbuf = &documentBufferView()->buffer();
3428 int nwa = twa->count();
3429 for (int i = 0; i < nwa; ++i) {
3430 if (&workArea(i)->bufferView().buffer() == curbuf) {
3432 if (np == NEXTBUFFER)
3433 next_index = (i == nwa - 1 ? 0 : i + 1);
3435 next_index = (i == 0 ? nwa - 1 : i - 1);
3437 twa->moveTab(i, next_index);
3439 setBuffer(&workArea(next_index)->bufferView().buffer());
3447 /// make sure the document is saved
3448 static bool ensureBufferClean(Buffer * buffer)
3450 LASSERT(buffer, return false);
3451 if (buffer->isClean() && !buffer->isUnnamed())
3454 docstring const file = buffer->fileName().displayName(30);
3457 if (!buffer->isUnnamed()) {
3458 text = bformat(_("The document %1$s has unsaved "
3459 "changes.\n\nDo you want to save "
3460 "the document?"), file);
3461 title = _("Save changed document?");
3464 text = bformat(_("The document %1$s has not been "
3465 "saved yet.\n\nDo you want to save "
3466 "the document?"), file);
3467 title = _("Save new document?");
3469 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3472 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3474 return buffer->isClean() && !buffer->isUnnamed();
3478 bool GuiView::reloadBuffer(Buffer & buf)
3480 currentBufferView()->cursor().reset();
3481 Buffer::ReadStatus status = buf.reload();
3482 return status == Buffer::ReadSuccess;
3486 void GuiView::checkExternallyModifiedBuffers()
3488 BufferList::iterator bit = theBufferList().begin();
3489 BufferList::iterator const bend = theBufferList().end();
3490 for (; bit != bend; ++bit) {
3491 Buffer * buf = *bit;
3492 if (buf->fileName().exists() && buf->isChecksumModified()) {
3493 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3494 " Reload now? Any local changes will be lost."),
3495 from_utf8(buf->absFileName()));
3496 int const ret = Alert::prompt(_("Reload externally changed document?"),
3497 text, 0, 1, _("&Reload"), _("&Cancel"));
3505 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3507 Buffer * buffer = documentBufferView()
3508 ? &(documentBufferView()->buffer()) : nullptr;
3510 switch (cmd.action()) {
3511 case LFUN_VC_REGISTER:
3512 if (!buffer || !ensureBufferClean(buffer))
3514 if (!buffer->lyxvc().inUse()) {
3515 if (buffer->lyxvc().registrer()) {
3516 reloadBuffer(*buffer);
3517 dr.clearMessageUpdate();
3522 case LFUN_VC_RENAME:
3523 case LFUN_VC_COPY: {
3524 if (!buffer || !ensureBufferClean(buffer))
3526 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3527 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3528 // Some changes are not yet committed.
3529 // We test here and not in getStatus(), since
3530 // this test is expensive.
3532 LyXVC::CommandResult ret =
3533 buffer->lyxvc().checkIn(log);
3535 if (ret == LyXVC::ErrorCommand ||
3536 ret == LyXVC::VCSuccess)
3537 reloadBuffer(*buffer);
3538 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3539 frontend::Alert::error(
3540 _("Revision control error."),
3541 _("Document could not be checked in."));
3545 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3546 LV_VC_RENAME : LV_VC_COPY;
3547 renameBuffer(*buffer, cmd.argument(), kind);
3552 case LFUN_VC_CHECK_IN:
3553 if (!buffer || !ensureBufferClean(buffer))
3555 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3557 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3559 // Only skip reloading if the checkin was cancelled or
3560 // an error occurred before the real checkin VCS command
3561 // was executed, since the VCS might have changed the
3562 // file even if it could not checkin successfully.
3563 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3564 reloadBuffer(*buffer);
3568 case LFUN_VC_CHECK_OUT:
3569 if (!buffer || !ensureBufferClean(buffer))
3571 if (buffer->lyxvc().inUse()) {
3572 dr.setMessage(buffer->lyxvc().checkOut());
3573 reloadBuffer(*buffer);
3577 case LFUN_VC_LOCKING_TOGGLE:
3578 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3580 if (buffer->lyxvc().inUse()) {
3581 string res = buffer->lyxvc().lockingToggle();
3583 frontend::Alert::error(_("Revision control error."),
3584 _("Error when setting the locking property."));
3587 reloadBuffer(*buffer);
3592 case LFUN_VC_REVERT:
3595 if (buffer->lyxvc().revert()) {
3596 reloadBuffer(*buffer);
3597 dr.clearMessageUpdate();
3601 case LFUN_VC_UNDO_LAST:
3604 buffer->lyxvc().undoLast();
3605 reloadBuffer(*buffer);
3606 dr.clearMessageUpdate();
3609 case LFUN_VC_REPO_UPDATE:
3612 if (ensureBufferClean(buffer)) {
3613 dr.setMessage(buffer->lyxvc().repoUpdate());
3614 checkExternallyModifiedBuffers();
3618 case LFUN_VC_COMMAND: {
3619 string flag = cmd.getArg(0);
3620 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3623 if (contains(flag, 'M')) {
3624 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3627 string path = cmd.getArg(1);
3628 if (contains(path, "$$p") && buffer)
3629 path = subst(path, "$$p", buffer->filePath());
3630 LYXERR(Debug::LYXVC, "Directory: " << path);
3632 if (!pp.isReadableDirectory()) {
3633 lyxerr << _("Directory is not accessible.") << endl;
3636 support::PathChanger p(pp);
3638 string command = cmd.getArg(2);
3639 if (command.empty())
3642 command = subst(command, "$$i", buffer->absFileName());
3643 command = subst(command, "$$p", buffer->filePath());
3645 command = subst(command, "$$m", to_utf8(message));
3646 LYXERR(Debug::LYXVC, "Command: " << command);
3648 one.startscript(Systemcall::Wait, command);
3652 if (contains(flag, 'I'))
3653 buffer->markDirty();
3654 if (contains(flag, 'R'))
3655 reloadBuffer(*buffer);
3660 case LFUN_VC_COMPARE: {
3661 if (cmd.argument().empty()) {
3662 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3668 string rev1 = cmd.getArg(0);
3672 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3675 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3676 f2 = buffer->absFileName();
3678 string rev2 = cmd.getArg(1);
3682 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3686 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3687 f1 << "\n" << f2 << "\n" );
3688 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3689 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3699 void GuiView::openChildDocument(string const & fname)
3701 LASSERT(documentBufferView(), return);
3702 Buffer & buffer = documentBufferView()->buffer();
3703 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3704 documentBufferView()->saveBookmark(false);
3705 Buffer * child = nullptr;
3706 if (theBufferList().exists(filename)) {
3707 child = theBufferList().getBuffer(filename);
3710 message(bformat(_("Opening child document %1$s..."),
3711 makeDisplayPath(filename.absFileName())));
3712 child = loadDocument(filename, false);
3714 // Set the parent name of the child document.
3715 // This makes insertion of citations and references in the child work,
3716 // when the target is in the parent or another child document.
3718 child->setParent(&buffer);
3722 bool GuiView::goToFileRow(string const & argument)
3726 size_t i = argument.find_last_of(' ');
3727 if (i != string::npos) {
3728 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3729 istringstream is(argument.substr(i + 1));
3734 if (i == string::npos) {
3735 LYXERR0("Wrong argument: " << argument);
3738 Buffer * buf = nullptr;
3739 string const realtmp = package().temp_dir().realPath();
3740 // We have to use os::path_prefix_is() here, instead of
3741 // simply prefixIs(), because the file name comes from
3742 // an external application and may need case adjustment.
3743 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3744 buf = theBufferList().getBufferFromTmp(file_name, true);
3745 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3746 << (buf ? " success" : " failed"));
3748 // Must replace extension of the file to be .lyx
3749 // and get full path
3750 FileName const s = fileSearch(string(),
3751 support::changeExtension(file_name, ".lyx"), "lyx");
3752 // Either change buffer or load the file
3753 if (theBufferList().exists(s))
3754 buf = theBufferList().getBuffer(s);
3755 else if (s.exists()) {
3756 buf = loadDocument(s);
3761 _("File does not exist: %1$s"),
3762 makeDisplayPath(file_name)));
3768 _("No buffer for file: %1$s."),
3769 makeDisplayPath(file_name))
3774 bool success = documentBufferView()->setCursorFromRow(row);
3776 LYXERR(Debug::LATEX,
3777 "setCursorFromRow: invalid position for row " << row);
3778 frontend::Alert::error(_("Inverse Search Failed"),
3779 _("Invalid position requested by inverse search.\n"
3780 "You may need to update the viewed document."));
3787 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3788 Buffer const * orig, Buffer * clone, string const & format)
3790 Buffer::ExportStatus const status = func(format);
3792 // the cloning operation will have produced a clone of the entire set of
3793 // documents, starting from the master. so we must delete those.
3794 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3796 busyBuffers.remove(orig);
3801 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3802 Buffer const * orig, Buffer * clone, string const & format)
3804 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3806 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3810 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3811 Buffer const * orig, Buffer * clone, string const & format)
3813 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3815 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3819 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3820 Buffer const * orig, Buffer * clone, string const & format)
3822 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3824 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3828 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3829 string const & argument,
3830 Buffer const * used_buffer,
3831 docstring const & msg,
3832 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3833 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3834 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3840 string format = argument;
3842 format = used_buffer->params().getDefaultOutputFormat();
3843 processing_format = format;
3845 progress_->clearMessages();
3848 #if EXPORT_in_THREAD
3850 GuiViewPrivate::busyBuffers.insert(used_buffer);
3851 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3852 if (!cloned_buffer) {
3853 Alert::error(_("Export Error"),
3854 _("Error cloning the Buffer."));
3857 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3862 setPreviewFuture(f);
3863 last_export_format = used_buffer->params().bufferFormat();
3866 // We are asynchronous, so we don't know here anything about the success
3869 Buffer::ExportStatus status;
3871 status = (used_buffer->*syncFunc)(format, false);
3872 } else if (previewFunc) {
3873 status = (used_buffer->*previewFunc)(format);
3876 handleExportStatus(gv_, status, format);
3878 return (status == Buffer::ExportSuccess
3879 || status == Buffer::PreviewSuccess);
3883 Buffer::ExportStatus status;
3885 status = (used_buffer->*syncFunc)(format, true);
3886 } else if (previewFunc) {
3887 status = (used_buffer->*previewFunc)(format);
3890 handleExportStatus(gv_, status, format);
3892 return (status == Buffer::ExportSuccess
3893 || status == Buffer::PreviewSuccess);
3897 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3899 BufferView * bv = currentBufferView();
3900 LASSERT(bv, return);
3902 // Let the current BufferView dispatch its own actions.
3903 bv->dispatch(cmd, dr);
3904 if (dr.dispatched()) {
3905 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3906 updateDialog("document", "");
3910 // Try with the document BufferView dispatch if any.
3911 BufferView * doc_bv = documentBufferView();
3912 if (doc_bv && doc_bv != bv) {
3913 doc_bv->dispatch(cmd, dr);
3914 if (dr.dispatched()) {
3915 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3916 updateDialog("document", "");
3921 // Then let the current Cursor dispatch its own actions.
3922 bv->cursor().dispatch(cmd);
3924 // update completion. We do it here and not in
3925 // processKeySym to avoid another redraw just for a
3926 // changed inline completion
3927 if (cmd.origin() == FuncRequest::KEYBOARD) {
3928 if (cmd.action() == LFUN_SELF_INSERT
3929 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3930 updateCompletion(bv->cursor(), true, true);
3931 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3932 updateCompletion(bv->cursor(), false, true);
3934 updateCompletion(bv->cursor(), false, false);
3937 dr = bv->cursor().result();
3941 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3943 BufferView * bv = currentBufferView();
3944 // By default we won't need any update.
3945 dr.screenUpdate(Update::None);
3946 // assume cmd will be dispatched
3947 dr.dispatched(true);
3949 Buffer * doc_buffer = documentBufferView()
3950 ? &(documentBufferView()->buffer()) : nullptr;
3952 if (cmd.origin() == FuncRequest::TOC) {
3953 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3954 toc->doDispatch(bv->cursor(), cmd, dr);
3958 string const argument = to_utf8(cmd.argument());
3960 switch(cmd.action()) {
3961 case LFUN_BUFFER_CHILD_OPEN:
3962 openChildDocument(to_utf8(cmd.argument()));
3965 case LFUN_BUFFER_IMPORT:
3966 importDocument(to_utf8(cmd.argument()));
3969 case LFUN_MASTER_BUFFER_EXPORT:
3971 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3973 case LFUN_BUFFER_EXPORT: {
3976 // GCC only sees strfwd.h when building merged
3977 if (::lyx::operator==(cmd.argument(), "custom")) {
3978 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3979 // so the following test should not be needed.
3980 // In principle, we could try to switch to such a view...
3981 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3982 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3986 string const dest = cmd.getArg(1);
3987 FileName target_dir;
3988 if (!dest.empty() && FileName::isAbsolute(dest))
3989 target_dir = FileName(support::onlyPath(dest));
3991 target_dir = doc_buffer->fileName().onlyPath();
3993 string const format = (argument.empty() || argument == "default") ?
3994 doc_buffer->params().getDefaultOutputFormat() : argument;
3996 if ((dest.empty() && doc_buffer->isUnnamed())
3997 || !target_dir.isDirWritable()) {
3998 exportBufferAs(*doc_buffer, from_utf8(format));
4001 /* TODO/Review: Is it a problem to also export the children?
4002 See the update_unincluded flag */
4003 d.asyncBufferProcessing(format,
4006 &GuiViewPrivate::exportAndDestroy,
4008 nullptr, cmd.allowAsync());
4009 // TODO Inform user about success
4013 case LFUN_BUFFER_EXPORT_AS: {
4014 LASSERT(doc_buffer, break);
4015 docstring f = cmd.argument();
4017 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4018 exportBufferAs(*doc_buffer, f);
4022 case LFUN_BUFFER_UPDATE: {
4023 d.asyncBufferProcessing(argument,
4026 &GuiViewPrivate::compileAndDestroy,
4028 nullptr, cmd.allowAsync());
4031 case LFUN_BUFFER_VIEW: {
4032 d.asyncBufferProcessing(argument,
4034 _("Previewing ..."),
4035 &GuiViewPrivate::previewAndDestroy,
4037 &Buffer::preview, cmd.allowAsync());
4040 case LFUN_MASTER_BUFFER_UPDATE: {
4041 d.asyncBufferProcessing(argument,
4042 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4044 &GuiViewPrivate::compileAndDestroy,
4046 nullptr, cmd.allowAsync());
4049 case LFUN_MASTER_BUFFER_VIEW: {
4050 d.asyncBufferProcessing(argument,
4051 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4053 &GuiViewPrivate::previewAndDestroy,
4054 nullptr, &Buffer::preview, cmd.allowAsync());
4057 case LFUN_EXPORT_CANCEL: {
4058 Systemcall::killscript();
4061 case LFUN_BUFFER_SWITCH: {
4062 string const file_name = to_utf8(cmd.argument());
4063 if (!FileName::isAbsolute(file_name)) {
4065 dr.setMessage(_("Absolute filename expected."));
4069 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4072 dr.setMessage(_("Document not loaded"));
4076 // Do we open or switch to the buffer in this view ?
4077 if (workArea(*buffer)
4078 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4083 // Look for the buffer in other views
4084 QList<int> const ids = guiApp->viewIds();
4086 for (; i != ids.size(); ++i) {
4087 GuiView & gv = guiApp->view(ids[i]);
4088 if (gv.workArea(*buffer)) {
4090 gv.activateWindow();
4092 gv.setBuffer(buffer);
4097 // If necessary, open a new window as a last resort
4098 if (i == ids.size()) {
4099 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4105 case LFUN_BUFFER_NEXT:
4106 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4109 case LFUN_BUFFER_MOVE_NEXT:
4110 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4113 case LFUN_BUFFER_PREVIOUS:
4114 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4117 case LFUN_BUFFER_MOVE_PREVIOUS:
4118 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4121 case LFUN_BUFFER_CHKTEX:
4122 LASSERT(doc_buffer, break);
4123 doc_buffer->runChktex();
4126 case LFUN_COMMAND_EXECUTE: {
4127 command_execute_ = true;
4128 minibuffer_focus_ = true;
4131 case LFUN_DROP_LAYOUTS_CHOICE:
4132 d.layout_->showPopup();
4135 case LFUN_MENU_OPEN:
4136 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4137 menu->exec(QCursor::pos());
4140 case LFUN_FILE_INSERT: {
4141 if (cmd.getArg(1) == "ignorelang")
4142 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4144 insertLyXFile(cmd.argument());
4148 case LFUN_FILE_INSERT_PLAINTEXT:
4149 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4150 string const fname = to_utf8(cmd.argument());
4151 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4152 dr.setMessage(_("Absolute filename expected."));
4156 FileName filename(fname);
4157 if (fname.empty()) {
4158 FileDialog dlg(qt_("Select file to insert"));
4160 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4161 QStringList(qt_("All Files (*)")));
4163 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4164 dr.setMessage(_("Canceled."));
4168 filename.set(fromqstr(result.second));
4172 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4173 bv->dispatch(new_cmd, dr);
4178 case LFUN_BUFFER_RELOAD: {
4179 LASSERT(doc_buffer, break);
4182 bool drop = (cmd.argument() == "dump");
4185 if (!drop && !doc_buffer->isClean()) {
4186 docstring const file =
4187 makeDisplayPath(doc_buffer->absFileName(), 20);
4188 if (doc_buffer->notifiesExternalModification()) {
4189 docstring text = _("The current version will be lost. "
4190 "Are you sure you want to load the version on disk "
4191 "of the document %1$s?");
4192 ret = Alert::prompt(_("Reload saved document?"),
4193 bformat(text, file), 1, 1,
4194 _("&Reload"), _("&Cancel"));
4196 docstring text = _("Any changes will be lost. "
4197 "Are you sure you want to revert to the saved version "
4198 "of the document %1$s?");
4199 ret = Alert::prompt(_("Revert to saved document?"),
4200 bformat(text, file), 1, 1,
4201 _("&Revert"), _("&Cancel"));
4206 doc_buffer->markClean();
4207 reloadBuffer(*doc_buffer);
4208 dr.forceBufferUpdate();
4213 case LFUN_BUFFER_RESET_EXPORT:
4214 LASSERT(doc_buffer, break);
4215 doc_buffer->requireFreshStart(true);
4216 dr.setMessage(_("Buffer export reset."));
4219 case LFUN_BUFFER_WRITE:
4220 LASSERT(doc_buffer, break);
4221 saveBuffer(*doc_buffer);
4224 case LFUN_BUFFER_WRITE_AS:
4225 LASSERT(doc_buffer, break);
4226 renameBuffer(*doc_buffer, cmd.argument());
4229 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4230 LASSERT(doc_buffer, break);
4231 renameBuffer(*doc_buffer, cmd.argument(),
4232 LV_WRITE_AS_TEMPLATE);
4235 case LFUN_BUFFER_WRITE_ALL: {
4236 Buffer * first = theBufferList().first();
4239 message(_("Saving all documents..."));
4240 // We cannot use a for loop as the buffer list cycles.
4243 if (!b->isClean()) {
4245 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4247 b = theBufferList().next(b);
4248 } while (b != first);
4249 dr.setMessage(_("All documents saved."));
4253 case LFUN_MASTER_BUFFER_FORALL: {
4257 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4258 funcToRun.allowAsync(false);
4260 for (Buffer const * buf : doc_buffer->allRelatives()) {
4261 // Switch to other buffer view and resend cmd
4262 lyx::dispatch(FuncRequest(
4263 LFUN_BUFFER_SWITCH, buf->absFileName()));
4264 lyx::dispatch(funcToRun);
4267 lyx::dispatch(FuncRequest(
4268 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4272 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4273 LASSERT(doc_buffer, break);
4274 doc_buffer->clearExternalModification();
4277 case LFUN_BUFFER_CLOSE:
4281 case LFUN_BUFFER_CLOSE_ALL:
4285 case LFUN_DEVEL_MODE_TOGGLE:
4286 devel_mode_ = !devel_mode_;
4288 dr.setMessage(_("Developer mode is now enabled."));
4290 dr.setMessage(_("Developer mode is now disabled."));
4293 case LFUN_TOOLBAR_TOGGLE: {
4294 string const name = cmd.getArg(0);
4295 if (GuiToolbar * t = toolbar(name))
4300 case LFUN_TOOLBAR_MOVABLE: {
4301 string const name = cmd.getArg(0);
4303 // toggle (all) toolbars movablility
4304 toolbarsMovable_ = !toolbarsMovable_;
4305 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4306 GuiToolbar * tb = toolbar(ti.name);
4307 if (tb && tb->isMovable() != toolbarsMovable_)
4308 // toggle toolbar movablity if it does not fit lock
4309 // (all) toolbars positions state silent = true, since
4310 // status bar notifications are slow
4313 if (toolbarsMovable_)
4314 dr.setMessage(_("Toolbars unlocked."));
4316 dr.setMessage(_("Toolbars locked."));
4317 } else if (GuiToolbar * t = toolbar(name)) {
4318 // toggle current toolbar movablity
4320 // update lock (all) toolbars positions
4321 updateLockToolbars();
4326 case LFUN_ICON_SIZE: {
4327 QSize size = d.iconSize(cmd.argument());
4329 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4330 size.width(), size.height()));
4334 case LFUN_DIALOG_UPDATE: {
4335 string const name = to_utf8(cmd.argument());
4336 if (name == "prefs" || name == "document")
4337 updateDialog(name, string());
4338 else if (name == "paragraph")
4339 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4340 else if (currentBufferView()) {
4341 Inset * inset = currentBufferView()->editedInset(name);
4342 // Can only update a dialog connected to an existing inset
4344 // FIXME: get rid of this indirection; GuiView ask the inset
4345 // if he is kind enough to update itself...
4346 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4347 //FIXME: pass DispatchResult here?
4348 inset->dispatch(currentBufferView()->cursor(), fr);
4354 case LFUN_DIALOG_TOGGLE: {
4355 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4356 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4357 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4361 case LFUN_DIALOG_DISCONNECT_INSET:
4362 disconnectDialog(to_utf8(cmd.argument()));
4365 case LFUN_DIALOG_HIDE: {
4366 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4370 case LFUN_DIALOG_SHOW: {
4371 string const name = cmd.getArg(0);
4372 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4374 if (name == "latexlog") {
4375 // getStatus checks that
4376 LASSERT(doc_buffer, break);
4377 Buffer::LogType type;
4378 string const logfile = doc_buffer->logName(&type);
4380 case Buffer::latexlog:
4383 case Buffer::buildlog:
4384 sdata = "literate ";
4387 sdata += Lexer::quoteString(logfile);
4388 showDialog("log", sdata);
4389 } else if (name == "vclog") {
4390 // getStatus checks that
4391 LASSERT(doc_buffer, break);
4392 string const sdata2 = "vc " +
4393 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4394 showDialog("log", sdata2);
4395 } else if (name == "symbols") {
4396 sdata = bv->cursor().getEncoding()->name();
4398 showDialog("symbols", sdata);
4400 } else if (name == "prefs" && isFullScreen()) {
4401 lfunUiToggle("fullscreen");
4402 showDialog("prefs", sdata);
4404 showDialog(name, sdata);
4409 dr.setMessage(cmd.argument());
4412 case LFUN_UI_TOGGLE: {
4413 string arg = cmd.getArg(0);
4414 if (!lfunUiToggle(arg)) {
4415 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4416 dr.setMessage(bformat(msg, from_utf8(arg)));
4418 // Make sure the keyboard focus stays in the work area.
4423 case LFUN_VIEW_SPLIT: {
4424 LASSERT(doc_buffer, break);
4425 string const orientation = cmd.getArg(0);
4426 d.splitter_->setOrientation(orientation == "vertical"
4427 ? Qt::Vertical : Qt::Horizontal);
4428 TabWorkArea * twa = addTabWorkArea();
4429 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4430 setCurrentWorkArea(wa);
4433 case LFUN_TAB_GROUP_CLOSE:
4434 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4435 closeTabWorkArea(twa);
4436 d.current_work_area_ = nullptr;
4437 twa = d.currentTabWorkArea();
4438 // Switch to the next GuiWorkArea in the found TabWorkArea.
4440 // Make sure the work area is up to date.
4441 setCurrentWorkArea(twa->currentWorkArea());
4443 setCurrentWorkArea(nullptr);
4448 case LFUN_VIEW_CLOSE:
4449 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4450 closeWorkArea(twa->currentWorkArea());
4451 d.current_work_area_ = nullptr;
4452 twa = d.currentTabWorkArea();
4453 // Switch to the next GuiWorkArea in the found TabWorkArea.
4455 // Make sure the work area is up to date.
4456 setCurrentWorkArea(twa->currentWorkArea());
4458 setCurrentWorkArea(nullptr);
4463 case LFUN_COMPLETION_INLINE:
4464 if (d.current_work_area_)
4465 d.current_work_area_->completer().showInline();
4468 case LFUN_COMPLETION_POPUP:
4469 if (d.current_work_area_)
4470 d.current_work_area_->completer().showPopup();
4475 if (d.current_work_area_)
4476 d.current_work_area_->completer().tab();
4479 case LFUN_COMPLETION_CANCEL:
4480 if (d.current_work_area_) {
4481 if (d.current_work_area_->completer().popupVisible())
4482 d.current_work_area_->completer().hidePopup();
4484 d.current_work_area_->completer().hideInline();
4488 case LFUN_COMPLETION_ACCEPT:
4489 if (d.current_work_area_)
4490 d.current_work_area_->completer().activate();
4493 case LFUN_BUFFER_ZOOM_IN:
4494 case LFUN_BUFFER_ZOOM_OUT:
4495 case LFUN_BUFFER_ZOOM: {
4496 if (cmd.argument().empty()) {
4497 if (cmd.action() == LFUN_BUFFER_ZOOM)
4499 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4504 if (cmd.action() == LFUN_BUFFER_ZOOM)
4505 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4506 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4507 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4509 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4512 // Actual zoom value: default zoom + fractional extra value
4513 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4514 if (zoom < static_cast<int>(zoom_min_))
4517 lyxrc.currentZoom = zoom;
4519 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4520 lyxrc.currentZoom, lyxrc.defaultZoom));
4522 guiApp->fontLoader().update();
4523 dr.screenUpdate(Update::Force | Update::FitCursor);
4527 case LFUN_VC_REGISTER:
4528 case LFUN_VC_RENAME:
4530 case LFUN_VC_CHECK_IN:
4531 case LFUN_VC_CHECK_OUT:
4532 case LFUN_VC_REPO_UPDATE:
4533 case LFUN_VC_LOCKING_TOGGLE:
4534 case LFUN_VC_REVERT:
4535 case LFUN_VC_UNDO_LAST:
4536 case LFUN_VC_COMMAND:
4537 case LFUN_VC_COMPARE:
4538 dispatchVC(cmd, dr);
4541 case LFUN_SERVER_GOTO_FILE_ROW:
4542 if(goToFileRow(to_utf8(cmd.argument())))
4543 dr.screenUpdate(Update::Force | Update::FitCursor);
4546 case LFUN_LYX_ACTIVATE:
4550 case LFUN_WINDOW_RAISE:
4556 case LFUN_FORWARD_SEARCH: {
4557 // it seems safe to assume we have a document buffer, since
4558 // getStatus wants one.
4559 LASSERT(doc_buffer, break);
4560 Buffer const * doc_master = doc_buffer->masterBuffer();
4561 FileName const path(doc_master->temppath());
4562 string const texname = doc_master->isChild(doc_buffer)
4563 ? DocFileName(changeExtension(
4564 doc_buffer->absFileName(),
4565 "tex")).mangledFileName()
4566 : doc_buffer->latexName();
4567 string const fulltexname =
4568 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4569 string const mastername =
4570 removeExtension(doc_master->latexName());
4571 FileName const dviname(addName(path.absFileName(),
4572 addExtension(mastername, "dvi")));
4573 FileName const pdfname(addName(path.absFileName(),
4574 addExtension(mastername, "pdf")));
4575 bool const have_dvi = dviname.exists();
4576 bool const have_pdf = pdfname.exists();
4577 if (!have_dvi && !have_pdf) {
4578 dr.setMessage(_("Please, preview the document first."));
4581 string outname = dviname.onlyFileName();
4582 string command = lyxrc.forward_search_dvi;
4583 if (!have_dvi || (have_pdf &&
4584 pdfname.lastModified() > dviname.lastModified())) {
4585 outname = pdfname.onlyFileName();
4586 command = lyxrc.forward_search_pdf;
4589 DocIterator cur = bv->cursor();
4590 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4591 LYXERR(Debug::ACTION, "Forward search: row:" << row
4593 if (row == -1 || command.empty()) {
4594 dr.setMessage(_("Couldn't proceed."));
4597 string texrow = convert<string>(row);
4599 command = subst(command, "$$n", texrow);
4600 command = subst(command, "$$f", fulltexname);
4601 command = subst(command, "$$t", texname);
4602 command = subst(command, "$$o", outname);
4604 volatile PathChanger p(path);
4606 one.startscript(Systemcall::DontWait, command);
4610 case LFUN_SPELLING_CONTINUOUSLY:
4611 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4612 dr.screenUpdate(Update::Force);
4615 case LFUN_CITATION_OPEN: {
4617 if (theFormats().getFormat("pdf"))
4618 pdfv = theFormats().getFormat("pdf")->viewer();
4619 if (theFormats().getFormat("ps"))
4620 psv = theFormats().getFormat("ps")->viewer();
4621 frontend::showTarget(argument, pdfv, psv);
4626 // The LFUN must be for one of BufferView, Buffer or Cursor;
4628 dispatchToBufferView(cmd, dr);
4632 // Part of automatic menu appearance feature.
4633 if (isFullScreen()) {
4634 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4638 // Need to update bv because many LFUNs here might have destroyed it
4639 bv = currentBufferView();
4641 // Clear non-empty selections
4642 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4644 Cursor & cur = bv->cursor();
4645 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4646 cur.clearSelection();
4652 bool GuiView::lfunUiToggle(string const & ui_component)
4654 if (ui_component == "scrollbar") {
4655 // hide() is of no help
4656 if (d.current_work_area_->verticalScrollBarPolicy() ==
4657 Qt::ScrollBarAlwaysOff)
4659 d.current_work_area_->setVerticalScrollBarPolicy(
4660 Qt::ScrollBarAsNeeded);
4662 d.current_work_area_->setVerticalScrollBarPolicy(
4663 Qt::ScrollBarAlwaysOff);
4664 } else if (ui_component == "statusbar") {
4665 statusBar()->setVisible(!statusBar()->isVisible());
4666 } else if (ui_component == "menubar") {
4667 menuBar()->setVisible(!menuBar()->isVisible());
4669 if (ui_component == "frame") {
4670 int const l = contentsMargins().left();
4672 //are the frames in default state?
4673 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4675 setContentsMargins(-2, -2, -2, -2);
4677 setContentsMargins(0, 0, 0, 0);
4680 if (ui_component == "fullscreen") {
4688 void GuiView::toggleFullScreen()
4690 setWindowState(windowState() ^ Qt::WindowFullScreen);
4694 Buffer const * GuiView::updateInset(Inset const * inset)
4699 Buffer const * inset_buffer = &(inset->buffer());
4701 for (int i = 0; i != d.splitter_->count(); ++i) {
4702 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4705 Buffer const * buffer = &(wa->bufferView().buffer());
4706 if (inset_buffer == buffer)
4707 wa->scheduleRedraw(true);
4709 return inset_buffer;
4713 void GuiView::restartCaret()
4715 /* When we move around, or type, it's nice to be able to see
4716 * the caret immediately after the keypress.
4718 if (d.current_work_area_)
4719 d.current_work_area_->startBlinkingCaret();
4721 // Take this occasion to update the other GUI elements.
4727 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4729 if (d.current_work_area_)
4730 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4735 // This list should be kept in sync with the list of insets in
4736 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4737 // dialog should have the same name as the inset.
4738 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4739 // docs in LyXAction.cpp.
4741 char const * const dialognames[] = {
4743 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4744 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4745 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4746 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4747 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4748 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4749 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4750 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4752 char const * const * const end_dialognames =
4753 dialognames + (sizeof(dialognames) / sizeof(char *));
4757 cmpCStr(char const * name) : name_(name) {}
4758 bool operator()(char const * other) {
4759 return strcmp(other, name_) == 0;
4766 bool isValidName(string const & name)
4768 return find_if(dialognames, end_dialognames,
4769 cmpCStr(name.c_str())) != end_dialognames;
4775 void GuiView::resetDialogs()
4777 // Make sure that no LFUN uses any GuiView.
4778 guiApp->setCurrentView(nullptr);
4782 constructToolbars();
4783 guiApp->menus().fillMenuBar(menuBar(), this, false);
4784 d.layout_->updateContents(true);
4785 // Now update controls with current buffer.
4786 guiApp->setCurrentView(this);
4792 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4794 for (QObject * child: widget->children()) {
4795 if (child->inherits("QGroupBox")) {
4796 QGroupBox * box = (QGroupBox*) child;
4799 flatGroupBoxes(child, flag);
4805 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4807 if (!isValidName(name))
4810 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4812 if (it != d.dialogs_.end()) {
4814 it->second->hideView();
4815 return it->second.get();
4818 Dialog * dialog = build(name);
4819 d.dialogs_[name].reset(dialog);
4820 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4821 // Force a uniform style for group boxes
4822 // On Mac non-flat works better, on Linux flat is standard
4823 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4825 if (lyxrc.allow_geometry_session)
4826 dialog->restoreSession();
4833 void GuiView::showDialog(string const & name, string const & sdata,
4836 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4840 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4846 const string name = fromqstr(qname);
4847 const string sdata = fromqstr(qdata);
4851 Dialog * dialog = findOrBuild(name, false);
4853 bool const visible = dialog->isVisibleView();
4854 dialog->showData(sdata);
4855 if (currentBufferView())
4856 currentBufferView()->editInset(name, inset);
4857 // We only set the focus to the new dialog if it was not yet
4858 // visible in order not to change the existing previous behaviour
4860 // activateWindow is needed for floating dockviews
4861 dialog->asQWidget()->raise();
4862 dialog->asQWidget()->activateWindow();
4863 dialog->asQWidget()->setFocus();
4867 catch (ExceptionMessage const & ex) {
4875 bool GuiView::isDialogVisible(string const & name) const
4877 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4878 if (it == d.dialogs_.end())
4880 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4884 void GuiView::hideDialog(string const & name, Inset * inset)
4886 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4887 if (it == d.dialogs_.end())
4891 if (!currentBufferView())
4893 if (inset != currentBufferView()->editedInset(name))
4897 Dialog * const dialog = it->second.get();
4898 if (dialog->isVisibleView())
4900 if (currentBufferView())
4901 currentBufferView()->editInset(name, nullptr);
4905 void GuiView::disconnectDialog(string const & name)
4907 if (!isValidName(name))
4909 if (currentBufferView())
4910 currentBufferView()->editInset(name, nullptr);
4914 void GuiView::hideAll() const
4916 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4917 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4919 for(; it != end; ++it)
4920 it->second->hideView();
4924 void GuiView::updateDialogs()
4926 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4927 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4929 for(; it != end; ++it) {
4930 Dialog * dialog = it->second.get();
4932 if (dialog->needBufferOpen() && !documentBufferView())
4933 hideDialog(fromqstr(dialog->name()), nullptr);
4934 else if (dialog->isVisibleView())
4935 dialog->checkStatus();
4942 Dialog * createDialog(GuiView & lv, string const & name);
4944 // will be replaced by a proper factory...
4945 Dialog * createGuiAbout(GuiView & lv);
4946 Dialog * createGuiBibtex(GuiView & lv);
4947 Dialog * createGuiChanges(GuiView & lv);
4948 Dialog * createGuiCharacter(GuiView & lv);
4949 Dialog * createGuiCitation(GuiView & lv);
4950 Dialog * createGuiCompare(GuiView & lv);
4951 Dialog * createGuiCompareHistory(GuiView & lv);
4952 Dialog * createGuiDelimiter(GuiView & lv);
4953 Dialog * createGuiDocument(GuiView & lv);
4954 Dialog * createGuiErrorList(GuiView & lv);
4955 Dialog * createGuiExternal(GuiView & lv);
4956 Dialog * createGuiGraphics(GuiView & lv);
4957 Dialog * createGuiInclude(GuiView & lv);
4958 Dialog * createGuiIndex(GuiView & lv);
4959 Dialog * createGuiListings(GuiView & lv);
4960 Dialog * createGuiLog(GuiView & lv);
4961 Dialog * createGuiLyXFiles(GuiView & lv);
4962 Dialog * createGuiMathMatrix(GuiView & lv);
4963 Dialog * createGuiNote(GuiView & lv);
4964 Dialog * createGuiParagraph(GuiView & lv);
4965 Dialog * createGuiPhantom(GuiView & lv);
4966 Dialog * createGuiPreferences(GuiView & lv);
4967 Dialog * createGuiPrint(GuiView & lv);
4968 Dialog * createGuiPrintindex(GuiView & lv);
4969 Dialog * createGuiRef(GuiView & lv);
4970 Dialog * createGuiSearch(GuiView & lv);
4971 Dialog * createGuiSearchAdv(GuiView & lv);
4972 Dialog * createGuiSendTo(GuiView & lv);
4973 Dialog * createGuiShowFile(GuiView & lv);
4974 Dialog * createGuiSpellchecker(GuiView & lv);
4975 Dialog * createGuiSymbols(GuiView & lv);
4976 Dialog * createGuiTabularCreate(GuiView & lv);
4977 Dialog * createGuiTexInfo(GuiView & lv);
4978 Dialog * createGuiToc(GuiView & lv);
4979 Dialog * createGuiThesaurus(GuiView & lv);
4980 Dialog * createGuiViewSource(GuiView & lv);
4981 Dialog * createGuiWrap(GuiView & lv);
4982 Dialog * createGuiProgressView(GuiView & lv);
4986 Dialog * GuiView::build(string const & name)
4988 LASSERT(isValidName(name), return nullptr);
4990 Dialog * dialog = createDialog(*this, name);
4994 if (name == "aboutlyx")
4995 return createGuiAbout(*this);
4996 if (name == "bibtex")
4997 return createGuiBibtex(*this);
4998 if (name == "changes")
4999 return createGuiChanges(*this);
5000 if (name == "character")
5001 return createGuiCharacter(*this);
5002 if (name == "citation")
5003 return createGuiCitation(*this);
5004 if (name == "compare")
5005 return createGuiCompare(*this);
5006 if (name == "comparehistory")
5007 return createGuiCompareHistory(*this);
5008 if (name == "document")
5009 return createGuiDocument(*this);
5010 if (name == "errorlist")
5011 return createGuiErrorList(*this);
5012 if (name == "external")
5013 return createGuiExternal(*this);
5015 return createGuiShowFile(*this);
5016 if (name == "findreplace")
5017 return createGuiSearch(*this);
5018 if (name == "findreplaceadv")
5019 return createGuiSearchAdv(*this);
5020 if (name == "graphics")
5021 return createGuiGraphics(*this);
5022 if (name == "include")
5023 return createGuiInclude(*this);
5024 if (name == "index")
5025 return createGuiIndex(*this);
5026 if (name == "index_print")
5027 return createGuiPrintindex(*this);
5028 if (name == "listings")
5029 return createGuiListings(*this);
5031 return createGuiLog(*this);
5032 if (name == "lyxfiles")
5033 return createGuiLyXFiles(*this);
5034 if (name == "mathdelimiter")
5035 return createGuiDelimiter(*this);
5036 if (name == "mathmatrix")
5037 return createGuiMathMatrix(*this);
5039 return createGuiNote(*this);
5040 if (name == "paragraph")
5041 return createGuiParagraph(*this);
5042 if (name == "phantom")
5043 return createGuiPhantom(*this);
5044 if (name == "prefs")
5045 return createGuiPreferences(*this);
5047 return createGuiRef(*this);
5048 if (name == "sendto")
5049 return createGuiSendTo(*this);
5050 if (name == "spellchecker")
5051 return createGuiSpellchecker(*this);
5052 if (name == "symbols")
5053 return createGuiSymbols(*this);
5054 if (name == "tabularcreate")
5055 return createGuiTabularCreate(*this);
5056 if (name == "texinfo")
5057 return createGuiTexInfo(*this);
5058 if (name == "thesaurus")
5059 return createGuiThesaurus(*this);
5061 return createGuiToc(*this);
5062 if (name == "view-source")
5063 return createGuiViewSource(*this);
5065 return createGuiWrap(*this);
5066 if (name == "progress")
5067 return createGuiProgressView(*this);
5073 SEMenu::SEMenu(QWidget * parent)
5075 QAction * action = addAction(qt_("Disable Shell Escape"));
5076 connect(action, SIGNAL(triggered()),
5077 parent, SLOT(disableShellEscape()));
5080 } // namespace frontend
5083 #include "moc_GuiView.cpp"