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 // set custom application bars context menu, e.g. tool bar and menu bar
681 setContextMenuPolicy(Qt::CustomContextMenu);
682 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
683 SLOT(toolBarPopup(const QPoint &)));
685 // Forbid too small unresizable window because it can happen
686 // with some window manager under X11.
687 setMinimumSize(300, 200);
689 if (lyxrc.allow_geometry_session) {
690 // Now take care of session management.
695 // no session handling, default to a sane size.
696 setGeometry(50, 50, 690, 510);
699 // clear session data if any.
701 settings.remove("views");
711 void GuiView::disableShellEscape()
713 BufferView * bv = documentBufferView();
716 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
717 bv->buffer().params().shell_escape = false;
718 bv->processUpdateFlags(Update::Force);
722 void GuiView::checkCancelBackground()
724 docstring const ttl = _("Cancel Export?");
725 docstring const msg = _("Do you want to cancel the background export process?");
727 Alert::prompt(ttl, msg, 1, 1,
728 _("&Cancel export"), _("Co&ntinue"));
730 Systemcall::killscript();
734 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
736 QVector<GuiWorkArea*> areas;
737 for (int i = 0; i < tabWorkAreaCount(); i++) {
738 TabWorkArea* ta = tabWorkArea(i);
739 for (int u = 0; u < ta->count(); u++) {
740 areas << ta->workArea(u);
746 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
747 string const & format)
749 docstring const fmt = theFormats().prettyName(format);
752 case Buffer::ExportSuccess:
753 msg = bformat(_("Successful export to format: %1$s"), fmt);
755 case Buffer::ExportCancel:
756 msg = _("Document export cancelled.");
758 case Buffer::ExportError:
759 case Buffer::ExportNoPathToFormat:
760 case Buffer::ExportTexPathHasSpaces:
761 case Buffer::ExportConverterError:
762 msg = bformat(_("Error while exporting format: %1$s"), fmt);
764 case Buffer::PreviewSuccess:
765 msg = bformat(_("Successful preview of format: %1$s"), fmt);
767 case Buffer::PreviewError:
768 msg = bformat(_("Error while previewing format: %1$s"), fmt);
770 case Buffer::ExportKilled:
771 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
778 void GuiView::processingThreadStarted()
783 void GuiView::processingThreadFinished()
785 QFutureWatcher<Buffer::ExportStatus> const * watcher =
786 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
788 Buffer::ExportStatus const status = watcher->result();
789 handleExportStatus(this, status, d.processing_format);
792 BufferView const * const bv = currentBufferView();
793 if (bv && !bv->buffer().errorList("Export").empty()) {
798 bool const error = (status != Buffer::ExportSuccess &&
799 status != Buffer::PreviewSuccess &&
800 status != Buffer::ExportCancel);
802 ErrorList & el = bv->buffer().errorList(d.last_export_format);
803 // at this point, we do not know if buffer-view or
804 // master-buffer-view was called. If there was an export error,
805 // and the current buffer's error log is empty, we guess that
806 // it must be master-buffer-view that was called so we set
808 errors(d.last_export_format, el.empty());
813 void GuiView::autoSaveThreadFinished()
815 QFutureWatcher<docstring> const * watcher =
816 static_cast<QFutureWatcher<docstring> const *>(sender());
817 message(watcher->result());
822 void GuiView::saveLayout() const
825 settings.setValue("zoom_ratio", zoom_ratio_);
826 settings.setValue("devel_mode", devel_mode_);
827 settings.beginGroup("views");
828 settings.beginGroup(QString::number(id_));
829 #if defined(Q_WS_X11) || defined(QPA_XCB)
830 settings.setValue("pos", pos());
831 settings.setValue("size", size());
833 settings.setValue("geometry", saveGeometry());
835 settings.setValue("layout", saveState(0));
836 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
840 void GuiView::saveUISettings() const
844 // Save the toolbar private states
845 ToolbarMap::iterator end = d.toolbars_.end();
846 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
847 it->second->saveSession(settings);
848 // Now take care of all other dialogs
849 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
850 for (; it!= d.dialogs_.end(); ++it)
851 it->second->saveSession(settings);
855 bool GuiView::restoreLayout()
858 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
859 // Actual zoom value: default zoom + fractional offset
860 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
861 if (zoom < static_cast<int>(zoom_min_))
863 lyxrc.currentZoom = zoom;
864 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
865 settings.beginGroup("views");
866 settings.beginGroup(QString::number(id_));
867 QString const icon_key = "icon_size";
868 if (!settings.contains(icon_key))
871 //code below is skipped when when ~/.config/LyX is (re)created
872 setIconSize(d.iconSize(settings.value(icon_key).toString()));
874 #if defined(Q_WS_X11) || defined(QPA_XCB)
875 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
876 QSize size = settings.value("size", QSize(690, 510)).toSize();
880 // Work-around for bug #6034: the window ends up in an undetermined
881 // state when trying to restore a maximized window when it is
882 // already maximized.
883 if (!(windowState() & Qt::WindowMaximized))
884 if (!restoreGeometry(settings.value("geometry").toByteArray()))
885 setGeometry(50, 50, 690, 510);
887 // Make sure layout is correctly oriented.
888 setLayoutDirection(qApp->layoutDirection());
890 // Allow the toc and view-source dock widget to be restored if needed.
892 if ((dialog = findOrBuild("toc", true)))
893 // see bug 5082. At least setup title and enabled state.
894 // Visibility will be adjusted by restoreState below.
895 dialog->prepareView();
896 if ((dialog = findOrBuild("view-source", true)))
897 dialog->prepareView();
898 if ((dialog = findOrBuild("progress", true)))
899 dialog->prepareView();
901 if (!restoreState(settings.value("layout").toByteArray(), 0))
904 // init the toolbars that have not been restored
905 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
906 Toolbars::Infos::iterator end = guiApp->toolbars().end();
907 for (; cit != end; ++cit) {
908 GuiToolbar * tb = toolbar(cit->name);
909 if (tb && !tb->isRestored())
910 initToolbar(cit->name);
913 // update lock (all) toolbars positions
914 updateLockToolbars();
921 GuiToolbar * GuiView::toolbar(string const & name)
923 ToolbarMap::iterator it = d.toolbars_.find(name);
924 if (it != d.toolbars_.end())
927 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
932 void GuiView::updateLockToolbars()
934 toolbarsMovable_ = false;
935 for (ToolbarInfo const & info : guiApp->toolbars()) {
936 GuiToolbar * tb = toolbar(info.name);
937 if (tb && tb->isMovable())
938 toolbarsMovable_ = true;
943 void GuiView::constructToolbars()
945 ToolbarMap::iterator it = d.toolbars_.begin();
946 for (; it != d.toolbars_.end(); ++it)
950 // I don't like doing this here, but the standard toolbar
951 // destroys this object when it's destroyed itself (vfr)
952 d.layout_ = new LayoutBox(*this);
953 d.stack_widget_->addWidget(d.layout_);
954 d.layout_->move(0,0);
956 // extracts the toolbars from the backend
957 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
958 Toolbars::Infos::iterator end = guiApp->toolbars().end();
959 for (; cit != end; ++cit)
960 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
964 void GuiView::initToolbars()
966 // extracts the toolbars from the backend
967 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
968 Toolbars::Infos::iterator end = guiApp->toolbars().end();
969 for (; cit != end; ++cit)
970 initToolbar(cit->name);
974 void GuiView::initToolbar(string const & name)
976 GuiToolbar * tb = toolbar(name);
979 int const visibility = guiApp->toolbars().defaultVisibility(name);
980 bool newline = !(visibility & Toolbars::SAMEROW);
981 tb->setVisible(false);
982 tb->setVisibility(visibility);
984 if (visibility & Toolbars::TOP) {
986 addToolBarBreak(Qt::TopToolBarArea);
987 addToolBar(Qt::TopToolBarArea, tb);
990 if (visibility & Toolbars::BOTTOM) {
992 addToolBarBreak(Qt::BottomToolBarArea);
993 addToolBar(Qt::BottomToolBarArea, tb);
996 if (visibility & Toolbars::LEFT) {
998 addToolBarBreak(Qt::LeftToolBarArea);
999 addToolBar(Qt::LeftToolBarArea, tb);
1002 if (visibility & Toolbars::RIGHT) {
1004 addToolBarBreak(Qt::RightToolBarArea);
1005 addToolBar(Qt::RightToolBarArea, tb);
1008 if (visibility & Toolbars::ON)
1009 tb->setVisible(true);
1011 tb->setMovable(true);
1015 TocModels & GuiView::tocModels()
1017 return d.toc_models_;
1021 void GuiView::setFocus()
1023 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1024 QMainWindow::setFocus();
1028 bool GuiView::hasFocus() const
1030 if (currentWorkArea())
1031 return currentWorkArea()->hasFocus();
1032 if (currentMainWorkArea())
1033 return currentMainWorkArea()->hasFocus();
1034 return d.bg_widget_->hasFocus();
1038 void GuiView::focusInEvent(QFocusEvent * e)
1040 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1041 QMainWindow::focusInEvent(e);
1042 // Make sure guiApp points to the correct view.
1043 guiApp->setCurrentView(this);
1044 if (currentWorkArea())
1045 currentWorkArea()->setFocus();
1046 else if (currentMainWorkArea())
1047 currentMainWorkArea()->setFocus();
1049 d.bg_widget_->setFocus();
1053 void GuiView::showEvent(QShowEvent * e)
1055 LYXERR(Debug::GUI, "Passed Geometry "
1056 << size().height() << "x" << size().width()
1057 << "+" << pos().x() << "+" << pos().y());
1059 if (d.splitter_->count() == 0)
1060 // No work area, switch to the background widget.
1064 QMainWindow::showEvent(e);
1068 bool GuiView::closeScheduled()
1075 bool GuiView::prepareAllBuffersForLogout()
1077 Buffer * first = theBufferList().first();
1081 // First, iterate over all buffers and ask the users if unsaved
1082 // changes should be saved.
1083 // We cannot use a for loop as the buffer list cycles.
1086 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1088 b = theBufferList().next(b);
1089 } while (b != first);
1091 // Next, save session state
1092 // When a view/window was closed before without quitting LyX, there
1093 // are already entries in the lastOpened list.
1094 theSession().lastOpened().clear();
1101 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1102 ** is responsibility of the container (e.g., dialog)
1104 void GuiView::closeEvent(QCloseEvent * close_event)
1106 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1108 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1109 Alert::warning(_("Exit LyX"),
1110 _("LyX could not be closed because documents are being processed by LyX."));
1111 close_event->setAccepted(false);
1115 // If the user pressed the x (so we didn't call closeView
1116 // programmatically), we want to clear all existing entries.
1118 theSession().lastOpened().clear();
1123 // it can happen that this event arrives without selecting the view,
1124 // e.g. when clicking the close button on a background window.
1126 if (!closeWorkAreaAll()) {
1128 close_event->ignore();
1132 // Make sure that nothing will use this to be closed View.
1133 guiApp->unregisterView(this);
1135 if (isFullScreen()) {
1136 // Switch off fullscreen before closing.
1141 // Make sure the timer time out will not trigger a statusbar update.
1142 d.statusbar_timer_.stop();
1144 // Saving fullscreen requires additional tweaks in the toolbar code.
1145 // It wouldn't also work under linux natively.
1146 if (lyxrc.allow_geometry_session) {
1151 close_event->accept();
1155 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1157 if (event->mimeData()->hasUrls())
1159 /// \todo Ask lyx-devel is this is enough:
1160 /// if (event->mimeData()->hasFormat("text/plain"))
1161 /// event->acceptProposedAction();
1165 void GuiView::dropEvent(QDropEvent * event)
1167 QList<QUrl> files = event->mimeData()->urls();
1168 if (files.isEmpty())
1171 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1172 for (int i = 0; i != files.size(); ++i) {
1173 string const file = os::internal_path(fromqstr(
1174 files.at(i).toLocalFile()));
1178 string const ext = support::getExtension(file);
1179 vector<const Format *> found_formats;
1181 // Find all formats that have the correct extension.
1182 vector<const Format *> const & import_formats
1183 = theConverters().importableFormats();
1184 vector<const Format *>::const_iterator it = import_formats.begin();
1185 for (; it != import_formats.end(); ++it)
1186 if ((*it)->hasExtension(ext))
1187 found_formats.push_back(*it);
1190 if (found_formats.size() >= 1) {
1191 if (found_formats.size() > 1) {
1192 //FIXME: show a dialog to choose the correct importable format
1193 LYXERR(Debug::FILES,
1194 "Multiple importable formats found, selecting first");
1196 string const arg = found_formats[0]->name() + " " + file;
1197 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1200 //FIXME: do we have to explicitly check whether it's a lyx file?
1201 LYXERR(Debug::FILES,
1202 "No formats found, trying to open it as a lyx file");
1203 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1205 // add the functions to the queue
1206 guiApp->addToFuncRequestQueue(cmd);
1209 // now process the collected functions. We perform the events
1210 // asynchronously. This prevents potential problems in case the
1211 // BufferView is closed within an event.
1212 guiApp->processFuncRequestQueueAsync();
1216 void GuiView::message(docstring const & str)
1218 if (ForkedProcess::iAmAChild())
1221 // call is moved to GUI-thread by GuiProgress
1222 d.progress_->appendMessage(toqstr(str));
1226 void GuiView::clearMessageText()
1228 message(docstring());
1232 void GuiView::updateStatusBarMessage(QString const & str)
1234 statusBar()->showMessage(str);
1235 d.statusbar_timer_.stop();
1236 d.statusbar_timer_.start(3000);
1240 void GuiView::clearMessage()
1242 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1243 // the hasFocus function mostly returns false, even if the focus is on
1244 // a workarea in this view.
1248 d.statusbar_timer_.stop();
1252 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1254 if (wa != d.current_work_area_
1255 || wa->bufferView().buffer().isInternal())
1257 Buffer const & buf = wa->bufferView().buffer();
1258 // Set the windows title
1259 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1260 if (buf.notifiesExternalModification()) {
1261 title = bformat(_("%1$s (modified externally)"), title);
1262 // If the external modification status has changed, then maybe the status of
1263 // buffer-save has changed too.
1267 title += from_ascii(" - LyX");
1269 setWindowTitle(toqstr(title));
1270 // Sets the path for the window: this is used by OSX to
1271 // allow a context click on the title bar showing a menu
1272 // with the path up to the file
1273 setWindowFilePath(toqstr(buf.absFileName()));
1274 // Tell Qt whether the current document is changed
1275 setWindowModified(!buf.isClean());
1277 if (buf.params().shell_escape)
1278 shell_escape_->show();
1280 shell_escape_->hide();
1282 if (buf.hasReadonlyFlag())
1287 if (buf.lyxvc().inUse()) {
1288 version_control_->show();
1289 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1291 version_control_->hide();
1295 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1297 if (d.current_work_area_)
1298 // disconnect the current work area from all slots
1299 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1301 disconnectBufferView();
1302 connectBufferView(wa->bufferView());
1303 connectBuffer(wa->bufferView().buffer());
1304 d.current_work_area_ = wa;
1305 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1306 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1307 QObject::connect(wa, SIGNAL(busy(bool)),
1308 this, SLOT(setBusy(bool)));
1309 // connection of a signal to a signal
1310 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1311 this, SIGNAL(bufferViewChanged()));
1312 Q_EMIT updateWindowTitle(wa);
1313 Q_EMIT bufferViewChanged();
1317 void GuiView::onBufferViewChanged()
1320 // Buffer-dependent dialogs must be updated. This is done here because
1321 // some dialogs require buffer()->text.
1326 void GuiView::on_lastWorkAreaRemoved()
1329 // We already are in a close event. Nothing more to do.
1332 if (d.splitter_->count() > 1)
1333 // We have a splitter so don't close anything.
1336 // Reset and updates the dialogs.
1337 Q_EMIT bufferViewChanged();
1342 if (lyxrc.open_buffers_in_tabs)
1343 // Nothing more to do, the window should stay open.
1346 if (guiApp->viewIds().size() > 1) {
1352 // On Mac we also close the last window because the application stay
1353 // resident in memory. On other platforms we don't close the last
1354 // window because this would quit the application.
1360 void GuiView::updateStatusBar()
1362 // let the user see the explicit message
1363 if (d.statusbar_timer_.isActive())
1370 void GuiView::showMessage()
1374 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1375 if (msg.isEmpty()) {
1376 BufferView const * bv = currentBufferView();
1378 msg = toqstr(bv->cursor().currentState(devel_mode_));
1380 msg = qt_("Welcome to LyX!");
1382 statusBar()->showMessage(msg);
1386 bool GuiView::event(QEvent * e)
1390 // Useful debug code:
1391 //case QEvent::ActivationChange:
1392 //case QEvent::WindowDeactivate:
1393 //case QEvent::Paint:
1394 //case QEvent::Enter:
1395 //case QEvent::Leave:
1396 //case QEvent::HoverEnter:
1397 //case QEvent::HoverLeave:
1398 //case QEvent::HoverMove:
1399 //case QEvent::StatusTip:
1400 //case QEvent::DragEnter:
1401 //case QEvent::DragLeave:
1402 //case QEvent::Drop:
1405 case QEvent::WindowStateChange: {
1406 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1407 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1408 bool result = QMainWindow::event(e);
1409 bool nfstate = (windowState() & Qt::WindowFullScreen);
1410 if (!ofstate && nfstate) {
1411 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1412 // switch to full-screen state
1413 if (lyxrc.full_screen_statusbar)
1414 statusBar()->hide();
1415 if (lyxrc.full_screen_menubar)
1417 if (lyxrc.full_screen_toolbars) {
1418 ToolbarMap::iterator end = d.toolbars_.end();
1419 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1420 if (it->second->isVisibiltyOn() && it->second->isVisible())
1423 for (int i = 0; i != d.splitter_->count(); ++i)
1424 d.tabWorkArea(i)->setFullScreen(true);
1425 setContentsMargins(-2, -2, -2, -2);
1427 hideDialogs("prefs", nullptr);
1428 } else if (ofstate && !nfstate) {
1429 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1430 // switch back from full-screen state
1431 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1432 statusBar()->show();
1433 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1435 if (lyxrc.full_screen_toolbars) {
1436 ToolbarMap::iterator end = d.toolbars_.end();
1437 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1438 if (it->second->isVisibiltyOn() && !it->second->isVisible())
1442 for (int i = 0; i != d.splitter_->count(); ++i)
1443 d.tabWorkArea(i)->setFullScreen(false);
1444 setContentsMargins(0, 0, 0, 0);
1448 case QEvent::WindowActivate: {
1449 GuiView * old_view = guiApp->currentView();
1450 if (this == old_view) {
1452 return QMainWindow::event(e);
1454 if (old_view && old_view->currentBufferView()) {
1455 // save current selection to the selection buffer to allow
1456 // middle-button paste in this window.
1457 cap::saveSelection(old_view->currentBufferView()->cursor());
1459 guiApp->setCurrentView(this);
1460 if (d.current_work_area_)
1461 on_currentWorkAreaChanged(d.current_work_area_);
1465 return QMainWindow::event(e);
1468 case QEvent::ShortcutOverride: {
1470 if (isFullScreen() && menuBar()->isHidden()) {
1471 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1472 // FIXME: we should also try to detect special LyX shortcut such as
1473 // Alt-P and Alt-M. Right now there is a hack in
1474 // GuiWorkArea::processKeySym() that hides again the menubar for
1476 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1478 return QMainWindow::event(e);
1481 return QMainWindow::event(e);
1485 return QMainWindow::event(e);
1489 void GuiView::resetWindowTitle()
1491 setWindowTitle(qt_("LyX"));
1494 bool GuiView::focusNextPrevChild(bool /*next*/)
1501 bool GuiView::busy() const
1507 void GuiView::setBusy(bool busy)
1509 bool const busy_before = busy_ > 0;
1510 busy ? ++busy_ : --busy_;
1511 if ((busy_ > 0) == busy_before)
1512 // busy state didn't change
1516 QApplication::setOverrideCursor(Qt::WaitCursor);
1519 QApplication::restoreOverrideCursor();
1524 void GuiView::resetCommandExecute()
1526 command_execute_ = false;
1531 double GuiView::pixelRatio() const
1533 #if QT_VERSION >= 0x050000
1534 return qt_scale_factor * devicePixelRatio();
1541 GuiWorkArea * GuiView::workArea(int index)
1543 if (TabWorkArea * twa = d.currentTabWorkArea())
1544 if (index < twa->count())
1545 return twa->workArea(index);
1550 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1552 if (currentWorkArea()
1553 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1554 return currentWorkArea();
1555 if (TabWorkArea * twa = d.currentTabWorkArea())
1556 return twa->workArea(buffer);
1561 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1563 // Automatically create a TabWorkArea if there are none yet.
1564 TabWorkArea * tab_widget = d.splitter_->count()
1565 ? d.currentTabWorkArea() : addTabWorkArea();
1566 return tab_widget->addWorkArea(buffer, *this);
1570 TabWorkArea * GuiView::addTabWorkArea()
1572 TabWorkArea * twa = new TabWorkArea;
1573 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1574 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1575 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1576 this, SLOT(on_lastWorkAreaRemoved()));
1578 d.splitter_->addWidget(twa);
1579 d.stack_widget_->setCurrentWidget(d.splitter_);
1584 GuiWorkArea const * GuiView::currentWorkArea() const
1586 return d.current_work_area_;
1590 GuiWorkArea * GuiView::currentWorkArea()
1592 return d.current_work_area_;
1596 GuiWorkArea const * GuiView::currentMainWorkArea() const
1598 if (!d.currentTabWorkArea())
1600 return d.currentTabWorkArea()->currentWorkArea();
1604 GuiWorkArea * GuiView::currentMainWorkArea()
1606 if (!d.currentTabWorkArea())
1608 return d.currentTabWorkArea()->currentWorkArea();
1612 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1614 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1616 d.current_work_area_ = nullptr;
1618 Q_EMIT bufferViewChanged();
1622 // FIXME: I've no clue why this is here and why it accesses
1623 // theGuiApp()->currentView, which might be 0 (bug 6464).
1624 // See also 27525 (vfr).
1625 if (theGuiApp()->currentView() == this
1626 && theGuiApp()->currentView()->currentWorkArea() == wa)
1629 if (currentBufferView())
1630 cap::saveSelection(currentBufferView()->cursor());
1632 theGuiApp()->setCurrentView(this);
1633 d.current_work_area_ = wa;
1635 // We need to reset this now, because it will need to be
1636 // right if the tabWorkArea gets reset in the for loop. We
1637 // will change it back if we aren't in that case.
1638 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1639 d.current_main_work_area_ = wa;
1641 for (int i = 0; i != d.splitter_->count(); ++i) {
1642 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1643 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1644 << ", Current main wa: " << currentMainWorkArea());
1649 d.current_main_work_area_ = old_cmwa;
1651 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1652 on_currentWorkAreaChanged(wa);
1653 BufferView & bv = wa->bufferView();
1654 bv.cursor().fixIfBroken();
1656 wa->setUpdatesEnabled(true);
1657 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1661 void GuiView::removeWorkArea(GuiWorkArea * wa)
1663 LASSERT(wa, return);
1664 if (wa == d.current_work_area_) {
1666 disconnectBufferView();
1667 d.current_work_area_ = nullptr;
1668 d.current_main_work_area_ = nullptr;
1671 bool found_twa = false;
1672 for (int i = 0; i != d.splitter_->count(); ++i) {
1673 TabWorkArea * twa = d.tabWorkArea(i);
1674 if (twa->removeWorkArea(wa)) {
1675 // Found in this tab group, and deleted the GuiWorkArea.
1677 if (twa->count() != 0) {
1678 if (d.current_work_area_ == nullptr)
1679 // This means that we are closing the current GuiWorkArea, so
1680 // switch to the next GuiWorkArea in the found TabWorkArea.
1681 setCurrentWorkArea(twa->currentWorkArea());
1683 // No more WorkAreas in this tab group, so delete it.
1690 // It is not a tabbed work area (i.e., the search work area), so it
1691 // should be deleted by other means.
1692 LASSERT(found_twa, return);
1694 if (d.current_work_area_ == nullptr) {
1695 if (d.splitter_->count() != 0) {
1696 TabWorkArea * twa = d.currentTabWorkArea();
1697 setCurrentWorkArea(twa->currentWorkArea());
1699 // No more work areas, switch to the background widget.
1700 setCurrentWorkArea(nullptr);
1706 LayoutBox * GuiView::getLayoutDialog() const
1712 void GuiView::updateLayoutList()
1715 d.layout_->updateContents(false);
1719 void GuiView::updateToolbars()
1721 ToolbarMap::iterator end = d.toolbars_.end();
1722 if (d.current_work_area_) {
1724 if (d.current_work_area_->bufferView().cursor().inMathed()
1725 && !d.current_work_area_->bufferView().cursor().inRegexped())
1726 context |= Toolbars::MATH;
1727 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1728 context |= Toolbars::TABLE;
1729 if (currentBufferView()->buffer().areChangesPresent()
1730 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1731 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1732 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1733 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1734 context |= Toolbars::REVIEW;
1735 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1736 context |= Toolbars::MATHMACROTEMPLATE;
1737 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1738 context |= Toolbars::IPA;
1739 if (command_execute_)
1740 context |= Toolbars::MINIBUFFER;
1741 if (minibuffer_focus_) {
1742 context |= Toolbars::MINIBUFFER_FOCUS;
1743 minibuffer_focus_ = false;
1746 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1747 it->second->update(context);
1749 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1750 it->second->update();
1754 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1756 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1757 LASSERT(newBuffer, return);
1759 GuiWorkArea * wa = workArea(*newBuffer);
1760 if (wa == nullptr) {
1762 newBuffer->masterBuffer()->updateBuffer();
1764 wa = addWorkArea(*newBuffer);
1765 // scroll to the position when the BufferView was last closed
1766 if (lyxrc.use_lastfilepos) {
1767 LastFilePosSection::FilePos filepos =
1768 theSession().lastFilePos().load(newBuffer->fileName());
1769 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1772 //Disconnect the old buffer...there's no new one.
1775 connectBuffer(*newBuffer);
1776 connectBufferView(wa->bufferView());
1778 setCurrentWorkArea(wa);
1782 void GuiView::connectBuffer(Buffer & buf)
1784 buf.setGuiDelegate(this);
1788 void GuiView::disconnectBuffer()
1790 if (d.current_work_area_)
1791 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1795 void GuiView::connectBufferView(BufferView & bv)
1797 bv.setGuiDelegate(this);
1801 void GuiView::disconnectBufferView()
1803 if (d.current_work_area_)
1804 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1808 void GuiView::errors(string const & error_type, bool from_master)
1810 BufferView const * const bv = currentBufferView();
1814 ErrorList const & el = from_master ?
1815 bv->buffer().masterBuffer()->errorList(error_type) :
1816 bv->buffer().errorList(error_type);
1821 string err = error_type;
1823 err = "from_master|" + error_type;
1824 showDialog("errorlist", err);
1828 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1830 d.toc_models_.updateItem(toqstr(type), dit);
1834 void GuiView::structureChanged()
1836 // This is called from the Buffer, which has no way to ensure that cursors
1837 // in BufferView remain valid.
1838 if (documentBufferView())
1839 documentBufferView()->cursor().sanitize();
1840 // FIXME: This is slightly expensive, though less than the tocBackend update
1841 // (#9880). This also resets the view in the Toc Widget (#6675).
1842 d.toc_models_.reset(documentBufferView());
1843 // Navigator needs more than a simple update in this case. It needs to be
1845 updateDialog("toc", "");
1849 void GuiView::updateDialog(string const & name, string const & sdata)
1851 if (!isDialogVisible(name))
1854 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1855 if (it == d.dialogs_.end())
1858 Dialog * const dialog = it->second.get();
1859 if (dialog->isVisibleView())
1860 dialog->initialiseParams(sdata);
1864 BufferView * GuiView::documentBufferView()
1866 return currentMainWorkArea()
1867 ? ¤tMainWorkArea()->bufferView()
1872 BufferView const * GuiView::documentBufferView() const
1874 return currentMainWorkArea()
1875 ? ¤tMainWorkArea()->bufferView()
1880 BufferView * GuiView::currentBufferView()
1882 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1886 BufferView const * GuiView::currentBufferView() const
1888 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1892 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1893 Buffer const * orig, Buffer * clone)
1895 bool const success = clone->autoSave();
1897 busyBuffers.remove(orig);
1899 ? _("Automatic save done.")
1900 : _("Automatic save failed!");
1904 void GuiView::autoSave()
1906 LYXERR(Debug::INFO, "Running autoSave()");
1908 Buffer * buffer = documentBufferView()
1909 ? &documentBufferView()->buffer() : nullptr;
1911 resetAutosaveTimers();
1915 GuiViewPrivate::busyBuffers.insert(buffer);
1916 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1917 buffer, buffer->cloneBufferOnly());
1918 d.autosave_watcher_.setFuture(f);
1919 resetAutosaveTimers();
1923 void GuiView::resetAutosaveTimers()
1926 d.autosave_timeout_.restart();
1930 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1933 Buffer * buf = currentBufferView()
1934 ? ¤tBufferView()->buffer() : nullptr;
1935 Buffer * doc_buffer = documentBufferView()
1936 ? &(documentBufferView()->buffer()) : nullptr;
1939 /* In LyX/Mac, when a dialog is open, the menus of the
1940 application can still be accessed without giving focus to
1941 the main window. In this case, we want to disable the menu
1942 entries that are buffer-related.
1943 This code must not be used on Linux and Windows, since it
1944 would disable buffer-related entries when hovering over the
1945 menu (see bug #9574).
1947 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1953 // Check whether we need a buffer
1954 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1955 // no, exit directly
1956 flag.message(from_utf8(N_("Command not allowed with"
1957 "out any document open")));
1958 flag.setEnabled(false);
1962 if (cmd.origin() == FuncRequest::TOC) {
1963 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1964 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1965 flag.setEnabled(false);
1969 switch(cmd.action()) {
1970 case LFUN_BUFFER_IMPORT:
1973 case LFUN_MASTER_BUFFER_EXPORT:
1975 && (doc_buffer->parent() != nullptr
1976 || doc_buffer->hasChildren())
1977 && !d.processing_thread_watcher_.isRunning()
1978 // this launches a dialog, which would be in the wrong Buffer
1979 && !(::lyx::operator==(cmd.argument(), "custom"));
1982 case LFUN_MASTER_BUFFER_UPDATE:
1983 case LFUN_MASTER_BUFFER_VIEW:
1985 && (doc_buffer->parent() != nullptr
1986 || doc_buffer->hasChildren())
1987 && !d.processing_thread_watcher_.isRunning();
1990 case LFUN_BUFFER_UPDATE:
1991 case LFUN_BUFFER_VIEW: {
1992 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1996 string format = to_utf8(cmd.argument());
1997 if (cmd.argument().empty())
1998 format = doc_buffer->params().getDefaultOutputFormat();
1999 enable = doc_buffer->params().isExportable(format, true);
2003 case LFUN_BUFFER_RELOAD:
2004 enable = doc_buffer && !doc_buffer->isUnnamed()
2005 && doc_buffer->fileName().exists()
2006 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2009 case LFUN_BUFFER_RESET_EXPORT:
2010 enable = doc_buffer != nullptr;
2013 case LFUN_BUFFER_CHILD_OPEN:
2014 enable = doc_buffer != nullptr;
2017 case LFUN_MASTER_BUFFER_FORALL: {
2018 if (doc_buffer == nullptr) {
2019 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2023 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2024 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2025 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2030 for (Buffer * buf : doc_buffer->allRelatives()) {
2031 GuiWorkArea * wa = workArea(*buf);
2034 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2035 enable = flag.enabled();
2042 case LFUN_BUFFER_WRITE:
2043 enable = doc_buffer && (doc_buffer->isUnnamed()
2044 || (!doc_buffer->isClean()
2045 || cmd.argument() == "force"));
2048 //FIXME: This LFUN should be moved to GuiApplication.
2049 case LFUN_BUFFER_WRITE_ALL: {
2050 // We enable the command only if there are some modified buffers
2051 Buffer * first = theBufferList().first();
2056 // We cannot use a for loop as the buffer list is a cycle.
2058 if (!b->isClean()) {
2062 b = theBufferList().next(b);
2063 } while (b != first);
2067 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2068 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2071 case LFUN_BUFFER_EXPORT: {
2072 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2076 return doc_buffer->getStatus(cmd, flag);
2079 case LFUN_BUFFER_EXPORT_AS:
2080 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2085 case LFUN_BUFFER_WRITE_AS:
2086 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2087 enable = doc_buffer != nullptr;
2090 case LFUN_EXPORT_CANCEL:
2091 enable = d.processing_thread_watcher_.isRunning();
2094 case LFUN_BUFFER_CLOSE:
2095 case LFUN_VIEW_CLOSE:
2096 enable = doc_buffer != nullptr;
2099 case LFUN_BUFFER_CLOSE_ALL:
2100 enable = theBufferList().last() != theBufferList().first();
2103 case LFUN_BUFFER_CHKTEX: {
2104 // hide if we have no checktex command
2105 if (lyxrc.chktex_command.empty()) {
2106 flag.setUnknown(true);
2110 if (!doc_buffer || !doc_buffer->params().isLatex()
2111 || d.processing_thread_watcher_.isRunning()) {
2112 // grey out, don't hide
2120 case LFUN_VIEW_SPLIT:
2121 if (cmd.getArg(0) == "vertical")
2122 enable = doc_buffer && (d.splitter_->count() == 1 ||
2123 d.splitter_->orientation() == Qt::Vertical);
2125 enable = doc_buffer && (d.splitter_->count() == 1 ||
2126 d.splitter_->orientation() == Qt::Horizontal);
2129 case LFUN_TAB_GROUP_CLOSE:
2130 enable = d.tabWorkAreaCount() > 1;
2133 case LFUN_DEVEL_MODE_TOGGLE:
2134 flag.setOnOff(devel_mode_);
2137 case LFUN_TOOLBAR_TOGGLE: {
2138 string const name = cmd.getArg(0);
2139 if (GuiToolbar * t = toolbar(name))
2140 flag.setOnOff(t->isVisible());
2143 docstring const msg =
2144 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2150 case LFUN_TOOLBAR_MOVABLE: {
2151 string const name = cmd.getArg(0);
2152 // use negation since locked == !movable
2154 // toolbar name * locks all toolbars
2155 flag.setOnOff(!toolbarsMovable_);
2156 else if (GuiToolbar * t = toolbar(name))
2157 flag.setOnOff(!(t->isMovable()));
2160 docstring const msg =
2161 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2167 case LFUN_ICON_SIZE:
2168 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2171 case LFUN_DROP_LAYOUTS_CHOICE:
2172 enable = buf != nullptr;
2175 case LFUN_UI_TOGGLE:
2176 flag.setOnOff(isFullScreen());
2179 case LFUN_DIALOG_DISCONNECT_INSET:
2182 case LFUN_DIALOG_HIDE:
2183 // FIXME: should we check if the dialog is shown?
2186 case LFUN_DIALOG_TOGGLE:
2187 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2190 case LFUN_DIALOG_SHOW: {
2191 string const name = cmd.getArg(0);
2193 enable = name == "aboutlyx"
2194 || name == "file" //FIXME: should be removed.
2195 || name == "lyxfiles"
2197 || name == "texinfo"
2198 || name == "progress"
2199 || name == "compare";
2200 else if (name == "character" || name == "symbols"
2201 || name == "mathdelimiter" || name == "mathmatrix") {
2202 if (!buf || buf->isReadonly())
2205 Cursor const & cur = currentBufferView()->cursor();
2206 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2209 else if (name == "latexlog")
2210 enable = FileName(doc_buffer->logName()).isReadableFile();
2211 else if (name == "spellchecker")
2212 enable = theSpellChecker()
2213 && !doc_buffer->isReadonly()
2214 && !doc_buffer->text().empty();
2215 else if (name == "vclog")
2216 enable = doc_buffer->lyxvc().inUse();
2220 case LFUN_DIALOG_UPDATE: {
2221 string const name = cmd.getArg(0);
2223 enable = name == "prefs";
2227 case LFUN_COMMAND_EXECUTE:
2229 case LFUN_MENU_OPEN:
2230 // Nothing to check.
2233 case LFUN_COMPLETION_INLINE:
2234 if (!d.current_work_area_
2235 || !d.current_work_area_->completer().inlinePossible(
2236 currentBufferView()->cursor()))
2240 case LFUN_COMPLETION_POPUP:
2241 if (!d.current_work_area_
2242 || !d.current_work_area_->completer().popupPossible(
2243 currentBufferView()->cursor()))
2248 if (!d.current_work_area_
2249 || !d.current_work_area_->completer().inlinePossible(
2250 currentBufferView()->cursor()))
2254 case LFUN_COMPLETION_ACCEPT:
2255 if (!d.current_work_area_
2256 || (!d.current_work_area_->completer().popupVisible()
2257 && !d.current_work_area_->completer().inlineVisible()
2258 && !d.current_work_area_->completer().completionAvailable()))
2262 case LFUN_COMPLETION_CANCEL:
2263 if (!d.current_work_area_
2264 || (!d.current_work_area_->completer().popupVisible()
2265 && !d.current_work_area_->completer().inlineVisible()))
2269 case LFUN_BUFFER_ZOOM_OUT:
2270 case LFUN_BUFFER_ZOOM_IN: {
2271 // only diff between these two is that the default for ZOOM_OUT
2273 bool const neg_zoom =
2274 convert<int>(cmd.argument()) < 0 ||
2275 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2276 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2277 docstring const msg =
2278 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2282 enable = doc_buffer;
2286 case LFUN_BUFFER_ZOOM: {
2287 bool const less_than_min_zoom =
2288 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2289 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2290 docstring const msg =
2291 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2296 enable = doc_buffer;
2300 case LFUN_BUFFER_MOVE_NEXT:
2301 case LFUN_BUFFER_MOVE_PREVIOUS:
2302 // we do not cycle when moving
2303 case LFUN_BUFFER_NEXT:
2304 case LFUN_BUFFER_PREVIOUS:
2305 // because we cycle, it doesn't matter whether on first or last
2306 enable = (d.currentTabWorkArea()->count() > 1);
2308 case LFUN_BUFFER_SWITCH:
2309 // toggle on the current buffer, but do not toggle off
2310 // the other ones (is that a good idea?)
2312 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2313 flag.setOnOff(true);
2316 case LFUN_VC_REGISTER:
2317 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2319 case LFUN_VC_RENAME:
2320 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2323 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2325 case LFUN_VC_CHECK_IN:
2326 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2328 case LFUN_VC_CHECK_OUT:
2329 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2331 case LFUN_VC_LOCKING_TOGGLE:
2332 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2333 && doc_buffer->lyxvc().lockingToggleEnabled();
2334 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2336 case LFUN_VC_REVERT:
2337 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2338 && !doc_buffer->hasReadonlyFlag();
2340 case LFUN_VC_UNDO_LAST:
2341 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2343 case LFUN_VC_REPO_UPDATE:
2344 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2346 case LFUN_VC_COMMAND: {
2347 if (cmd.argument().empty())
2349 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2353 case LFUN_VC_COMPARE:
2354 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2357 case LFUN_SERVER_GOTO_FILE_ROW:
2358 case LFUN_LYX_ACTIVATE:
2359 case LFUN_WINDOW_RAISE:
2361 case LFUN_FORWARD_SEARCH:
2362 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2365 case LFUN_FILE_INSERT_PLAINTEXT:
2366 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2367 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2370 case LFUN_SPELLING_CONTINUOUSLY:
2371 flag.setOnOff(lyxrc.spellcheck_continuously);
2374 case LFUN_CITATION_OPEN:
2383 flag.setEnabled(false);
2389 static FileName selectTemplateFile()
2391 FileDialog dlg(qt_("Select template file"));
2392 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2393 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2395 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2396 QStringList(qt_("LyX Documents (*.lyx)")));
2398 if (result.first == FileDialog::Later)
2400 if (result.second.isEmpty())
2402 return FileName(fromqstr(result.second));
2406 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2410 Buffer * newBuffer = nullptr;
2412 newBuffer = checkAndLoadLyXFile(filename);
2413 } catch (ExceptionMessage const & e) {
2420 message(_("Document not loaded."));
2424 setBuffer(newBuffer);
2425 newBuffer->errors("Parse");
2428 theSession().lastFiles().add(filename);
2429 theSession().writeFile();
2436 void GuiView::openDocument(string const & fname)
2438 string initpath = lyxrc.document_path;
2440 if (documentBufferView()) {
2441 string const trypath = documentBufferView()->buffer().filePath();
2442 // If directory is writeable, use this as default.
2443 if (FileName(trypath).isDirWritable())
2449 if (fname.empty()) {
2450 FileDialog dlg(qt_("Select document to open"));
2451 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2452 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2454 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2455 FileDialog::Result result =
2456 dlg.open(toqstr(initpath), filter);
2458 if (result.first == FileDialog::Later)
2461 filename = fromqstr(result.second);
2463 // check selected filename
2464 if (filename.empty()) {
2465 message(_("Canceled."));
2471 // get absolute path of file and add ".lyx" to the filename if
2473 FileName const fullname =
2474 fileSearch(string(), filename, "lyx", support::may_not_exist);
2475 if (!fullname.empty())
2476 filename = fullname.absFileName();
2478 if (!fullname.onlyPath().isDirectory()) {
2479 Alert::warning(_("Invalid filename"),
2480 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2481 from_utf8(fullname.absFileName())));
2485 // if the file doesn't exist and isn't already open (bug 6645),
2486 // let the user create one
2487 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2488 !LyXVC::file_not_found_hook(fullname)) {
2489 // the user specifically chose this name. Believe him.
2490 Buffer * const b = newFile(filename, string(), true);
2496 docstring const disp_fn = makeDisplayPath(filename);
2497 message(bformat(_("Opening document %1$s..."), disp_fn));
2500 Buffer * buf = loadDocument(fullname);
2502 str2 = bformat(_("Document %1$s opened."), disp_fn);
2503 if (buf->lyxvc().inUse())
2504 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2505 " " + _("Version control detected.");
2507 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2512 // FIXME: clean that
2513 static bool import(GuiView * lv, FileName const & filename,
2514 string const & format, ErrorList & errorList)
2516 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2518 string loader_format;
2519 vector<string> loaders = theConverters().loaders();
2520 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2521 vector<string>::const_iterator it = loaders.begin();
2522 vector<string>::const_iterator en = loaders.end();
2523 for (; it != en; ++it) {
2524 if (!theConverters().isReachable(format, *it))
2527 string const tofile =
2528 support::changeExtension(filename.absFileName(),
2529 theFormats().extension(*it));
2530 if (theConverters().convert(nullptr, filename, FileName(tofile),
2531 filename, format, *it, errorList) != Converters::SUCCESS)
2533 loader_format = *it;
2536 if (loader_format.empty()) {
2537 frontend::Alert::error(_("Couldn't import file"),
2538 bformat(_("No information for importing the format %1$s."),
2539 theFormats().prettyName(format)));
2543 loader_format = format;
2545 if (loader_format == "lyx") {
2546 Buffer * buf = lv->loadDocument(lyxfile);
2550 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2554 bool as_paragraphs = loader_format == "textparagraph";
2555 string filename2 = (loader_format == format) ? filename.absFileName()
2556 : support::changeExtension(filename.absFileName(),
2557 theFormats().extension(loader_format));
2558 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2560 guiApp->setCurrentView(lv);
2561 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2568 void GuiView::importDocument(string const & argument)
2571 string filename = split(argument, format, ' ');
2573 LYXERR(Debug::INFO, format << " file: " << filename);
2575 // need user interaction
2576 if (filename.empty()) {
2577 string initpath = lyxrc.document_path;
2578 if (documentBufferView()) {
2579 string const trypath = documentBufferView()->buffer().filePath();
2580 // If directory is writeable, use this as default.
2581 if (FileName(trypath).isDirWritable())
2585 docstring const text = bformat(_("Select %1$s file to import"),
2586 theFormats().prettyName(format));
2588 FileDialog dlg(toqstr(text));
2589 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2590 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2592 docstring filter = theFormats().prettyName(format);
2595 filter += from_utf8(theFormats().extensions(format));
2598 FileDialog::Result result =
2599 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2601 if (result.first == FileDialog::Later)
2604 filename = fromqstr(result.second);
2606 // check selected filename
2607 if (filename.empty())
2608 message(_("Canceled."));
2611 if (filename.empty())
2614 // get absolute path of file
2615 FileName const fullname(support::makeAbsPath(filename));
2617 // Can happen if the user entered a path into the dialog
2619 if (fullname.onlyFileName().empty()) {
2620 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2621 "Aborting import."),
2622 from_utf8(fullname.absFileName()));
2623 frontend::Alert::error(_("File name error"), msg);
2624 message(_("Canceled."));
2629 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2631 // Check if the document already is open
2632 Buffer * buf = theBufferList().getBuffer(lyxfile);
2635 if (!closeBuffer()) {
2636 message(_("Canceled."));
2641 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2643 // if the file exists already, and we didn't do
2644 // -i lyx thefile.lyx, warn
2645 if (lyxfile.exists() && fullname != lyxfile) {
2647 docstring text = bformat(_("The document %1$s already exists.\n\n"
2648 "Do you want to overwrite that document?"), displaypath);
2649 int const ret = Alert::prompt(_("Overwrite document?"),
2650 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2653 message(_("Canceled."));
2658 message(bformat(_("Importing %1$s..."), displaypath));
2659 ErrorList errorList;
2660 if (import(this, fullname, format, errorList))
2661 message(_("imported."));
2663 message(_("file not imported!"));
2665 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2669 void GuiView::newDocument(string const & filename, string templatefile,
2672 FileName initpath(lyxrc.document_path);
2673 if (documentBufferView()) {
2674 FileName const trypath(documentBufferView()->buffer().filePath());
2675 // If directory is writeable, use this as default.
2676 if (trypath.isDirWritable())
2680 if (from_template) {
2681 if (templatefile.empty())
2682 templatefile = selectTemplateFile().absFileName();
2683 if (templatefile.empty())
2688 if (filename.empty())
2689 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2691 b = newFile(filename, templatefile, true);
2696 // If no new document could be created, it is unsure
2697 // whether there is a valid BufferView.
2698 if (currentBufferView())
2699 // Ensure the cursor is correctly positioned on screen.
2700 currentBufferView()->showCursor();
2704 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2706 BufferView * bv = documentBufferView();
2711 FileName filename(to_utf8(fname));
2712 if (filename.empty()) {
2713 // Launch a file browser
2715 string initpath = lyxrc.document_path;
2716 string const trypath = bv->buffer().filePath();
2717 // If directory is writeable, use this as default.
2718 if (FileName(trypath).isDirWritable())
2722 FileDialog dlg(qt_("Select LyX document to insert"));
2723 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2724 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2726 FileDialog::Result result = dlg.open(toqstr(initpath),
2727 QStringList(qt_("LyX Documents (*.lyx)")));
2729 if (result.first == FileDialog::Later)
2733 filename.set(fromqstr(result.second));
2735 // check selected filename
2736 if (filename.empty()) {
2737 // emit message signal.
2738 message(_("Canceled."));
2743 bv->insertLyXFile(filename, ignorelang);
2744 bv->buffer().errors("Parse");
2748 string const GuiView::getTemplatesPath(Buffer & b)
2750 // We start off with the user's templates path
2751 string result = addPath(package().user_support().absFileName(), "templates");
2752 // Check for the document language
2753 string const langcode = b.params().language->code();
2754 string const shortcode = langcode.substr(0, 2);
2755 if (!langcode.empty() && shortcode != "en") {
2756 string subpath = addPath(result, shortcode);
2757 string subpath_long = addPath(result, langcode);
2758 // If we have a subdirectory for the language already,
2760 FileName sp = FileName(subpath);
2761 if (sp.isDirectory())
2763 else if (FileName(subpath_long).isDirectory())
2764 result = subpath_long;
2766 // Ask whether we should create such a subdirectory
2767 docstring const text =
2768 bformat(_("It is suggested to save the template in a subdirectory\n"
2769 "appropriate to the document language (%1$s).\n"
2770 "This subdirectory does not exists yet.\n"
2771 "Do you want to create it?"),
2772 _(b.params().language->display()));
2773 if (Alert::prompt(_("Create Language Directory?"),
2774 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2775 // If the user agreed, we try to create it and report if this failed.
2776 if (!sp.createDirectory(0777))
2777 Alert::error(_("Subdirectory creation failed!"),
2778 _("Could not create subdirectory.\n"
2779 "The template will be saved in the parent directory."));
2785 // Do we have a layout category?
2786 string const cat = b.params().baseClass() ?
2787 b.params().baseClass()->category()
2790 string subpath = addPath(result, cat);
2791 // If we have a subdirectory for the category already,
2793 FileName sp = FileName(subpath);
2794 if (sp.isDirectory())
2797 // Ask whether we should create such a subdirectory
2798 docstring const text =
2799 bformat(_("It is suggested to save the template in a subdirectory\n"
2800 "appropriate to the layout category (%1$s).\n"
2801 "This subdirectory does not exists yet.\n"
2802 "Do you want to create it?"),
2804 if (Alert::prompt(_("Create Category Directory?"),
2805 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2806 // If the user agreed, we try to create it and report if this failed.
2807 if (!sp.createDirectory(0777))
2808 Alert::error(_("Subdirectory creation failed!"),
2809 _("Could not create subdirectory.\n"
2810 "The template will be saved in the parent directory."));
2820 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2822 FileName fname = b.fileName();
2823 FileName const oldname = fname;
2824 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2826 if (!newname.empty()) {
2829 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2831 fname = support::makeAbsPath(to_utf8(newname),
2832 oldname.onlyPath().absFileName());
2834 // Switch to this Buffer.
2837 // No argument? Ask user through dialog.
2839 QString const title = as_template ? qt_("Choose a filename to save template as")
2840 : qt_("Choose a filename to save document as");
2841 FileDialog dlg(title);
2842 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2843 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2845 if (!isLyXFileName(fname.absFileName()))
2846 fname.changeExtension(".lyx");
2848 string const path = as_template ?
2850 : fname.onlyPath().absFileName();
2851 FileDialog::Result result =
2852 dlg.save(toqstr(path),
2853 QStringList(qt_("LyX Documents (*.lyx)")),
2854 toqstr(fname.onlyFileName()));
2856 if (result.first == FileDialog::Later)
2859 fname.set(fromqstr(result.second));
2864 if (!isLyXFileName(fname.absFileName()))
2865 fname.changeExtension(".lyx");
2868 // fname is now the new Buffer location.
2870 // if there is already a Buffer open with this name, we do not want
2871 // to have another one. (the second test makes sure we're not just
2872 // trying to overwrite ourselves, which is fine.)
2873 if (theBufferList().exists(fname) && fname != oldname
2874 && theBufferList().getBuffer(fname) != &b) {
2875 docstring const text =
2876 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2877 "Please close it before attempting to overwrite it.\n"
2878 "Do you want to choose a new filename?"),
2879 from_utf8(fname.absFileName()));
2880 int const ret = Alert::prompt(_("Chosen File Already Open"),
2881 text, 0, 1, _("&Rename"), _("&Cancel"));
2883 case 0: return renameBuffer(b, docstring(), kind);
2884 case 1: return false;
2889 bool const existsLocal = fname.exists();
2890 bool const existsInVC = LyXVC::fileInVC(fname);
2891 if (existsLocal || existsInVC) {
2892 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2893 if (kind != LV_WRITE_AS && existsInVC) {
2894 // renaming to a name that is already in VC
2896 docstring text = bformat(_("The document %1$s "
2897 "is already registered.\n\n"
2898 "Do you want to choose a new name?"),
2900 docstring const title = (kind == LV_VC_RENAME) ?
2901 _("Rename document?") : _("Copy document?");
2902 docstring const button = (kind == LV_VC_RENAME) ?
2903 _("&Rename") : _("&Copy");
2904 int const ret = Alert::prompt(title, text, 0, 1,
2905 button, _("&Cancel"));
2907 case 0: return renameBuffer(b, docstring(), kind);
2908 case 1: return false;
2913 docstring text = bformat(_("The document %1$s "
2914 "already exists.\n\n"
2915 "Do you want to overwrite that document?"),
2917 int const ret = Alert::prompt(_("Overwrite document?"),
2918 text, 0, 2, _("&Overwrite"),
2919 _("&Rename"), _("&Cancel"));
2922 case 1: return renameBuffer(b, docstring(), kind);
2923 case 2: return false;
2929 case LV_VC_RENAME: {
2930 string msg = b.lyxvc().rename(fname);
2933 message(from_utf8(msg));
2937 string msg = b.lyxvc().copy(fname);
2940 message(from_utf8(msg));
2944 case LV_WRITE_AS_TEMPLATE:
2947 // LyXVC created the file already in case of LV_VC_RENAME or
2948 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2949 // relative paths of included stuff right if we moved e.g. from
2950 // /a/b.lyx to /a/c/b.lyx.
2952 bool const saved = saveBuffer(b, fname);
2959 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2961 FileName fname = b.fileName();
2963 FileDialog dlg(qt_("Choose a filename to export the document as"));
2964 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2967 QString const anyformat = qt_("Guess from extension (*.*)");
2970 vector<Format const *> export_formats;
2971 for (Format const & f : theFormats())
2972 if (f.documentFormat())
2973 export_formats.push_back(&f);
2974 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2975 map<QString, string> fmap;
2978 for (Format const * f : export_formats) {
2979 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2980 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2982 from_ascii(f->extension())));
2983 types << loc_filter;
2984 fmap[loc_filter] = f->name();
2985 if (from_ascii(f->name()) == iformat) {
2986 filter = loc_filter;
2987 ext = f->extension();
2990 string ofname = fname.onlyFileName();
2992 ofname = support::changeExtension(ofname, ext);
2993 FileDialog::Result result =
2994 dlg.save(toqstr(fname.onlyPath().absFileName()),
2998 if (result.first != FileDialog::Chosen)
3002 fname.set(fromqstr(result.second));
3003 if (filter == anyformat)
3004 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3006 fmt_name = fmap[filter];
3007 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3008 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3010 if (fmt_name.empty() || fname.empty())
3013 // fname is now the new Buffer location.
3014 if (FileName(fname).exists()) {
3015 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3016 docstring text = bformat(_("The document %1$s already "
3017 "exists.\n\nDo you want to "
3018 "overwrite that document?"),
3020 int const ret = Alert::prompt(_("Overwrite document?"),
3021 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3024 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3025 case 2: return false;
3029 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3032 return dr.dispatched();
3036 bool GuiView::saveBuffer(Buffer & b)
3038 return saveBuffer(b, FileName());
3042 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3044 if (workArea(b) && workArea(b)->inDialogMode())
3047 if (fn.empty() && b.isUnnamed())
3048 return renameBuffer(b, docstring());
3050 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3052 theSession().lastFiles().add(b.fileName());
3053 theSession().writeFile();
3057 // Switch to this Buffer.
3060 // FIXME: we don't tell the user *WHY* the save failed !!
3061 docstring const file = makeDisplayPath(b.absFileName(), 30);
3062 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3063 "Do you want to rename the document and "
3064 "try again?"), file);
3065 int const ret = Alert::prompt(_("Rename and save?"),
3066 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3069 if (!renameBuffer(b, docstring()))
3078 return saveBuffer(b, fn);
3082 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3084 return closeWorkArea(wa, false);
3088 // We only want to close the buffer if it is not visible in other workareas
3089 // of the same view, nor in other views, and if this is not a child
3090 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3092 Buffer & buf = wa->bufferView().buffer();
3094 bool last_wa = d.countWorkAreasOf(buf) == 1
3095 && !inOtherView(buf) && !buf.parent();
3097 bool close_buffer = last_wa;
3100 if (lyxrc.close_buffer_with_last_view == "yes")
3102 else if (lyxrc.close_buffer_with_last_view == "no")
3103 close_buffer = false;
3106 if (buf.isUnnamed())
3107 file = from_utf8(buf.fileName().onlyFileName());
3109 file = buf.fileName().displayName(30);
3110 docstring const text = bformat(
3111 _("Last view on document %1$s is being closed.\n"
3112 "Would you like to close or hide the document?\n"
3114 "Hidden documents can be displayed back through\n"
3115 "the menu: View->Hidden->...\n"
3117 "To remove this question, set your preference in:\n"
3118 " Tools->Preferences->Look&Feel->UserInterface\n"
3120 int ret = Alert::prompt(_("Close or hide document?"),
3121 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3124 close_buffer = (ret == 0);
3128 return closeWorkArea(wa, close_buffer);
3132 bool GuiView::closeBuffer()
3134 GuiWorkArea * wa = currentMainWorkArea();
3135 // coverity complained about this
3136 // it seems unnecessary, but perhaps is worth the check
3137 LASSERT(wa, return false);
3139 setCurrentWorkArea(wa);
3140 Buffer & buf = wa->bufferView().buffer();
3141 return closeWorkArea(wa, !buf.parent());
3145 void GuiView::writeSession() const {
3146 GuiWorkArea const * active_wa = currentMainWorkArea();
3147 for (int i = 0; i < d.splitter_->count(); ++i) {
3148 TabWorkArea * twa = d.tabWorkArea(i);
3149 for (int j = 0; j < twa->count(); ++j) {
3150 GuiWorkArea * wa = twa->workArea(j);
3151 Buffer & buf = wa->bufferView().buffer();
3152 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3158 bool GuiView::closeBufferAll()
3161 for (auto & buf : theBufferList()) {
3162 if (!saveBufferIfNeeded(*buf, false)) {
3163 // Closing has been cancelled, so abort.
3168 // Close the workareas in all other views
3169 QList<int> const ids = guiApp->viewIds();
3170 for (int i = 0; i != ids.size(); ++i) {
3171 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3175 // Close our own workareas
3176 if (!closeWorkAreaAll())
3183 bool GuiView::closeWorkAreaAll()
3185 setCurrentWorkArea(currentMainWorkArea());
3187 // We might be in a situation that there is still a tabWorkArea, but
3188 // there are no tabs anymore. This can happen when we get here after a
3189 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3190 // many TabWorkArea's have no documents anymore.
3193 // We have to call count() each time, because it can happen that
3194 // more than one splitter will disappear in one iteration (bug 5998).
3195 while (d.splitter_->count() > empty_twa) {
3196 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3198 if (twa->count() == 0)
3201 setCurrentWorkArea(twa->currentWorkArea());
3202 if (!closeTabWorkArea(twa))
3210 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3215 Buffer & buf = wa->bufferView().buffer();
3217 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3218 Alert::warning(_("Close document"),
3219 _("Document could not be closed because it is being processed by LyX."));
3224 return closeBuffer(buf);
3226 if (!inMultiTabs(wa))
3227 if (!saveBufferIfNeeded(buf, true))
3235 bool GuiView::closeBuffer(Buffer & buf)
3237 bool success = true;
3238 ListOfBuffers clist = buf.getChildren();
3239 ListOfBuffers::const_iterator it = clist.begin();
3240 ListOfBuffers::const_iterator const bend = clist.end();
3241 for (; it != bend; ++it) {
3242 Buffer * child_buf = *it;
3243 if (theBufferList().isOthersChild(&buf, child_buf)) {
3244 child_buf->setParent(nullptr);
3248 // FIXME: should we look in other tabworkareas?
3249 // ANSWER: I don't think so. I've tested, and if the child is
3250 // open in some other window, it closes without a problem.
3251 GuiWorkArea * child_wa = workArea(*child_buf);
3254 // If we are in a close_event all children will be closed in some time,
3255 // so no need to do it here. This will ensure that the children end up
3256 // in the session file in the correct order. If we close the master
3257 // buffer, we can close or release the child buffers here too.
3259 success = closeWorkArea(child_wa, true);
3263 // In this case the child buffer is open but hidden.
3264 // Even in this case, children can be dirty (e.g.,
3265 // after a label change in the master, see #11405).
3266 // Therefore, check this
3267 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3268 // If we are in a close_event all children will be closed in some time,
3269 // so no need to do it here. This will ensure that the children end up
3270 // in the session file in the correct order. If we close the master
3271 // buffer, we can close or release the child buffers here too.
3274 // Save dirty buffers also if closing_!
3275 if (saveBufferIfNeeded(*child_buf, false)) {
3276 child_buf->removeAutosaveFile();
3277 theBufferList().release(child_buf);
3279 // Saving of dirty children has been cancelled.
3280 // Cancel the whole process.
3287 // goto bookmark to update bookmark pit.
3288 // FIXME: we should update only the bookmarks related to this buffer!
3289 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3290 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3291 guiApp->gotoBookmark(i + 1, false, false);
3293 if (saveBufferIfNeeded(buf, false)) {
3294 buf.removeAutosaveFile();
3295 theBufferList().release(&buf);
3299 // open all children again to avoid a crash because of dangling
3300 // pointers (bug 6603)
3306 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3308 while (twa == d.currentTabWorkArea()) {
3309 twa->setCurrentIndex(twa->count() - 1);
3311 GuiWorkArea * wa = twa->currentWorkArea();
3312 Buffer & b = wa->bufferView().buffer();
3314 // We only want to close the buffer if the same buffer is not visible
3315 // in another view, and if this is not a child and if we are closing
3316 // a view (not a tabgroup).
3317 bool const close_buffer =
3318 !inOtherView(b) && !b.parent() && closing_;
3320 if (!closeWorkArea(wa, close_buffer))
3327 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3329 if (buf.isClean() || buf.paragraphs().empty())
3332 // Switch to this Buffer.
3338 if (buf.isUnnamed()) {
3339 file = from_utf8(buf.fileName().onlyFileName());
3342 FileName filename = buf.fileName();
3344 file = filename.displayName(30);
3345 exists = filename.exists();
3348 // Bring this window to top before asking questions.
3353 if (hiding && buf.isUnnamed()) {
3354 docstring const text = bformat(_("The document %1$s has not been "
3355 "saved yet.\n\nDo you want to save "
3356 "the document?"), file);
3357 ret = Alert::prompt(_("Save new document?"),
3358 text, 0, 1, _("&Save"), _("&Cancel"));
3362 docstring const text = exists ?
3363 bformat(_("The document %1$s has unsaved changes."
3364 "\n\nDo you want to save the document or "
3365 "discard the changes?"), file) :
3366 bformat(_("The document %1$s has not been saved yet."
3367 "\n\nDo you want to save the document or "
3368 "discard it entirely?"), file);
3369 docstring const title = exists ?
3370 _("Save changed document?") : _("Save document?");
3371 ret = Alert::prompt(title, text, 0, 2,
3372 _("&Save"), _("&Discard"), _("&Cancel"));
3377 if (!saveBuffer(buf))
3381 // If we crash after this we could have no autosave file
3382 // but I guess this is really improbable (Jug).
3383 // Sometimes improbable things happen:
3384 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3385 // buf.removeAutosaveFile();
3387 // revert all changes
3398 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3400 Buffer & buf = wa->bufferView().buffer();
3402 for (int i = 0; i != d.splitter_->count(); ++i) {
3403 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3404 if (wa_ && wa_ != wa)
3407 return inOtherView(buf);
3411 bool GuiView::inOtherView(Buffer & buf)
3413 QList<int> const ids = guiApp->viewIds();
3415 for (int i = 0; i != ids.size(); ++i) {
3419 if (guiApp->view(ids[i]).workArea(buf))
3426 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3428 if (!documentBufferView())
3431 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3432 Buffer * const curbuf = &documentBufferView()->buffer();
3433 int nwa = twa->count();
3434 for (int i = 0; i < nwa; ++i) {
3435 if (&workArea(i)->bufferView().buffer() == curbuf) {
3437 if (np == NEXTBUFFER)
3438 next_index = (i == nwa - 1 ? 0 : i + 1);
3440 next_index = (i == 0 ? nwa - 1 : i - 1);
3442 twa->moveTab(i, next_index);
3444 setBuffer(&workArea(next_index)->bufferView().buffer());
3452 /// make sure the document is saved
3453 static bool ensureBufferClean(Buffer * buffer)
3455 LASSERT(buffer, return false);
3456 if (buffer->isClean() && !buffer->isUnnamed())
3459 docstring const file = buffer->fileName().displayName(30);
3462 if (!buffer->isUnnamed()) {
3463 text = bformat(_("The document %1$s has unsaved "
3464 "changes.\n\nDo you want to save "
3465 "the document?"), file);
3466 title = _("Save changed document?");
3469 text = bformat(_("The document %1$s has not been "
3470 "saved yet.\n\nDo you want to save "
3471 "the document?"), file);
3472 title = _("Save new document?");
3474 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3477 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3479 return buffer->isClean() && !buffer->isUnnamed();
3483 bool GuiView::reloadBuffer(Buffer & buf)
3485 currentBufferView()->cursor().reset();
3486 Buffer::ReadStatus status = buf.reload();
3487 return status == Buffer::ReadSuccess;
3491 void GuiView::checkExternallyModifiedBuffers()
3493 BufferList::iterator bit = theBufferList().begin();
3494 BufferList::iterator const bend = theBufferList().end();
3495 for (; bit != bend; ++bit) {
3496 Buffer * buf = *bit;
3497 if (buf->fileName().exists() && buf->isChecksumModified()) {
3498 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3499 " Reload now? Any local changes will be lost."),
3500 from_utf8(buf->absFileName()));
3501 int const ret = Alert::prompt(_("Reload externally changed document?"),
3502 text, 0, 1, _("&Reload"), _("&Cancel"));
3510 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3512 Buffer * buffer = documentBufferView()
3513 ? &(documentBufferView()->buffer()) : nullptr;
3515 switch (cmd.action()) {
3516 case LFUN_VC_REGISTER:
3517 if (!buffer || !ensureBufferClean(buffer))
3519 if (!buffer->lyxvc().inUse()) {
3520 if (buffer->lyxvc().registrer()) {
3521 reloadBuffer(*buffer);
3522 dr.clearMessageUpdate();
3527 case LFUN_VC_RENAME:
3528 case LFUN_VC_COPY: {
3529 if (!buffer || !ensureBufferClean(buffer))
3531 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3532 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3533 // Some changes are not yet committed.
3534 // We test here and not in getStatus(), since
3535 // this test is expensive.
3537 LyXVC::CommandResult ret =
3538 buffer->lyxvc().checkIn(log);
3540 if (ret == LyXVC::ErrorCommand ||
3541 ret == LyXVC::VCSuccess)
3542 reloadBuffer(*buffer);
3543 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3544 frontend::Alert::error(
3545 _("Revision control error."),
3546 _("Document could not be checked in."));
3550 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3551 LV_VC_RENAME : LV_VC_COPY;
3552 renameBuffer(*buffer, cmd.argument(), kind);
3557 case LFUN_VC_CHECK_IN:
3558 if (!buffer || !ensureBufferClean(buffer))
3560 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3562 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3564 // Only skip reloading if the checkin was cancelled or
3565 // an error occurred before the real checkin VCS command
3566 // was executed, since the VCS might have changed the
3567 // file even if it could not checkin successfully.
3568 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3569 reloadBuffer(*buffer);
3573 case LFUN_VC_CHECK_OUT:
3574 if (!buffer || !ensureBufferClean(buffer))
3576 if (buffer->lyxvc().inUse()) {
3577 dr.setMessage(buffer->lyxvc().checkOut());
3578 reloadBuffer(*buffer);
3582 case LFUN_VC_LOCKING_TOGGLE:
3583 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3585 if (buffer->lyxvc().inUse()) {
3586 string res = buffer->lyxvc().lockingToggle();
3588 frontend::Alert::error(_("Revision control error."),
3589 _("Error when setting the locking property."));
3592 reloadBuffer(*buffer);
3597 case LFUN_VC_REVERT:
3600 if (buffer->lyxvc().revert()) {
3601 reloadBuffer(*buffer);
3602 dr.clearMessageUpdate();
3606 case LFUN_VC_UNDO_LAST:
3609 buffer->lyxvc().undoLast();
3610 reloadBuffer(*buffer);
3611 dr.clearMessageUpdate();
3614 case LFUN_VC_REPO_UPDATE:
3617 if (ensureBufferClean(buffer)) {
3618 dr.setMessage(buffer->lyxvc().repoUpdate());
3619 checkExternallyModifiedBuffers();
3623 case LFUN_VC_COMMAND: {
3624 string flag = cmd.getArg(0);
3625 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3628 if (contains(flag, 'M')) {
3629 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3632 string path = cmd.getArg(1);
3633 if (contains(path, "$$p") && buffer)
3634 path = subst(path, "$$p", buffer->filePath());
3635 LYXERR(Debug::LYXVC, "Directory: " << path);
3637 if (!pp.isReadableDirectory()) {
3638 lyxerr << _("Directory is not accessible.") << endl;
3641 support::PathChanger p(pp);
3643 string command = cmd.getArg(2);
3644 if (command.empty())
3647 command = subst(command, "$$i", buffer->absFileName());
3648 command = subst(command, "$$p", buffer->filePath());
3650 command = subst(command, "$$m", to_utf8(message));
3651 LYXERR(Debug::LYXVC, "Command: " << command);
3653 one.startscript(Systemcall::Wait, command);
3657 if (contains(flag, 'I'))
3658 buffer->markDirty();
3659 if (contains(flag, 'R'))
3660 reloadBuffer(*buffer);
3665 case LFUN_VC_COMPARE: {
3666 if (cmd.argument().empty()) {
3667 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3673 string rev1 = cmd.getArg(0);
3677 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3680 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3681 f2 = buffer->absFileName();
3683 string rev2 = cmd.getArg(1);
3687 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3691 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3692 f1 << "\n" << f2 << "\n" );
3693 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3694 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3704 void GuiView::openChildDocument(string const & fname)
3706 LASSERT(documentBufferView(), return);
3707 Buffer & buffer = documentBufferView()->buffer();
3708 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3709 documentBufferView()->saveBookmark(false);
3710 Buffer * child = nullptr;
3711 if (theBufferList().exists(filename)) {
3712 child = theBufferList().getBuffer(filename);
3715 message(bformat(_("Opening child document %1$s..."),
3716 makeDisplayPath(filename.absFileName())));
3717 child = loadDocument(filename, false);
3719 // Set the parent name of the child document.
3720 // This makes insertion of citations and references in the child work,
3721 // when the target is in the parent or another child document.
3723 child->setParent(&buffer);
3727 bool GuiView::goToFileRow(string const & argument)
3731 size_t i = argument.find_last_of(' ');
3732 if (i != string::npos) {
3733 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3734 istringstream is(argument.substr(i + 1));
3739 if (i == string::npos) {
3740 LYXERR0("Wrong argument: " << argument);
3743 Buffer * buf = nullptr;
3744 string const realtmp = package().temp_dir().realPath();
3745 // We have to use os::path_prefix_is() here, instead of
3746 // simply prefixIs(), because the file name comes from
3747 // an external application and may need case adjustment.
3748 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3749 buf = theBufferList().getBufferFromTmp(file_name, true);
3750 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3751 << (buf ? " success" : " failed"));
3753 // Must replace extension of the file to be .lyx
3754 // and get full path
3755 FileName const s = fileSearch(string(),
3756 support::changeExtension(file_name, ".lyx"), "lyx");
3757 // Either change buffer or load the file
3758 if (theBufferList().exists(s))
3759 buf = theBufferList().getBuffer(s);
3760 else if (s.exists()) {
3761 buf = loadDocument(s);
3766 _("File does not exist: %1$s"),
3767 makeDisplayPath(file_name)));
3773 _("No buffer for file: %1$s."),
3774 makeDisplayPath(file_name))
3779 bool success = documentBufferView()->setCursorFromRow(row);
3781 LYXERR(Debug::LATEX,
3782 "setCursorFromRow: invalid position for row " << row);
3783 frontend::Alert::error(_("Inverse Search Failed"),
3784 _("Invalid position requested by inverse search.\n"
3785 "You may need to update the viewed document."));
3791 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3793 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3794 menu->exec(QCursor::pos());
3799 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3800 Buffer const * orig, Buffer * clone, string const & format)
3802 Buffer::ExportStatus const status = func(format);
3804 // the cloning operation will have produced a clone of the entire set of
3805 // documents, starting from the master. so we must delete those.
3806 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3808 busyBuffers.remove(orig);
3813 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3814 Buffer const * orig, Buffer * clone, string const & format)
3816 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3818 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3822 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3823 Buffer const * orig, Buffer * clone, string const & format)
3825 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3827 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3831 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3832 Buffer const * orig, Buffer * clone, string const & format)
3834 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3836 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3840 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3841 string const & argument,
3842 Buffer const * used_buffer,
3843 docstring const & msg,
3844 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3845 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3846 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3852 string format = argument;
3854 format = used_buffer->params().getDefaultOutputFormat();
3855 processing_format = format;
3857 progress_->clearMessages();
3860 #if EXPORT_in_THREAD
3862 GuiViewPrivate::busyBuffers.insert(used_buffer);
3863 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3864 if (!cloned_buffer) {
3865 Alert::error(_("Export Error"),
3866 _("Error cloning the Buffer."));
3869 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3874 setPreviewFuture(f);
3875 last_export_format = used_buffer->params().bufferFormat();
3878 // We are asynchronous, so we don't know here anything about the success
3881 Buffer::ExportStatus status;
3883 status = (used_buffer->*syncFunc)(format, false);
3884 } else if (previewFunc) {
3885 status = (used_buffer->*previewFunc)(format);
3888 handleExportStatus(gv_, status, format);
3890 return (status == Buffer::ExportSuccess
3891 || status == Buffer::PreviewSuccess);
3895 Buffer::ExportStatus status;
3897 status = (used_buffer->*syncFunc)(format, true);
3898 } else if (previewFunc) {
3899 status = (used_buffer->*previewFunc)(format);
3902 handleExportStatus(gv_, status, format);
3904 return (status == Buffer::ExportSuccess
3905 || status == Buffer::PreviewSuccess);
3909 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3911 BufferView * bv = currentBufferView();
3912 LASSERT(bv, return);
3914 // Let the current BufferView dispatch its own actions.
3915 bv->dispatch(cmd, dr);
3916 if (dr.dispatched()) {
3917 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3918 updateDialog("document", "");
3922 // Try with the document BufferView dispatch if any.
3923 BufferView * doc_bv = documentBufferView();
3924 if (doc_bv && doc_bv != bv) {
3925 doc_bv->dispatch(cmd, dr);
3926 if (dr.dispatched()) {
3927 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3928 updateDialog("document", "");
3933 // Then let the current Cursor dispatch its own actions.
3934 bv->cursor().dispatch(cmd);
3936 // update completion. We do it here and not in
3937 // processKeySym to avoid another redraw just for a
3938 // changed inline completion
3939 if (cmd.origin() == FuncRequest::KEYBOARD) {
3940 if (cmd.action() == LFUN_SELF_INSERT
3941 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3942 updateCompletion(bv->cursor(), true, true);
3943 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3944 updateCompletion(bv->cursor(), false, true);
3946 updateCompletion(bv->cursor(), false, false);
3949 dr = bv->cursor().result();
3953 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3955 BufferView * bv = currentBufferView();
3956 // By default we won't need any update.
3957 dr.screenUpdate(Update::None);
3958 // assume cmd will be dispatched
3959 dr.dispatched(true);
3961 Buffer * doc_buffer = documentBufferView()
3962 ? &(documentBufferView()->buffer()) : nullptr;
3964 if (cmd.origin() == FuncRequest::TOC) {
3965 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3966 toc->doDispatch(bv->cursor(), cmd, dr);
3970 string const argument = to_utf8(cmd.argument());
3972 switch(cmd.action()) {
3973 case LFUN_BUFFER_CHILD_OPEN:
3974 openChildDocument(to_utf8(cmd.argument()));
3977 case LFUN_BUFFER_IMPORT:
3978 importDocument(to_utf8(cmd.argument()));
3981 case LFUN_MASTER_BUFFER_EXPORT:
3983 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3985 case LFUN_BUFFER_EXPORT: {
3988 // GCC only sees strfwd.h when building merged
3989 if (::lyx::operator==(cmd.argument(), "custom")) {
3990 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3991 // so the following test should not be needed.
3992 // In principle, we could try to switch to such a view...
3993 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3994 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3998 string const dest = cmd.getArg(1);
3999 FileName target_dir;
4000 if (!dest.empty() && FileName::isAbsolute(dest))
4001 target_dir = FileName(support::onlyPath(dest));
4003 target_dir = doc_buffer->fileName().onlyPath();
4005 string const format = (argument.empty() || argument == "default") ?
4006 doc_buffer->params().getDefaultOutputFormat() : argument;
4008 if ((dest.empty() && doc_buffer->isUnnamed())
4009 || !target_dir.isDirWritable()) {
4010 exportBufferAs(*doc_buffer, from_utf8(format));
4013 /* TODO/Review: Is it a problem to also export the children?
4014 See the update_unincluded flag */
4015 d.asyncBufferProcessing(format,
4018 &GuiViewPrivate::exportAndDestroy,
4020 nullptr, cmd.allowAsync());
4021 // TODO Inform user about success
4025 case LFUN_BUFFER_EXPORT_AS: {
4026 LASSERT(doc_buffer, break);
4027 docstring f = cmd.argument();
4029 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4030 exportBufferAs(*doc_buffer, f);
4034 case LFUN_BUFFER_UPDATE: {
4035 d.asyncBufferProcessing(argument,
4038 &GuiViewPrivate::compileAndDestroy,
4040 nullptr, cmd.allowAsync());
4043 case LFUN_BUFFER_VIEW: {
4044 d.asyncBufferProcessing(argument,
4046 _("Previewing ..."),
4047 &GuiViewPrivate::previewAndDestroy,
4049 &Buffer::preview, cmd.allowAsync());
4052 case LFUN_MASTER_BUFFER_UPDATE: {
4053 d.asyncBufferProcessing(argument,
4054 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4056 &GuiViewPrivate::compileAndDestroy,
4058 nullptr, cmd.allowAsync());
4061 case LFUN_MASTER_BUFFER_VIEW: {
4062 d.asyncBufferProcessing(argument,
4063 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4065 &GuiViewPrivate::previewAndDestroy,
4066 nullptr, &Buffer::preview, cmd.allowAsync());
4069 case LFUN_EXPORT_CANCEL: {
4070 Systemcall::killscript();
4073 case LFUN_BUFFER_SWITCH: {
4074 string const file_name = to_utf8(cmd.argument());
4075 if (!FileName::isAbsolute(file_name)) {
4077 dr.setMessage(_("Absolute filename expected."));
4081 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4084 dr.setMessage(_("Document not loaded"));
4088 // Do we open or switch to the buffer in this view ?
4089 if (workArea(*buffer)
4090 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4095 // Look for the buffer in other views
4096 QList<int> const ids = guiApp->viewIds();
4098 for (; i != ids.size(); ++i) {
4099 GuiView & gv = guiApp->view(ids[i]);
4100 if (gv.workArea(*buffer)) {
4102 gv.activateWindow();
4104 gv.setBuffer(buffer);
4109 // If necessary, open a new window as a last resort
4110 if (i == ids.size()) {
4111 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4117 case LFUN_BUFFER_NEXT:
4118 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4121 case LFUN_BUFFER_MOVE_NEXT:
4122 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4125 case LFUN_BUFFER_PREVIOUS:
4126 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4129 case LFUN_BUFFER_MOVE_PREVIOUS:
4130 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4133 case LFUN_BUFFER_CHKTEX:
4134 LASSERT(doc_buffer, break);
4135 doc_buffer->runChktex();
4138 case LFUN_COMMAND_EXECUTE: {
4139 command_execute_ = true;
4140 minibuffer_focus_ = true;
4143 case LFUN_DROP_LAYOUTS_CHOICE:
4144 d.layout_->showPopup();
4147 case LFUN_MENU_OPEN:
4148 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4149 menu->exec(QCursor::pos());
4152 case LFUN_FILE_INSERT: {
4153 if (cmd.getArg(1) == "ignorelang")
4154 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4156 insertLyXFile(cmd.argument());
4160 case LFUN_FILE_INSERT_PLAINTEXT:
4161 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4162 string const fname = to_utf8(cmd.argument());
4163 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4164 dr.setMessage(_("Absolute filename expected."));
4168 FileName filename(fname);
4169 if (fname.empty()) {
4170 FileDialog dlg(qt_("Select file to insert"));
4172 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4173 QStringList(qt_("All Files (*)")));
4175 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4176 dr.setMessage(_("Canceled."));
4180 filename.set(fromqstr(result.second));
4184 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4185 bv->dispatch(new_cmd, dr);
4190 case LFUN_BUFFER_RELOAD: {
4191 LASSERT(doc_buffer, break);
4194 bool drop = (cmd.argument() == "dump");
4197 if (!drop && !doc_buffer->isClean()) {
4198 docstring const file =
4199 makeDisplayPath(doc_buffer->absFileName(), 20);
4200 if (doc_buffer->notifiesExternalModification()) {
4201 docstring text = _("The current version will be lost. "
4202 "Are you sure you want to load the version on disk "
4203 "of the document %1$s?");
4204 ret = Alert::prompt(_("Reload saved document?"),
4205 bformat(text, file), 1, 1,
4206 _("&Reload"), _("&Cancel"));
4208 docstring text = _("Any changes will be lost. "
4209 "Are you sure you want to revert to the saved version "
4210 "of the document %1$s?");
4211 ret = Alert::prompt(_("Revert to saved document?"),
4212 bformat(text, file), 1, 1,
4213 _("&Revert"), _("&Cancel"));
4218 doc_buffer->markClean();
4219 reloadBuffer(*doc_buffer);
4220 dr.forceBufferUpdate();
4225 case LFUN_BUFFER_RESET_EXPORT:
4226 LASSERT(doc_buffer, break);
4227 doc_buffer->requireFreshStart(true);
4228 dr.setMessage(_("Buffer export reset."));
4231 case LFUN_BUFFER_WRITE:
4232 LASSERT(doc_buffer, break);
4233 saveBuffer(*doc_buffer);
4236 case LFUN_BUFFER_WRITE_AS:
4237 LASSERT(doc_buffer, break);
4238 renameBuffer(*doc_buffer, cmd.argument());
4241 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4242 LASSERT(doc_buffer, break);
4243 renameBuffer(*doc_buffer, cmd.argument(),
4244 LV_WRITE_AS_TEMPLATE);
4247 case LFUN_BUFFER_WRITE_ALL: {
4248 Buffer * first = theBufferList().first();
4251 message(_("Saving all documents..."));
4252 // We cannot use a for loop as the buffer list cycles.
4255 if (!b->isClean()) {
4257 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4259 b = theBufferList().next(b);
4260 } while (b != first);
4261 dr.setMessage(_("All documents saved."));
4265 case LFUN_MASTER_BUFFER_FORALL: {
4269 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4270 funcToRun.allowAsync(false);
4272 for (Buffer const * buf : doc_buffer->allRelatives()) {
4273 // Switch to other buffer view and resend cmd
4274 lyx::dispatch(FuncRequest(
4275 LFUN_BUFFER_SWITCH, buf->absFileName()));
4276 lyx::dispatch(funcToRun);
4279 lyx::dispatch(FuncRequest(
4280 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4284 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4285 LASSERT(doc_buffer, break);
4286 doc_buffer->clearExternalModification();
4289 case LFUN_BUFFER_CLOSE:
4293 case LFUN_BUFFER_CLOSE_ALL:
4297 case LFUN_DEVEL_MODE_TOGGLE:
4298 devel_mode_ = !devel_mode_;
4300 dr.setMessage(_("Developer mode is now enabled."));
4302 dr.setMessage(_("Developer mode is now disabled."));
4305 case LFUN_TOOLBAR_TOGGLE: {
4306 string const name = cmd.getArg(0);
4307 if (GuiToolbar * t = toolbar(name))
4312 case LFUN_TOOLBAR_MOVABLE: {
4313 string const name = cmd.getArg(0);
4315 // toggle (all) toolbars movablility
4316 toolbarsMovable_ = !toolbarsMovable_;
4317 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4318 GuiToolbar * tb = toolbar(ti.name);
4319 if (tb && tb->isMovable() != toolbarsMovable_)
4320 // toggle toolbar movablity if it does not fit lock
4321 // (all) toolbars positions state silent = true, since
4322 // status bar notifications are slow
4325 if (toolbarsMovable_)
4326 dr.setMessage(_("Toolbars unlocked."));
4328 dr.setMessage(_("Toolbars locked."));
4329 } else if (GuiToolbar * t = toolbar(name)) {
4330 // toggle current toolbar movablity
4332 // update lock (all) toolbars positions
4333 updateLockToolbars();
4338 case LFUN_ICON_SIZE: {
4339 QSize size = d.iconSize(cmd.argument());
4341 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4342 size.width(), size.height()));
4346 case LFUN_DIALOG_UPDATE: {
4347 string const name = to_utf8(cmd.argument());
4348 if (name == "prefs" || name == "document")
4349 updateDialog(name, string());
4350 else if (name == "paragraph")
4351 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4352 else if (currentBufferView()) {
4353 Inset * inset = currentBufferView()->editedInset(name);
4354 // Can only update a dialog connected to an existing inset
4356 // FIXME: get rid of this indirection; GuiView ask the inset
4357 // if he is kind enough to update itself...
4358 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4359 //FIXME: pass DispatchResult here?
4360 inset->dispatch(currentBufferView()->cursor(), fr);
4366 case LFUN_DIALOG_TOGGLE: {
4367 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4368 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4369 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4373 case LFUN_DIALOG_DISCONNECT_INSET:
4374 disconnectDialog(to_utf8(cmd.argument()));
4377 case LFUN_DIALOG_HIDE: {
4378 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4382 case LFUN_DIALOG_SHOW: {
4383 string const name = cmd.getArg(0);
4384 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4386 if (name == "latexlog") {
4387 // getStatus checks that
4388 LASSERT(doc_buffer, break);
4389 Buffer::LogType type;
4390 string const logfile = doc_buffer->logName(&type);
4392 case Buffer::latexlog:
4395 case Buffer::buildlog:
4396 sdata = "literate ";
4399 sdata += Lexer::quoteString(logfile);
4400 showDialog("log", sdata);
4401 } else if (name == "vclog") {
4402 // getStatus checks that
4403 LASSERT(doc_buffer, break);
4404 string const sdata2 = "vc " +
4405 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4406 showDialog("log", sdata2);
4407 } else if (name == "symbols") {
4408 sdata = bv->cursor().getEncoding()->name();
4410 showDialog("symbols", sdata);
4412 } else if (name == "prefs" && isFullScreen()) {
4413 lfunUiToggle("fullscreen");
4414 showDialog("prefs", sdata);
4416 showDialog(name, sdata);
4421 dr.setMessage(cmd.argument());
4424 case LFUN_UI_TOGGLE: {
4425 string arg = cmd.getArg(0);
4426 if (!lfunUiToggle(arg)) {
4427 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4428 dr.setMessage(bformat(msg, from_utf8(arg)));
4430 // Make sure the keyboard focus stays in the work area.
4435 case LFUN_VIEW_SPLIT: {
4436 LASSERT(doc_buffer, break);
4437 string const orientation = cmd.getArg(0);
4438 d.splitter_->setOrientation(orientation == "vertical"
4439 ? Qt::Vertical : Qt::Horizontal);
4440 TabWorkArea * twa = addTabWorkArea();
4441 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4442 setCurrentWorkArea(wa);
4445 case LFUN_TAB_GROUP_CLOSE:
4446 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4447 closeTabWorkArea(twa);
4448 d.current_work_area_ = nullptr;
4449 twa = d.currentTabWorkArea();
4450 // Switch to the next GuiWorkArea in the found TabWorkArea.
4452 // Make sure the work area is up to date.
4453 setCurrentWorkArea(twa->currentWorkArea());
4455 setCurrentWorkArea(nullptr);
4460 case LFUN_VIEW_CLOSE:
4461 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4462 closeWorkArea(twa->currentWorkArea());
4463 d.current_work_area_ = nullptr;
4464 twa = d.currentTabWorkArea();
4465 // Switch to the next GuiWorkArea in the found TabWorkArea.
4467 // Make sure the work area is up to date.
4468 setCurrentWorkArea(twa->currentWorkArea());
4470 setCurrentWorkArea(nullptr);
4475 case LFUN_COMPLETION_INLINE:
4476 if (d.current_work_area_)
4477 d.current_work_area_->completer().showInline();
4480 case LFUN_COMPLETION_POPUP:
4481 if (d.current_work_area_)
4482 d.current_work_area_->completer().showPopup();
4487 if (d.current_work_area_)
4488 d.current_work_area_->completer().tab();
4491 case LFUN_COMPLETION_CANCEL:
4492 if (d.current_work_area_) {
4493 if (d.current_work_area_->completer().popupVisible())
4494 d.current_work_area_->completer().hidePopup();
4496 d.current_work_area_->completer().hideInline();
4500 case LFUN_COMPLETION_ACCEPT:
4501 if (d.current_work_area_)
4502 d.current_work_area_->completer().activate();
4505 case LFUN_BUFFER_ZOOM_IN:
4506 case LFUN_BUFFER_ZOOM_OUT:
4507 case LFUN_BUFFER_ZOOM: {
4508 if (cmd.argument().empty()) {
4509 if (cmd.action() == LFUN_BUFFER_ZOOM)
4511 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4516 if (cmd.action() == LFUN_BUFFER_ZOOM)
4517 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4518 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4519 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4521 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4524 // Actual zoom value: default zoom + fractional extra value
4525 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4526 if (zoom < static_cast<int>(zoom_min_))
4529 lyxrc.currentZoom = zoom;
4531 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4532 lyxrc.currentZoom, lyxrc.defaultZoom));
4534 guiApp->fontLoader().update();
4535 dr.screenUpdate(Update::Force | Update::FitCursor);
4539 case LFUN_VC_REGISTER:
4540 case LFUN_VC_RENAME:
4542 case LFUN_VC_CHECK_IN:
4543 case LFUN_VC_CHECK_OUT:
4544 case LFUN_VC_REPO_UPDATE:
4545 case LFUN_VC_LOCKING_TOGGLE:
4546 case LFUN_VC_REVERT:
4547 case LFUN_VC_UNDO_LAST:
4548 case LFUN_VC_COMMAND:
4549 case LFUN_VC_COMPARE:
4550 dispatchVC(cmd, dr);
4553 case LFUN_SERVER_GOTO_FILE_ROW:
4554 if(goToFileRow(to_utf8(cmd.argument())))
4555 dr.screenUpdate(Update::Force | Update::FitCursor);
4558 case LFUN_LYX_ACTIVATE:
4562 case LFUN_WINDOW_RAISE:
4568 case LFUN_FORWARD_SEARCH: {
4569 // it seems safe to assume we have a document buffer, since
4570 // getStatus wants one.
4571 LASSERT(doc_buffer, break);
4572 Buffer const * doc_master = doc_buffer->masterBuffer();
4573 FileName const path(doc_master->temppath());
4574 string const texname = doc_master->isChild(doc_buffer)
4575 ? DocFileName(changeExtension(
4576 doc_buffer->absFileName(),
4577 "tex")).mangledFileName()
4578 : doc_buffer->latexName();
4579 string const fulltexname =
4580 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4581 string const mastername =
4582 removeExtension(doc_master->latexName());
4583 FileName const dviname(addName(path.absFileName(),
4584 addExtension(mastername, "dvi")));
4585 FileName const pdfname(addName(path.absFileName(),
4586 addExtension(mastername, "pdf")));
4587 bool const have_dvi = dviname.exists();
4588 bool const have_pdf = pdfname.exists();
4589 if (!have_dvi && !have_pdf) {
4590 dr.setMessage(_("Please, preview the document first."));
4593 string outname = dviname.onlyFileName();
4594 string command = lyxrc.forward_search_dvi;
4595 if (!have_dvi || (have_pdf &&
4596 pdfname.lastModified() > dviname.lastModified())) {
4597 outname = pdfname.onlyFileName();
4598 command = lyxrc.forward_search_pdf;
4601 DocIterator cur = bv->cursor();
4602 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4603 LYXERR(Debug::ACTION, "Forward search: row:" << row
4605 if (row == -1 || command.empty()) {
4606 dr.setMessage(_("Couldn't proceed."));
4609 string texrow = convert<string>(row);
4611 command = subst(command, "$$n", texrow);
4612 command = subst(command, "$$f", fulltexname);
4613 command = subst(command, "$$t", texname);
4614 command = subst(command, "$$o", outname);
4616 volatile PathChanger p(path);
4618 one.startscript(Systemcall::DontWait, command);
4622 case LFUN_SPELLING_CONTINUOUSLY:
4623 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4624 dr.screenUpdate(Update::Force);
4627 case LFUN_CITATION_OPEN:
4628 frontend::showTarget(argument);
4632 // The LFUN must be for one of BufferView, Buffer or Cursor;
4634 dispatchToBufferView(cmd, dr);
4638 // Part of automatic menu appearance feature.
4639 if (isFullScreen()) {
4640 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4644 // Need to update bv because many LFUNs here might have destroyed it
4645 bv = currentBufferView();
4647 // Clear non-empty selections
4648 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4650 Cursor & cur = bv->cursor();
4651 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4652 cur.clearSelection();
4658 bool GuiView::lfunUiToggle(string const & ui_component)
4660 if (ui_component == "scrollbar") {
4661 // hide() is of no help
4662 if (d.current_work_area_->verticalScrollBarPolicy() ==
4663 Qt::ScrollBarAlwaysOff)
4665 d.current_work_area_->setVerticalScrollBarPolicy(
4666 Qt::ScrollBarAsNeeded);
4668 d.current_work_area_->setVerticalScrollBarPolicy(
4669 Qt::ScrollBarAlwaysOff);
4670 } else if (ui_component == "statusbar") {
4671 statusBar()->setVisible(!statusBar()->isVisible());
4672 } else if (ui_component == "menubar") {
4673 menuBar()->setVisible(!menuBar()->isVisible());
4675 if (ui_component == "frame") {
4676 int const l = contentsMargins().left();
4678 //are the frames in default state?
4679 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4681 setContentsMargins(-2, -2, -2, -2);
4683 setContentsMargins(0, 0, 0, 0);
4686 if (ui_component == "fullscreen") {
4694 void GuiView::toggleFullScreen()
4696 setWindowState(windowState() ^ Qt::WindowFullScreen);
4700 Buffer const * GuiView::updateInset(Inset const * inset)
4705 Buffer const * inset_buffer = &(inset->buffer());
4707 for (int i = 0; i != d.splitter_->count(); ++i) {
4708 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4711 Buffer const * buffer = &(wa->bufferView().buffer());
4712 if (inset_buffer == buffer)
4713 wa->scheduleRedraw(true);
4715 return inset_buffer;
4719 void GuiView::restartCaret()
4721 /* When we move around, or type, it's nice to be able to see
4722 * the caret immediately after the keypress.
4724 if (d.current_work_area_)
4725 d.current_work_area_->startBlinkingCaret();
4727 // Take this occasion to update the other GUI elements.
4733 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4735 if (d.current_work_area_)
4736 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4741 // This list should be kept in sync with the list of insets in
4742 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4743 // dialog should have the same name as the inset.
4744 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4745 // docs in LyXAction.cpp.
4747 char const * const dialognames[] = {
4749 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4750 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4751 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4752 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4753 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4754 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4755 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4756 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4758 char const * const * const end_dialognames =
4759 dialognames + (sizeof(dialognames) / sizeof(char *));
4763 cmpCStr(char const * name) : name_(name) {}
4764 bool operator()(char const * other) {
4765 return strcmp(other, name_) == 0;
4772 bool isValidName(string const & name)
4774 return find_if(dialognames, end_dialognames,
4775 cmpCStr(name.c_str())) != end_dialognames;
4781 void GuiView::resetDialogs()
4783 // Make sure that no LFUN uses any GuiView.
4784 guiApp->setCurrentView(nullptr);
4788 constructToolbars();
4789 guiApp->menus().fillMenuBar(menuBar(), this, false);
4790 d.layout_->updateContents(true);
4791 // Now update controls with current buffer.
4792 guiApp->setCurrentView(this);
4798 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4800 for (QObject * child: widget->children()) {
4801 if (child->inherits("QGroupBox")) {
4802 QGroupBox * box = (QGroupBox*) child;
4805 flatGroupBoxes(child, flag);
4811 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4813 if (!isValidName(name))
4816 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4818 if (it != d.dialogs_.end()) {
4820 it->second->hideView();
4821 return it->second.get();
4824 Dialog * dialog = build(name);
4825 d.dialogs_[name].reset(dialog);
4826 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4827 // Force a uniform style for group boxes
4828 // On Mac non-flat works better, on Linux flat is standard
4829 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4831 if (lyxrc.allow_geometry_session)
4832 dialog->restoreSession();
4839 void GuiView::showDialog(string const & name, string const & sdata,
4842 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4846 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4852 const string name = fromqstr(qname);
4853 const string sdata = fromqstr(qdata);
4857 Dialog * dialog = findOrBuild(name, false);
4859 bool const visible = dialog->isVisibleView();
4860 dialog->showData(sdata);
4861 if (currentBufferView())
4862 currentBufferView()->editInset(name, inset);
4863 // We only set the focus to the new dialog if it was not yet
4864 // visible in order not to change the existing previous behaviour
4866 // activateWindow is needed for floating dockviews
4867 dialog->asQWidget()->raise();
4868 dialog->asQWidget()->activateWindow();
4869 dialog->asQWidget()->setFocus();
4873 catch (ExceptionMessage const & ex) {
4881 bool GuiView::isDialogVisible(string const & name) const
4883 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4884 if (it == d.dialogs_.end())
4886 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4890 void GuiView::hideDialog(string const & name, Inset * inset)
4892 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4893 if (it == d.dialogs_.end())
4897 if (!currentBufferView())
4899 if (inset != currentBufferView()->editedInset(name))
4903 Dialog * const dialog = it->second.get();
4904 if (dialog->isVisibleView())
4906 if (currentBufferView())
4907 currentBufferView()->editInset(name, nullptr);
4911 void GuiView::disconnectDialog(string const & name)
4913 if (!isValidName(name))
4915 if (currentBufferView())
4916 currentBufferView()->editInset(name, nullptr);
4920 void GuiView::hideAll() const
4922 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4923 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4925 for(; it != end; ++it)
4926 it->second->hideView();
4930 void GuiView::updateDialogs()
4932 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4933 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4935 for(; it != end; ++it) {
4936 Dialog * dialog = it->second.get();
4938 if (dialog->needBufferOpen() && !documentBufferView())
4939 hideDialog(fromqstr(dialog->name()), nullptr);
4940 else if (dialog->isVisibleView())
4941 dialog->checkStatus();
4948 Dialog * createDialog(GuiView & lv, string const & name);
4950 // will be replaced by a proper factory...
4951 Dialog * createGuiAbout(GuiView & lv);
4952 Dialog * createGuiBibtex(GuiView & lv);
4953 Dialog * createGuiChanges(GuiView & lv);
4954 Dialog * createGuiCharacter(GuiView & lv);
4955 Dialog * createGuiCitation(GuiView & lv);
4956 Dialog * createGuiCompare(GuiView & lv);
4957 Dialog * createGuiCompareHistory(GuiView & lv);
4958 Dialog * createGuiDelimiter(GuiView & lv);
4959 Dialog * createGuiDocument(GuiView & lv);
4960 Dialog * createGuiErrorList(GuiView & lv);
4961 Dialog * createGuiExternal(GuiView & lv);
4962 Dialog * createGuiGraphics(GuiView & lv);
4963 Dialog * createGuiInclude(GuiView & lv);
4964 Dialog * createGuiIndex(GuiView & lv);
4965 Dialog * createGuiListings(GuiView & lv);
4966 Dialog * createGuiLog(GuiView & lv);
4967 Dialog * createGuiLyXFiles(GuiView & lv);
4968 Dialog * createGuiMathMatrix(GuiView & lv);
4969 Dialog * createGuiNote(GuiView & lv);
4970 Dialog * createGuiParagraph(GuiView & lv);
4971 Dialog * createGuiPhantom(GuiView & lv);
4972 Dialog * createGuiPreferences(GuiView & lv);
4973 Dialog * createGuiPrint(GuiView & lv);
4974 Dialog * createGuiPrintindex(GuiView & lv);
4975 Dialog * createGuiRef(GuiView & lv);
4976 Dialog * createGuiSearch(GuiView & lv);
4977 Dialog * createGuiSearchAdv(GuiView & lv);
4978 Dialog * createGuiSendTo(GuiView & lv);
4979 Dialog * createGuiShowFile(GuiView & lv);
4980 Dialog * createGuiSpellchecker(GuiView & lv);
4981 Dialog * createGuiSymbols(GuiView & lv);
4982 Dialog * createGuiTabularCreate(GuiView & lv);
4983 Dialog * createGuiTexInfo(GuiView & lv);
4984 Dialog * createGuiToc(GuiView & lv);
4985 Dialog * createGuiThesaurus(GuiView & lv);
4986 Dialog * createGuiViewSource(GuiView & lv);
4987 Dialog * createGuiWrap(GuiView & lv);
4988 Dialog * createGuiProgressView(GuiView & lv);
4992 Dialog * GuiView::build(string const & name)
4994 LASSERT(isValidName(name), return nullptr);
4996 Dialog * dialog = createDialog(*this, name);
5000 if (name == "aboutlyx")
5001 return createGuiAbout(*this);
5002 if (name == "bibtex")
5003 return createGuiBibtex(*this);
5004 if (name == "changes")
5005 return createGuiChanges(*this);
5006 if (name == "character")
5007 return createGuiCharacter(*this);
5008 if (name == "citation")
5009 return createGuiCitation(*this);
5010 if (name == "compare")
5011 return createGuiCompare(*this);
5012 if (name == "comparehistory")
5013 return createGuiCompareHistory(*this);
5014 if (name == "document")
5015 return createGuiDocument(*this);
5016 if (name == "errorlist")
5017 return createGuiErrorList(*this);
5018 if (name == "external")
5019 return createGuiExternal(*this);
5021 return createGuiShowFile(*this);
5022 if (name == "findreplace")
5023 return createGuiSearch(*this);
5024 if (name == "findreplaceadv")
5025 return createGuiSearchAdv(*this);
5026 if (name == "graphics")
5027 return createGuiGraphics(*this);
5028 if (name == "include")
5029 return createGuiInclude(*this);
5030 if (name == "index")
5031 return createGuiIndex(*this);
5032 if (name == "index_print")
5033 return createGuiPrintindex(*this);
5034 if (name == "listings")
5035 return createGuiListings(*this);
5037 return createGuiLog(*this);
5038 if (name == "lyxfiles")
5039 return createGuiLyXFiles(*this);
5040 if (name == "mathdelimiter")
5041 return createGuiDelimiter(*this);
5042 if (name == "mathmatrix")
5043 return createGuiMathMatrix(*this);
5045 return createGuiNote(*this);
5046 if (name == "paragraph")
5047 return createGuiParagraph(*this);
5048 if (name == "phantom")
5049 return createGuiPhantom(*this);
5050 if (name == "prefs")
5051 return createGuiPreferences(*this);
5053 return createGuiRef(*this);
5054 if (name == "sendto")
5055 return createGuiSendTo(*this);
5056 if (name == "spellchecker")
5057 return createGuiSpellchecker(*this);
5058 if (name == "symbols")
5059 return createGuiSymbols(*this);
5060 if (name == "tabularcreate")
5061 return createGuiTabularCreate(*this);
5062 if (name == "texinfo")
5063 return createGuiTexInfo(*this);
5064 if (name == "thesaurus")
5065 return createGuiThesaurus(*this);
5067 return createGuiToc(*this);
5068 if (name == "view-source")
5069 return createGuiViewSource(*this);
5071 return createGuiWrap(*this);
5072 if (name == "progress")
5073 return createGuiProgressView(*this);
5079 SEMenu::SEMenu(QWidget * parent)
5081 QAction * action = addAction(qt_("Disable Shell Escape"));
5082 connect(action, SIGNAL(triggered()),
5083 parent, SLOT(disableShellEscape()));
5086 } // namespace frontend
5089 #include "moc_GuiView.cpp"