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 "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
53 #include "KeySymbol.h"
55 #include "LayoutFile.h"
57 #include "LyXAction.h"
61 #include "Paragraph.h"
62 #include "SpellChecker.h"
69 #include "support/convert.h"
70 #include "support/debug.h"
71 #include "support/ExceptionMessage.h"
72 #include "support/FileName.h"
73 #include "support/gettext.h"
74 #include "support/ForkedCalls.h"
75 #include "support/lassert.h"
76 #include "support/lstrings.h"
77 #include "support/os.h"
78 #include "support/Package.h"
79 #include "support/PathChanger.h"
80 #include "support/Systemcall.h"
81 #include "support/Timeout.h"
82 #include "support/ProgressInterface.h"
85 #include <QApplication>
86 #include <QCloseEvent>
87 #include <QDragEnterEvent>
90 #include <QFutureWatcher>
102 #include <QShowEvent>
104 #include <QStackedWidget>
105 #include <QStatusBar>
106 #include <QSvgRenderer>
107 #include <QtConcurrentRun>
110 #include <QWindowStateChangeEvent>
113 // sync with GuiAlert.cpp
114 #define EXPORT_in_THREAD 1
117 #include "support/bind.h"
121 #ifdef HAVE_SYS_TIME_H
122 # include <sys/time.h>
130 using namespace lyx::support;
134 using support::addExtension;
135 using support::changeExtension;
136 using support::removeExtension;
142 class BackgroundWidget : public QWidget
145 BackgroundWidget(int width, int height)
146 : width_(width), height_(height)
148 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
149 if (!lyxrc.show_banner)
151 /// The text to be written on top of the pixmap
152 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
153 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
154 /// The text to be written on top of the pixmap
155 QString const text = lyx_version ?
156 qt_("version ") + lyx_version : qt_("unknown version");
157 #if QT_VERSION >= 0x050000
158 QString imagedir = "images/";
159 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
160 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
161 if (svgRenderer.isValid()) {
162 splash_ = QPixmap(splashSize());
163 QPainter painter(&splash_);
164 svgRenderer.render(&painter);
165 splash_.setDevicePixelRatio(pixelRatio());
167 splash_ = getPixmap("images/", "banner", "png");
170 splash_ = getPixmap("images/", "banner", "svgz,png");
173 QPainter pain(&splash_);
174 pain.setPen(QColor(0, 0, 0));
175 qreal const fsize = fontSize();
178 qreal locscale = htextsize.toFloat(&ok);
181 QPointF const position = textPosition(false);
182 QPointF const hposition = textPosition(true);
183 QRectF const hrect(hposition, splashSize());
185 "widget pixel ratio: " << pixelRatio() <<
186 " splash pixel ratio: " << splashPixelRatio() <<
187 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
189 // The font used to display the version info
190 font.setStyleHint(QFont::SansSerif);
191 font.setWeight(QFont::Bold);
192 font.setPointSizeF(fsize);
194 pain.drawText(position, text);
195 // The font used to display the version info
196 font.setStyleHint(QFont::SansSerif);
197 font.setWeight(QFont::Normal);
198 font.setPointSizeF(hfsize);
199 // Check how long the logo gets with the current font
200 // and adapt if the font is running wider than what
202 QFontMetrics fm(font);
203 // Split the title into lines to measure the longest line
204 // in the current l7n.
205 QStringList titlesegs = htext.split('\n');
207 int hline = fm.height();
208 QStringList::const_iterator sit;
209 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
210 if (fm.width(*sit) > wline)
211 wline = fm.width(*sit);
213 // The longest line in the reference font (for English)
214 // is 180. Calculate scale factor from that.
215 double const wscale = wline > 0 ? (180.0 / wline) : 1;
216 // Now do the same for the height (necessary for condensed fonts)
217 double const hscale = (34.0 / hline);
218 // take the lower of the two scale factors.
219 double const scale = min(wscale, hscale);
220 // Now rescale. Also consider l7n's offset factor.
221 font.setPointSizeF(hfsize * scale * locscale);
224 pain.drawText(hrect, Qt::AlignLeft, htext);
225 setFocusPolicy(Qt::StrongFocus);
228 void paintEvent(QPaintEvent *) override
230 int const w = width_;
231 int const h = height_;
232 int const x = (width() - w) / 2;
233 int const y = (height() - h) / 2;
235 "widget pixel ratio: " << pixelRatio() <<
236 " splash pixel ratio: " << splashPixelRatio() <<
237 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
239 pain.drawPixmap(x, y, w, h, splash_);
242 void keyPressEvent(QKeyEvent * ev) override
245 setKeySymbol(&sym, ev);
247 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
259 /// Current ratio between physical pixels and device-independent pixels
260 double pixelRatio() const {
261 #if QT_VERSION >= 0x050000
262 return qt_scale_factor * devicePixelRatio();
268 qreal fontSize() const {
269 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
272 QPointF textPosition(bool const heading) const {
273 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
274 : QPointF(width_/2 - 18, height_/2 + 45);
277 QSize splashSize() const {
279 static_cast<unsigned int>(width_ * pixelRatio()),
280 static_cast<unsigned int>(height_ * pixelRatio()));
283 /// Ratio between physical pixels and device-independent pixels of splash image
284 double splashPixelRatio() const {
285 #if QT_VERSION >= 0x050000
286 return splash_.devicePixelRatio();
294 /// Toolbar store providing access to individual toolbars by name.
295 typedef map<string, GuiToolbar *> ToolbarMap;
297 typedef shared_ptr<Dialog> DialogPtr;
302 class GuiView::GuiViewPrivate
305 GuiViewPrivate(GuiViewPrivate const &);
306 void operator=(GuiViewPrivate const &);
308 GuiViewPrivate(GuiView * gv)
309 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
310 layout_(nullptr), autosave_timeout_(5000),
313 // hardcode here the platform specific icon size
314 smallIconSize = 16; // scaling problems
315 normalIconSize = 20; // ok, default if iconsize.png is missing
316 bigIconSize = 26; // better for some math icons
317 hugeIconSize = 32; // better for hires displays
320 // if it exists, use width of iconsize.png as normal size
321 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
322 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
324 QImage image(toqstr(fn.absFileName()));
325 if (image.width() < int(smallIconSize))
326 normalIconSize = smallIconSize;
327 else if (image.width() > int(giantIconSize))
328 normalIconSize = giantIconSize;
330 normalIconSize = image.width();
333 splitter_ = new QSplitter;
334 bg_widget_ = new BackgroundWidget(400, 250);
335 stack_widget_ = new QStackedWidget;
336 stack_widget_->addWidget(bg_widget_);
337 stack_widget_->addWidget(splitter_);
340 // TODO cleanup, remove the singleton, handle multiple Windows?
341 progress_ = ProgressInterface::instance();
342 if (!dynamic_cast<GuiProgress*>(progress_)) {
343 progress_ = new GuiProgress; // TODO who deletes it
344 ProgressInterface::setInstance(progress_);
347 dynamic_cast<GuiProgress*>(progress_),
348 SIGNAL(updateStatusBarMessage(QString const&)),
349 gv, SLOT(updateStatusBarMessage(QString const&)));
351 dynamic_cast<GuiProgress*>(progress_),
352 SIGNAL(clearMessageText()),
353 gv, SLOT(clearMessageText()));
360 delete stack_widget_;
365 stack_widget_->setCurrentWidget(bg_widget_);
366 bg_widget_->setUpdatesEnabled(true);
367 bg_widget_->setFocus();
370 int tabWorkAreaCount()
372 return splitter_->count();
375 TabWorkArea * tabWorkArea(int i)
377 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
380 TabWorkArea * currentTabWorkArea()
382 int areas = tabWorkAreaCount();
384 // The first TabWorkArea is always the first one, if any.
385 return tabWorkArea(0);
387 for (int i = 0; i != areas; ++i) {
388 TabWorkArea * twa = tabWorkArea(i);
389 if (current_main_work_area_ == twa->currentWorkArea())
393 // None has the focus so we just take the first one.
394 return tabWorkArea(0);
397 int countWorkAreasOf(Buffer & buf)
399 int areas = tabWorkAreaCount();
401 for (int i = 0; i != areas; ++i) {
402 TabWorkArea * twa = tabWorkArea(i);
403 if (twa->workArea(buf))
409 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
411 if (processing_thread_watcher_.isRunning()) {
412 // we prefer to cancel this preview in order to keep a snappy
416 processing_thread_watcher_.setFuture(f);
419 QSize iconSize(docstring const & icon_size)
422 if (icon_size == "small")
423 size = smallIconSize;
424 else if (icon_size == "normal")
425 size = normalIconSize;
426 else if (icon_size == "big")
428 else if (icon_size == "huge")
430 else if (icon_size == "giant")
431 size = giantIconSize;
433 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
435 if (size < smallIconSize)
436 size = smallIconSize;
438 return QSize(size, size);
441 QSize iconSize(QString const & icon_size)
443 return iconSize(qstring_to_ucs4(icon_size));
446 string & iconSize(QSize const & qsize)
448 LATTEST(qsize.width() == qsize.height());
450 static string icon_size;
452 unsigned int size = qsize.width();
454 if (size < smallIconSize)
455 size = smallIconSize;
457 if (size == smallIconSize)
459 else if (size == normalIconSize)
460 icon_size = "normal";
461 else if (size == bigIconSize)
463 else if (size == hugeIconSize)
465 else if (size == giantIconSize)
468 icon_size = convert<string>(size);
473 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
474 Buffer * buffer, string const & format);
475 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
476 Buffer * buffer, string const & format);
477 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
482 static Buffer::ExportStatus runAndDestroy(const T& func,
483 Buffer const * orig, Buffer * buffer, string const & format);
485 // TODO syncFunc/previewFunc: use bind
486 bool asyncBufferProcessing(string const & argument,
487 Buffer const * used_buffer,
488 docstring const & msg,
489 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
490 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
491 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
494 QVector<GuiWorkArea*> guiWorkAreas();
498 GuiWorkArea * current_work_area_;
499 GuiWorkArea * current_main_work_area_;
500 QSplitter * splitter_;
501 QStackedWidget * stack_widget_;
502 BackgroundWidget * bg_widget_;
504 ToolbarMap toolbars_;
505 ProgressInterface* progress_;
506 /// The main layout box.
508 * \warning Don't Delete! The layout box is actually owned by
509 * whichever toolbar contains it. All the GuiView class needs is a
510 * means of accessing it.
512 * FIXME: replace that with a proper model so that we are not limited
513 * to only one dialog.
518 map<string, DialogPtr> dialogs_;
521 QTimer statusbar_timer_;
522 /// auto-saving of buffers
523 Timeout autosave_timeout_;
526 TocModels toc_models_;
529 QFutureWatcher<docstring> autosave_watcher_;
530 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
532 string last_export_format;
533 string processing_format;
535 static QSet<Buffer const *> busyBuffers;
537 unsigned int smallIconSize;
538 unsigned int normalIconSize;
539 unsigned int bigIconSize;
540 unsigned int hugeIconSize;
541 unsigned int giantIconSize;
543 /// flag against a race condition due to multiclicks, see bug #1119
547 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
550 GuiView::GuiView(int id)
551 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
552 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
555 connect(this, SIGNAL(bufferViewChanged()),
556 this, SLOT(onBufferViewChanged()));
558 // GuiToolbars *must* be initialised before the menu bar.
559 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
562 // set ourself as the current view. This is needed for the menu bar
563 // filling, at least for the static special menu item on Mac. Otherwise
564 // they are greyed out.
565 guiApp->setCurrentView(this);
567 // Fill up the menu bar.
568 guiApp->menus().fillMenuBar(menuBar(), this, true);
570 setCentralWidget(d.stack_widget_);
572 // Start autosave timer
573 if (lyxrc.autosave) {
574 // The connection is closed when this is destroyed.
575 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
576 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
577 d.autosave_timeout_.start();
579 connect(&d.statusbar_timer_, SIGNAL(timeout()),
580 this, SLOT(clearMessage()));
582 // We don't want to keep the window in memory if it is closed.
583 setAttribute(Qt::WA_DeleteOnClose, true);
585 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
586 // QIcon::fromTheme was introduced in Qt 4.6
587 #if (QT_VERSION >= 0x040600)
588 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
589 // since the icon is provided in the application bundle. We use a themed
590 // version when available and use the bundled one as fallback.
591 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
593 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
599 // use tabbed dock area for multiple docks
600 // (such as "source" and "messages")
601 setDockOptions(QMainWindow::ForceTabbedDocks);
604 setAcceptDrops(true);
606 // add busy indicator to statusbar
607 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
608 statusBar()->addPermanentWidget(busylabel);
609 search_mode mode = theGuiApp()->imageSearchMode();
610 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
611 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
612 busylabel->setMovie(busyanim);
616 connect(&d.processing_thread_watcher_, SIGNAL(started()),
617 busylabel, SLOT(show()));
618 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
619 busylabel, SLOT(hide()));
620 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
622 QFontMetrics const fm(statusBar()->fontMetrics());
623 int const iconheight = max(int(d.normalIconSize), fm.height());
624 QSize const iconsize(iconheight, iconheight);
626 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
627 shell_escape_ = new QLabel(statusBar());
628 shell_escape_->setPixmap(shellescape);
629 shell_escape_->setScaledContents(true);
630 shell_escape_->setAlignment(Qt::AlignCenter);
631 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
632 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
633 "external commands for this document. "
634 "Right click to change."));
635 SEMenu * menu = new SEMenu(this);
636 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
637 menu, SLOT(showMenu(QPoint)));
638 shell_escape_->hide();
639 statusBar()->addPermanentWidget(shell_escape_);
641 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
642 read_only_ = new QLabel(statusBar());
643 read_only_->setPixmap(readonly);
644 read_only_->setScaledContents(true);
645 read_only_->setAlignment(Qt::AlignCenter);
647 statusBar()->addPermanentWidget(read_only_);
649 version_control_ = new QLabel(statusBar());
650 version_control_->setAlignment(Qt::AlignCenter);
651 version_control_->setFrameStyle(QFrame::StyledPanel);
652 version_control_->hide();
653 statusBar()->addPermanentWidget(version_control_);
655 statusBar()->setSizeGripEnabled(true);
658 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
659 SLOT(autoSaveThreadFinished()));
661 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
662 SLOT(processingThreadStarted()));
663 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
664 SLOT(processingThreadFinished()));
666 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
667 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
669 // set custom application bars context menu, e.g. tool bar and menu bar
670 setContextMenuPolicy(Qt::CustomContextMenu);
671 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
672 SLOT(toolBarPopup(const QPoint &)));
674 // Forbid too small unresizable window because it can happen
675 // with some window manager under X11.
676 setMinimumSize(300, 200);
678 if (lyxrc.allow_geometry_session) {
679 // Now take care of session management.
684 // no session handling, default to a sane size.
685 setGeometry(50, 50, 690, 510);
688 // clear session data if any.
690 settings.remove("views");
700 void GuiView::disableShellEscape()
702 BufferView * bv = documentBufferView();
705 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
706 bv->buffer().params().shell_escape = false;
707 bv->processUpdateFlags(Update::Force);
711 void GuiView::checkCancelBackground()
713 docstring const ttl = _("Cancel Export?");
714 docstring const msg = _("Do you want to cancel the background export process?");
716 Alert::prompt(ttl, msg, 1, 1,
717 _("&Cancel export"), _("Co&ntinue"));
719 Systemcall::killscript();
723 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
725 QVector<GuiWorkArea*> areas;
726 for (int i = 0; i < tabWorkAreaCount(); i++) {
727 TabWorkArea* ta = tabWorkArea(i);
728 for (int u = 0; u < ta->count(); u++) {
729 areas << ta->workArea(u);
735 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
736 string const & format)
738 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
741 case Buffer::ExportSuccess:
742 msg = bformat(_("Successful export to format: %1$s"), fmt);
744 case Buffer::ExportCancel:
745 msg = _("Document export cancelled.");
747 case Buffer::ExportError:
748 case Buffer::ExportNoPathToFormat:
749 case Buffer::ExportTexPathHasSpaces:
750 case Buffer::ExportConverterError:
751 msg = bformat(_("Error while exporting format: %1$s"), fmt);
753 case Buffer::PreviewSuccess:
754 msg = bformat(_("Successful preview of format: %1$s"), fmt);
756 case Buffer::PreviewError:
757 msg = bformat(_("Error while previewing format: %1$s"), fmt);
759 case Buffer::ExportKilled:
760 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
767 void GuiView::processingThreadStarted()
772 void GuiView::processingThreadFinished()
774 QFutureWatcher<Buffer::ExportStatus> const * watcher =
775 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
777 Buffer::ExportStatus const status = watcher->result();
778 handleExportStatus(this, status, d.processing_format);
781 BufferView const * const bv = currentBufferView();
782 if (bv && !bv->buffer().errorList("Export").empty()) {
787 bool const error = (status != Buffer::ExportSuccess &&
788 status != Buffer::PreviewSuccess &&
789 status != Buffer::ExportCancel);
791 ErrorList & el = bv->buffer().errorList(d.last_export_format);
792 // at this point, we do not know if buffer-view or
793 // master-buffer-view was called. If there was an export error,
794 // and the current buffer's error log is empty, we guess that
795 // it must be master-buffer-view that was called so we set
797 errors(d.last_export_format, el.empty());
802 void GuiView::autoSaveThreadFinished()
804 QFutureWatcher<docstring> const * watcher =
805 static_cast<QFutureWatcher<docstring> const *>(sender());
806 message(watcher->result());
811 void GuiView::saveLayout() const
814 settings.setValue("zoom_ratio", zoom_ratio_);
815 settings.setValue("devel_mode", devel_mode_);
816 settings.beginGroup("views");
817 settings.beginGroup(QString::number(id_));
818 #if defined(Q_WS_X11) || defined(QPA_XCB)
819 settings.setValue("pos", pos());
820 settings.setValue("size", size());
822 settings.setValue("geometry", saveGeometry());
824 settings.setValue("layout", saveState(0));
825 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
829 void GuiView::saveUISettings() const
833 // Save the toolbar private states
834 ToolbarMap::iterator end = d.toolbars_.end();
835 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
836 it->second->saveSession(settings);
837 // Now take care of all other dialogs
838 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
839 for (; it!= d.dialogs_.end(); ++it)
840 it->second->saveSession(settings);
844 bool GuiView::restoreLayout()
847 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
848 // Actual zoom value: default zoom + fractional offset
849 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
850 if (zoom < static_cast<int>(zoom_min_))
852 lyxrc.currentZoom = zoom;
853 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
854 settings.beginGroup("views");
855 settings.beginGroup(QString::number(id_));
856 QString const icon_key = "icon_size";
857 if (!settings.contains(icon_key))
860 //code below is skipped when when ~/.config/LyX is (re)created
861 setIconSize(d.iconSize(settings.value(icon_key).toString()));
863 #if defined(Q_WS_X11) || defined(QPA_XCB)
864 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
865 QSize size = settings.value("size", QSize(690, 510)).toSize();
869 // Work-around for bug #6034: the window ends up in an undetermined
870 // state when trying to restore a maximized window when it is
871 // already maximized.
872 if (!(windowState() & Qt::WindowMaximized))
873 if (!restoreGeometry(settings.value("geometry").toByteArray()))
874 setGeometry(50, 50, 690, 510);
876 // Make sure layout is correctly oriented.
877 setLayoutDirection(qApp->layoutDirection());
879 // Allow the toc and view-source dock widget to be restored if needed.
881 if ((dialog = findOrBuild("toc", true)))
882 // see bug 5082. At least setup title and enabled state.
883 // Visibility will be adjusted by restoreState below.
884 dialog->prepareView();
885 if ((dialog = findOrBuild("view-source", true)))
886 dialog->prepareView();
887 if ((dialog = findOrBuild("progress", true)))
888 dialog->prepareView();
890 if (!restoreState(settings.value("layout").toByteArray(), 0))
893 // init the toolbars that have not been restored
894 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
895 Toolbars::Infos::iterator end = guiApp->toolbars().end();
896 for (; cit != end; ++cit) {
897 GuiToolbar * tb = toolbar(cit->name);
898 if (tb && !tb->isRestored())
899 initToolbar(cit->name);
902 // update lock (all) toolbars positions
903 updateLockToolbars();
910 GuiToolbar * GuiView::toolbar(string const & name)
912 ToolbarMap::iterator it = d.toolbars_.find(name);
913 if (it != d.toolbars_.end())
916 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
921 void GuiView::updateLockToolbars()
923 toolbarsMovable_ = false;
924 for (ToolbarInfo const & info : guiApp->toolbars()) {
925 GuiToolbar * tb = toolbar(info.name);
926 if (tb && tb->isMovable())
927 toolbarsMovable_ = true;
932 void GuiView::constructToolbars()
934 ToolbarMap::iterator it = d.toolbars_.begin();
935 for (; it != d.toolbars_.end(); ++it)
939 // I don't like doing this here, but the standard toolbar
940 // destroys this object when it's destroyed itself (vfr)
941 d.layout_ = new LayoutBox(*this);
942 d.stack_widget_->addWidget(d.layout_);
943 d.layout_->move(0,0);
945 // extracts the toolbars from the backend
946 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
947 Toolbars::Infos::iterator end = guiApp->toolbars().end();
948 for (; cit != end; ++cit)
949 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
953 void GuiView::initToolbars()
955 // extracts the toolbars from the backend
956 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
957 Toolbars::Infos::iterator end = guiApp->toolbars().end();
958 for (; cit != end; ++cit)
959 initToolbar(cit->name);
963 void GuiView::initToolbar(string const & name)
965 GuiToolbar * tb = toolbar(name);
968 int const visibility = guiApp->toolbars().defaultVisibility(name);
969 bool newline = !(visibility & Toolbars::SAMEROW);
970 tb->setVisible(false);
971 tb->setVisibility(visibility);
973 if (visibility & Toolbars::TOP) {
975 addToolBarBreak(Qt::TopToolBarArea);
976 addToolBar(Qt::TopToolBarArea, tb);
979 if (visibility & Toolbars::BOTTOM) {
981 addToolBarBreak(Qt::BottomToolBarArea);
982 addToolBar(Qt::BottomToolBarArea, tb);
985 if (visibility & Toolbars::LEFT) {
987 addToolBarBreak(Qt::LeftToolBarArea);
988 addToolBar(Qt::LeftToolBarArea, tb);
991 if (visibility & Toolbars::RIGHT) {
993 addToolBarBreak(Qt::RightToolBarArea);
994 addToolBar(Qt::RightToolBarArea, tb);
997 if (visibility & Toolbars::ON)
998 tb->setVisible(true);
1000 tb->setMovable(true);
1004 TocModels & GuiView::tocModels()
1006 return d.toc_models_;
1010 void GuiView::setFocus()
1012 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1013 QMainWindow::setFocus();
1017 bool GuiView::hasFocus() const
1019 if (currentWorkArea())
1020 return currentWorkArea()->hasFocus();
1021 if (currentMainWorkArea())
1022 return currentMainWorkArea()->hasFocus();
1023 return d.bg_widget_->hasFocus();
1027 void GuiView::focusInEvent(QFocusEvent * e)
1029 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1030 QMainWindow::focusInEvent(e);
1031 // Make sure guiApp points to the correct view.
1032 guiApp->setCurrentView(this);
1033 if (currentWorkArea())
1034 currentWorkArea()->setFocus();
1035 else if (currentMainWorkArea())
1036 currentMainWorkArea()->setFocus();
1038 d.bg_widget_->setFocus();
1042 void GuiView::showEvent(QShowEvent * e)
1044 LYXERR(Debug::GUI, "Passed Geometry "
1045 << size().height() << "x" << size().width()
1046 << "+" << pos().x() << "+" << pos().y());
1048 if (d.splitter_->count() == 0)
1049 // No work area, switch to the background widget.
1053 QMainWindow::showEvent(e);
1057 bool GuiView::closeScheduled()
1064 bool GuiView::prepareAllBuffersForLogout()
1066 Buffer * first = theBufferList().first();
1070 // First, iterate over all buffers and ask the users if unsaved
1071 // changes should be saved.
1072 // We cannot use a for loop as the buffer list cycles.
1075 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1077 b = theBufferList().next(b);
1078 } while (b != first);
1080 // Next, save session state
1081 // When a view/window was closed before without quitting LyX, there
1082 // are already entries in the lastOpened list.
1083 theSession().lastOpened().clear();
1090 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1091 ** is responsibility of the container (e.g., dialog)
1093 void GuiView::closeEvent(QCloseEvent * close_event)
1095 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1097 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1098 Alert::warning(_("Exit LyX"),
1099 _("LyX could not be closed because documents are being processed by LyX."));
1100 close_event->setAccepted(false);
1104 // If the user pressed the x (so we didn't call closeView
1105 // programmatically), we want to clear all existing entries.
1107 theSession().lastOpened().clear();
1112 // it can happen that this event arrives without selecting the view,
1113 // e.g. when clicking the close button on a background window.
1115 if (!closeWorkAreaAll()) {
1117 close_event->ignore();
1121 // Make sure that nothing will use this to be closed View.
1122 guiApp->unregisterView(this);
1124 if (isFullScreen()) {
1125 // Switch off fullscreen before closing.
1130 // Make sure the timer time out will not trigger a statusbar update.
1131 d.statusbar_timer_.stop();
1133 // Saving fullscreen requires additional tweaks in the toolbar code.
1134 // It wouldn't also work under linux natively.
1135 if (lyxrc.allow_geometry_session) {
1140 close_event->accept();
1144 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1146 if (event->mimeData()->hasUrls())
1148 /// \todo Ask lyx-devel is this is enough:
1149 /// if (event->mimeData()->hasFormat("text/plain"))
1150 /// event->acceptProposedAction();
1154 void GuiView::dropEvent(QDropEvent * event)
1156 QList<QUrl> files = event->mimeData()->urls();
1157 if (files.isEmpty())
1160 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1161 for (int i = 0; i != files.size(); ++i) {
1162 string const file = os::internal_path(fromqstr(
1163 files.at(i).toLocalFile()));
1167 string const ext = support::getExtension(file);
1168 vector<const Format *> found_formats;
1170 // Find all formats that have the correct extension.
1171 vector<const Format *> const & import_formats
1172 = theConverters().importableFormats();
1173 vector<const Format *>::const_iterator it = import_formats.begin();
1174 for (; it != import_formats.end(); ++it)
1175 if ((*it)->hasExtension(ext))
1176 found_formats.push_back(*it);
1179 if (!found_formats.empty()) {
1180 if (found_formats.size() > 1) {
1181 //FIXME: show a dialog to choose the correct importable format
1182 LYXERR(Debug::FILES,
1183 "Multiple importable formats found, selecting first");
1185 string const arg = found_formats[0]->name() + " " + file;
1186 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1189 //FIXME: do we have to explicitly check whether it's a lyx file?
1190 LYXERR(Debug::FILES,
1191 "No formats found, trying to open it as a lyx file");
1192 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1194 // add the functions to the queue
1195 guiApp->addToFuncRequestQueue(cmd);
1198 // now process the collected functions. We perform the events
1199 // asynchronously. This prevents potential problems in case the
1200 // BufferView is closed within an event.
1201 guiApp->processFuncRequestQueueAsync();
1205 void GuiView::message(docstring const & str)
1207 if (ForkedProcess::iAmAChild())
1210 // call is moved to GUI-thread by GuiProgress
1211 d.progress_->appendMessage(toqstr(str));
1215 void GuiView::clearMessageText()
1217 message(docstring());
1221 void GuiView::updateStatusBarMessage(QString const & str)
1223 statusBar()->showMessage(str);
1224 d.statusbar_timer_.stop();
1225 d.statusbar_timer_.start(3000);
1229 void GuiView::clearMessage()
1231 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1232 // the hasFocus function mostly returns false, even if the focus is on
1233 // a workarea in this view.
1237 d.statusbar_timer_.stop();
1241 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1243 if (wa != d.current_work_area_
1244 || wa->bufferView().buffer().isInternal())
1246 Buffer const & buf = wa->bufferView().buffer();
1247 // Set the windows title
1248 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1249 if (buf.notifiesExternalModification()) {
1250 title = bformat(_("%1$s (modified externally)"), title);
1251 // If the external modification status has changed, then maybe the status of
1252 // buffer-save has changed too.
1256 title += from_ascii(" - LyX");
1258 setWindowTitle(toqstr(title));
1259 // Sets the path for the window: this is used by OSX to
1260 // allow a context click on the title bar showing a menu
1261 // with the path up to the file
1262 setWindowFilePath(toqstr(buf.absFileName()));
1263 // Tell Qt whether the current document is changed
1264 setWindowModified(!buf.isClean());
1266 if (buf.params().shell_escape)
1267 shell_escape_->show();
1269 shell_escape_->hide();
1271 if (buf.hasReadonlyFlag())
1276 if (buf.lyxvc().inUse()) {
1277 version_control_->show();
1278 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1280 version_control_->hide();
1284 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1286 if (d.current_work_area_)
1287 // disconnect the current work area from all slots
1288 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1290 disconnectBufferView();
1291 connectBufferView(wa->bufferView());
1292 connectBuffer(wa->bufferView().buffer());
1293 d.current_work_area_ = wa;
1294 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1295 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1296 QObject::connect(wa, SIGNAL(busy(bool)),
1297 this, SLOT(setBusy(bool)));
1298 // connection of a signal to a signal
1299 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1300 this, SIGNAL(bufferViewChanged()));
1301 Q_EMIT updateWindowTitle(wa);
1302 Q_EMIT bufferViewChanged();
1306 void GuiView::onBufferViewChanged()
1309 // Buffer-dependent dialogs must be updated. This is done here because
1310 // some dialogs require buffer()->text.
1315 void GuiView::on_lastWorkAreaRemoved()
1318 // We already are in a close event. Nothing more to do.
1321 if (d.splitter_->count() > 1)
1322 // We have a splitter so don't close anything.
1325 // Reset and updates the dialogs.
1326 Q_EMIT bufferViewChanged();
1331 if (lyxrc.open_buffers_in_tabs)
1332 // Nothing more to do, the window should stay open.
1335 if (guiApp->viewIds().size() > 1) {
1341 // On Mac we also close the last window because the application stay
1342 // resident in memory. On other platforms we don't close the last
1343 // window because this would quit the application.
1349 void GuiView::updateStatusBar()
1351 // let the user see the explicit message
1352 if (d.statusbar_timer_.isActive())
1359 void GuiView::showMessage()
1363 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1364 if (msg.isEmpty()) {
1365 BufferView const * bv = currentBufferView();
1367 msg = toqstr(bv->cursor().currentState(devel_mode_));
1369 msg = qt_("Welcome to LyX!");
1371 statusBar()->showMessage(msg);
1375 bool GuiView::event(QEvent * e)
1379 // Useful debug code:
1380 //case QEvent::ActivationChange:
1381 //case QEvent::WindowDeactivate:
1382 //case QEvent::Paint:
1383 //case QEvent::Enter:
1384 //case QEvent::Leave:
1385 //case QEvent::HoverEnter:
1386 //case QEvent::HoverLeave:
1387 //case QEvent::HoverMove:
1388 //case QEvent::StatusTip:
1389 //case QEvent::DragEnter:
1390 //case QEvent::DragLeave:
1391 //case QEvent::Drop:
1394 case QEvent::WindowStateChange: {
1395 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1396 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1397 bool result = QMainWindow::event(e);
1398 bool nfstate = (windowState() & Qt::WindowFullScreen);
1399 if (!ofstate && nfstate) {
1400 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1401 // switch to full-screen state
1402 if (lyxrc.full_screen_statusbar)
1403 statusBar()->hide();
1404 if (lyxrc.full_screen_menubar)
1406 if (lyxrc.full_screen_toolbars) {
1407 ToolbarMap::iterator end = d.toolbars_.end();
1408 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1409 if (it->second->isVisibiltyOn() && it->second->isVisible())
1412 for (int i = 0; i != d.splitter_->count(); ++i)
1413 d.tabWorkArea(i)->setFullScreen(true);
1414 setContentsMargins(-2, -2, -2, -2);
1416 hideDialogs("prefs", nullptr);
1417 } else if (ofstate && !nfstate) {
1418 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1419 // switch back from full-screen state
1420 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1421 statusBar()->show();
1422 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1424 if (lyxrc.full_screen_toolbars) {
1425 ToolbarMap::iterator end = d.toolbars_.end();
1426 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1427 if (it->second->isVisibiltyOn() && !it->second->isVisible())
1431 for (int i = 0; i != d.splitter_->count(); ++i)
1432 d.tabWorkArea(i)->setFullScreen(false);
1433 setContentsMargins(0, 0, 0, 0);
1437 case QEvent::WindowActivate: {
1438 GuiView * old_view = guiApp->currentView();
1439 if (this == old_view) {
1441 return QMainWindow::event(e);
1443 if (old_view && old_view->currentBufferView()) {
1444 // save current selection to the selection buffer to allow
1445 // middle-button paste in this window.
1446 cap::saveSelection(old_view->currentBufferView()->cursor());
1448 guiApp->setCurrentView(this);
1449 if (d.current_work_area_)
1450 on_currentWorkAreaChanged(d.current_work_area_);
1454 return QMainWindow::event(e);
1457 case QEvent::ShortcutOverride: {
1459 if (isFullScreen() && menuBar()->isHidden()) {
1460 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1461 // FIXME: we should also try to detect special LyX shortcut such as
1462 // Alt-P and Alt-M. Right now there is a hack in
1463 // GuiWorkArea::processKeySym() that hides again the menubar for
1465 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1467 return QMainWindow::event(e);
1470 return QMainWindow::event(e);
1474 return QMainWindow::event(e);
1478 void GuiView::resetWindowTitle()
1480 setWindowTitle(qt_("LyX"));
1483 bool GuiView::focusNextPrevChild(bool /*next*/)
1490 bool GuiView::busy() const
1496 void GuiView::setBusy(bool busy)
1498 bool const busy_before = busy_ > 0;
1499 busy ? ++busy_ : --busy_;
1500 if ((busy_ > 0) == busy_before)
1501 // busy state didn't change
1505 QApplication::setOverrideCursor(Qt::WaitCursor);
1508 QApplication::restoreOverrideCursor();
1513 void GuiView::resetCommandExecute()
1515 command_execute_ = false;
1520 double GuiView::pixelRatio() const
1522 #if QT_VERSION >= 0x050000
1523 return qt_scale_factor * devicePixelRatio();
1530 GuiWorkArea * GuiView::workArea(int index)
1532 if (TabWorkArea * twa = d.currentTabWorkArea())
1533 if (index < twa->count())
1534 return twa->workArea(index);
1539 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1541 if (currentWorkArea()
1542 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1543 return currentWorkArea();
1544 if (TabWorkArea * twa = d.currentTabWorkArea())
1545 return twa->workArea(buffer);
1550 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1552 // Automatically create a TabWorkArea if there are none yet.
1553 TabWorkArea * tab_widget = d.splitter_->count()
1554 ? d.currentTabWorkArea() : addTabWorkArea();
1555 return tab_widget->addWorkArea(buffer, *this);
1559 TabWorkArea * GuiView::addTabWorkArea()
1561 TabWorkArea * twa = new TabWorkArea;
1562 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1563 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1564 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1565 this, SLOT(on_lastWorkAreaRemoved()));
1567 d.splitter_->addWidget(twa);
1568 d.stack_widget_->setCurrentWidget(d.splitter_);
1573 GuiWorkArea const * GuiView::currentWorkArea() const
1575 return d.current_work_area_;
1579 GuiWorkArea * GuiView::currentWorkArea()
1581 return d.current_work_area_;
1585 GuiWorkArea const * GuiView::currentMainWorkArea() const
1587 if (!d.currentTabWorkArea())
1589 return d.currentTabWorkArea()->currentWorkArea();
1593 GuiWorkArea * GuiView::currentMainWorkArea()
1595 if (!d.currentTabWorkArea())
1597 return d.currentTabWorkArea()->currentWorkArea();
1601 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1603 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1605 d.current_work_area_ = nullptr;
1607 Q_EMIT bufferViewChanged();
1611 // FIXME: I've no clue why this is here and why it accesses
1612 // theGuiApp()->currentView, which might be 0 (bug 6464).
1613 // See also 27525 (vfr).
1614 if (theGuiApp()->currentView() == this
1615 && theGuiApp()->currentView()->currentWorkArea() == wa)
1618 if (currentBufferView())
1619 cap::saveSelection(currentBufferView()->cursor());
1621 theGuiApp()->setCurrentView(this);
1622 d.current_work_area_ = wa;
1624 // We need to reset this now, because it will need to be
1625 // right if the tabWorkArea gets reset in the for loop. We
1626 // will change it back if we aren't in that case.
1627 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1628 d.current_main_work_area_ = wa;
1630 for (int i = 0; i != d.splitter_->count(); ++i) {
1631 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1632 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1633 << ", Current main wa: " << currentMainWorkArea());
1638 d.current_main_work_area_ = old_cmwa;
1640 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1641 on_currentWorkAreaChanged(wa);
1642 BufferView & bv = wa->bufferView();
1643 bv.cursor().fixIfBroken();
1645 wa->setUpdatesEnabled(true);
1646 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1650 void GuiView::removeWorkArea(GuiWorkArea * wa)
1652 LASSERT(wa, return);
1653 if (wa == d.current_work_area_) {
1655 disconnectBufferView();
1656 d.current_work_area_ = nullptr;
1657 d.current_main_work_area_ = nullptr;
1660 bool found_twa = false;
1661 for (int i = 0; i != d.splitter_->count(); ++i) {
1662 TabWorkArea * twa = d.tabWorkArea(i);
1663 if (twa->removeWorkArea(wa)) {
1664 // Found in this tab group, and deleted the GuiWorkArea.
1666 if (twa->count() != 0) {
1667 if (d.current_work_area_ == nullptr)
1668 // This means that we are closing the current GuiWorkArea, so
1669 // switch to the next GuiWorkArea in the found TabWorkArea.
1670 setCurrentWorkArea(twa->currentWorkArea());
1672 // No more WorkAreas in this tab group, so delete it.
1679 // It is not a tabbed work area (i.e., the search work area), so it
1680 // should be deleted by other means.
1681 LASSERT(found_twa, return);
1683 if (d.current_work_area_ == nullptr) {
1684 if (d.splitter_->count() != 0) {
1685 TabWorkArea * twa = d.currentTabWorkArea();
1686 setCurrentWorkArea(twa->currentWorkArea());
1688 // No more work areas, switch to the background widget.
1689 setCurrentWorkArea(nullptr);
1695 LayoutBox * GuiView::getLayoutDialog() const
1701 void GuiView::updateLayoutList()
1704 d.layout_->updateContents(false);
1708 void GuiView::updateToolbars()
1710 ToolbarMap::iterator end = d.toolbars_.end();
1711 if (d.current_work_area_) {
1713 if (d.current_work_area_->bufferView().cursor().inMathed()
1714 && !d.current_work_area_->bufferView().cursor().inRegexped())
1715 context |= Toolbars::MATH;
1716 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1717 context |= Toolbars::TABLE;
1718 if (currentBufferView()->buffer().areChangesPresent()
1719 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1720 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1721 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1722 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1723 context |= Toolbars::REVIEW;
1724 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1725 context |= Toolbars::MATHMACROTEMPLATE;
1726 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1727 context |= Toolbars::IPA;
1728 if (command_execute_)
1729 context |= Toolbars::MINIBUFFER;
1730 if (minibuffer_focus_) {
1731 context |= Toolbars::MINIBUFFER_FOCUS;
1732 minibuffer_focus_ = false;
1735 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1736 it->second->update(context);
1738 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1739 it->second->update();
1743 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1745 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1746 LASSERT(newBuffer, return);
1748 GuiWorkArea * wa = workArea(*newBuffer);
1749 if (wa == nullptr) {
1751 newBuffer->masterBuffer()->updateBuffer();
1753 wa = addWorkArea(*newBuffer);
1754 // scroll to the position when the BufferView was last closed
1755 if (lyxrc.use_lastfilepos) {
1756 LastFilePosSection::FilePos filepos =
1757 theSession().lastFilePos().load(newBuffer->fileName());
1758 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1761 //Disconnect the old buffer...there's no new one.
1764 connectBuffer(*newBuffer);
1765 connectBufferView(wa->bufferView());
1767 setCurrentWorkArea(wa);
1771 void GuiView::connectBuffer(Buffer & buf)
1773 buf.setGuiDelegate(this);
1777 void GuiView::disconnectBuffer()
1779 if (d.current_work_area_)
1780 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1784 void GuiView::connectBufferView(BufferView & bv)
1786 bv.setGuiDelegate(this);
1790 void GuiView::disconnectBufferView()
1792 if (d.current_work_area_)
1793 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1797 void GuiView::errors(string const & error_type, bool from_master)
1799 BufferView const * const bv = currentBufferView();
1803 ErrorList const & el = from_master ?
1804 bv->buffer().masterBuffer()->errorList(error_type) :
1805 bv->buffer().errorList(error_type);
1810 string err = error_type;
1812 err = "from_master|" + error_type;
1813 showDialog("errorlist", err);
1817 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1819 d.toc_models_.updateItem(toqstr(type), dit);
1823 void GuiView::structureChanged()
1825 // This is called from the Buffer, which has no way to ensure that cursors
1826 // in BufferView remain valid.
1827 if (documentBufferView())
1828 documentBufferView()->cursor().sanitize();
1829 // FIXME: This is slightly expensive, though less than the tocBackend update
1830 // (#9880). This also resets the view in the Toc Widget (#6675).
1831 d.toc_models_.reset(documentBufferView());
1832 // Navigator needs more than a simple update in this case. It needs to be
1834 updateDialog("toc", "");
1838 void GuiView::updateDialog(string const & name, string const & sdata)
1840 if (!isDialogVisible(name))
1843 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1844 if (it == d.dialogs_.end())
1847 Dialog * const dialog = it->second.get();
1848 if (dialog->isVisibleView())
1849 dialog->initialiseParams(sdata);
1853 BufferView * GuiView::documentBufferView()
1855 return currentMainWorkArea()
1856 ? ¤tMainWorkArea()->bufferView()
1861 BufferView const * GuiView::documentBufferView() const
1863 return currentMainWorkArea()
1864 ? ¤tMainWorkArea()->bufferView()
1869 BufferView * GuiView::currentBufferView()
1871 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1875 BufferView const * GuiView::currentBufferView() const
1877 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1881 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1882 Buffer const * orig, Buffer * clone)
1884 bool const success = clone->autoSave();
1886 busyBuffers.remove(orig);
1888 ? _("Automatic save done.")
1889 : _("Automatic save failed!");
1893 void GuiView::autoSave()
1895 LYXERR(Debug::INFO, "Running autoSave()");
1897 Buffer * buffer = documentBufferView()
1898 ? &documentBufferView()->buffer() : nullptr;
1900 resetAutosaveTimers();
1904 GuiViewPrivate::busyBuffers.insert(buffer);
1905 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1906 buffer, buffer->cloneBufferOnly());
1907 d.autosave_watcher_.setFuture(f);
1908 resetAutosaveTimers();
1912 void GuiView::resetAutosaveTimers()
1915 d.autosave_timeout_.restart();
1919 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1922 Buffer * buf = currentBufferView()
1923 ? ¤tBufferView()->buffer() : nullptr;
1924 Buffer * doc_buffer = documentBufferView()
1925 ? &(documentBufferView()->buffer()) : nullptr;
1928 /* In LyX/Mac, when a dialog is open, the menus of the
1929 application can still be accessed without giving focus to
1930 the main window. In this case, we want to disable the menu
1931 entries that are buffer-related.
1932 This code must not be used on Linux and Windows, since it
1933 would disable buffer-related entries when hovering over the
1934 menu (see bug #9574).
1936 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1942 // Check whether we need a buffer
1943 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1944 // no, exit directly
1945 flag.message(from_utf8(N_("Command not allowed with"
1946 "out any document open")));
1947 flag.setEnabled(false);
1951 if (cmd.origin() == FuncRequest::TOC) {
1952 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1953 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1954 flag.setEnabled(false);
1958 switch(cmd.action()) {
1959 case LFUN_BUFFER_IMPORT:
1962 case LFUN_MASTER_BUFFER_EXPORT:
1964 && (doc_buffer->parent() != nullptr
1965 || doc_buffer->hasChildren())
1966 && !d.processing_thread_watcher_.isRunning()
1967 // this launches a dialog, which would be in the wrong Buffer
1968 && !(::lyx::operator==(cmd.argument(), "custom"));
1971 case LFUN_MASTER_BUFFER_UPDATE:
1972 case LFUN_MASTER_BUFFER_VIEW:
1974 && (doc_buffer->parent() != nullptr
1975 || doc_buffer->hasChildren())
1976 && !d.processing_thread_watcher_.isRunning();
1979 case LFUN_BUFFER_UPDATE:
1980 case LFUN_BUFFER_VIEW: {
1981 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1985 string format = to_utf8(cmd.argument());
1986 if (cmd.argument().empty())
1987 format = doc_buffer->params().getDefaultOutputFormat();
1988 enable = doc_buffer->params().isExportable(format, true);
1992 case LFUN_BUFFER_RELOAD:
1993 enable = doc_buffer && !doc_buffer->isUnnamed()
1994 && doc_buffer->fileName().exists()
1995 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1998 case LFUN_BUFFER_RESET_EXPORT:
1999 enable = doc_buffer != nullptr;
2002 case LFUN_BUFFER_CHILD_OPEN:
2003 enable = doc_buffer != nullptr;
2006 case LFUN_MASTER_BUFFER_FORALL: {
2007 if (doc_buffer == nullptr) {
2008 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2012 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2013 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2014 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2019 for (Buffer * buf : doc_buffer->allRelatives()) {
2020 GuiWorkArea * wa = workArea(*buf);
2023 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2024 enable = flag.enabled();
2031 case LFUN_BUFFER_WRITE:
2032 enable = doc_buffer && (doc_buffer->isUnnamed()
2033 || (!doc_buffer->isClean()
2034 || cmd.argument() == "force"));
2037 //FIXME: This LFUN should be moved to GuiApplication.
2038 case LFUN_BUFFER_WRITE_ALL: {
2039 // We enable the command only if there are some modified buffers
2040 Buffer * first = theBufferList().first();
2045 // We cannot use a for loop as the buffer list is a cycle.
2047 if (!b->isClean()) {
2051 b = theBufferList().next(b);
2052 } while (b != first);
2056 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2057 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2060 case LFUN_BUFFER_EXPORT: {
2061 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2065 return doc_buffer->getStatus(cmd, flag);
2068 case LFUN_BUFFER_EXPORT_AS:
2069 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2074 case LFUN_BUFFER_WRITE_AS:
2075 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2076 enable = doc_buffer != nullptr;
2079 case LFUN_EXPORT_CANCEL:
2080 enable = d.processing_thread_watcher_.isRunning();
2083 case LFUN_BUFFER_CLOSE:
2084 case LFUN_VIEW_CLOSE:
2085 enable = doc_buffer != nullptr;
2088 case LFUN_BUFFER_CLOSE_ALL:
2089 enable = theBufferList().last() != theBufferList().first();
2092 case LFUN_BUFFER_CHKTEX: {
2093 // hide if we have no checktex command
2094 if (lyxrc.chktex_command.empty()) {
2095 flag.setUnknown(true);
2099 if (!doc_buffer || !doc_buffer->params().isLatex()
2100 || d.processing_thread_watcher_.isRunning()) {
2101 // grey out, don't hide
2109 case LFUN_VIEW_SPLIT:
2110 if (cmd.getArg(0) == "vertical")
2111 enable = doc_buffer && (d.splitter_->count() == 1 ||
2112 d.splitter_->orientation() == Qt::Vertical);
2114 enable = doc_buffer && (d.splitter_->count() == 1 ||
2115 d.splitter_->orientation() == Qt::Horizontal);
2118 case LFUN_TAB_GROUP_CLOSE:
2119 enable = d.tabWorkAreaCount() > 1;
2122 case LFUN_DEVEL_MODE_TOGGLE:
2123 flag.setOnOff(devel_mode_);
2126 case LFUN_TOOLBAR_TOGGLE: {
2127 string const name = cmd.getArg(0);
2128 if (GuiToolbar * t = toolbar(name))
2129 flag.setOnOff(t->isVisible());
2132 docstring const msg =
2133 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2139 case LFUN_TOOLBAR_MOVABLE: {
2140 string const name = cmd.getArg(0);
2141 // use negation since locked == !movable
2143 // toolbar name * locks all toolbars
2144 flag.setOnOff(!toolbarsMovable_);
2145 else if (GuiToolbar * t = toolbar(name))
2146 flag.setOnOff(!(t->isMovable()));
2149 docstring const msg =
2150 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2156 case LFUN_ICON_SIZE:
2157 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2160 case LFUN_DROP_LAYOUTS_CHOICE:
2161 enable = buf != nullptr;
2164 case LFUN_UI_TOGGLE:
2165 flag.setOnOff(isFullScreen());
2168 case LFUN_DIALOG_DISCONNECT_INSET:
2171 case LFUN_DIALOG_HIDE:
2172 // FIXME: should we check if the dialog is shown?
2175 case LFUN_DIALOG_TOGGLE:
2176 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2179 case LFUN_DIALOG_SHOW: {
2180 string const name = cmd.getArg(0);
2182 enable = name == "aboutlyx"
2183 || name == "file" //FIXME: should be removed.
2184 || name == "lyxfiles"
2186 || name == "texinfo"
2187 || name == "progress"
2188 || name == "compare";
2189 else if (name == "character" || name == "symbols"
2190 || name == "mathdelimiter" || name == "mathmatrix") {
2191 if (!buf || buf->isReadonly())
2194 Cursor const & cur = currentBufferView()->cursor();
2195 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2198 else if (name == "latexlog")
2199 enable = FileName(doc_buffer->logName()).isReadableFile();
2200 else if (name == "spellchecker")
2201 enable = theSpellChecker()
2202 && !doc_buffer->isReadonly()
2203 && !doc_buffer->text().empty();
2204 else if (name == "vclog")
2205 enable = doc_buffer->lyxvc().inUse();
2209 case LFUN_DIALOG_UPDATE: {
2210 string const name = cmd.getArg(0);
2212 enable = name == "prefs";
2216 case LFUN_COMMAND_EXECUTE:
2218 case LFUN_MENU_OPEN:
2219 // Nothing to check.
2222 case LFUN_COMPLETION_INLINE:
2223 if (!d.current_work_area_
2224 || !d.current_work_area_->completer().inlinePossible(
2225 currentBufferView()->cursor()))
2229 case LFUN_COMPLETION_POPUP:
2230 if (!d.current_work_area_
2231 || !d.current_work_area_->completer().popupPossible(
2232 currentBufferView()->cursor()))
2237 if (!d.current_work_area_
2238 || !d.current_work_area_->completer().inlinePossible(
2239 currentBufferView()->cursor()))
2243 case LFUN_COMPLETION_ACCEPT:
2244 if (!d.current_work_area_
2245 || (!d.current_work_area_->completer().popupVisible()
2246 && !d.current_work_area_->completer().inlineVisible()
2247 && !d.current_work_area_->completer().completionAvailable()))
2251 case LFUN_COMPLETION_CANCEL:
2252 if (!d.current_work_area_
2253 || (!d.current_work_area_->completer().popupVisible()
2254 && !d.current_work_area_->completer().inlineVisible()))
2258 case LFUN_BUFFER_ZOOM_OUT:
2259 case LFUN_BUFFER_ZOOM_IN: {
2260 // only diff between these two is that the default for ZOOM_OUT
2262 bool const neg_zoom =
2263 convert<int>(cmd.argument()) < 0 ||
2264 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2265 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2266 docstring const msg =
2267 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2271 enable = doc_buffer;
2275 case LFUN_BUFFER_ZOOM: {
2276 bool const less_than_min_zoom =
2277 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2278 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2279 docstring const msg =
2280 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2285 enable = doc_buffer;
2289 case LFUN_BUFFER_MOVE_NEXT:
2290 case LFUN_BUFFER_MOVE_PREVIOUS:
2291 // we do not cycle when moving
2292 case LFUN_BUFFER_NEXT:
2293 case LFUN_BUFFER_PREVIOUS:
2294 // because we cycle, it doesn't matter whether on first or last
2295 enable = (d.currentTabWorkArea()->count() > 1);
2297 case LFUN_BUFFER_SWITCH:
2298 // toggle on the current buffer, but do not toggle off
2299 // the other ones (is that a good idea?)
2301 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2302 flag.setOnOff(true);
2305 case LFUN_VC_REGISTER:
2306 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2308 case LFUN_VC_RENAME:
2309 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2312 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2314 case LFUN_VC_CHECK_IN:
2315 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2317 case LFUN_VC_CHECK_OUT:
2318 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2320 case LFUN_VC_LOCKING_TOGGLE:
2321 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2322 && doc_buffer->lyxvc().lockingToggleEnabled();
2323 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2325 case LFUN_VC_REVERT:
2326 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2327 && !doc_buffer->hasReadonlyFlag();
2329 case LFUN_VC_UNDO_LAST:
2330 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2332 case LFUN_VC_REPO_UPDATE:
2333 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2335 case LFUN_VC_COMMAND: {
2336 if (cmd.argument().empty())
2338 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2342 case LFUN_VC_COMPARE:
2343 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2346 case LFUN_SERVER_GOTO_FILE_ROW:
2347 case LFUN_LYX_ACTIVATE:
2348 case LFUN_WINDOW_RAISE:
2350 case LFUN_FORWARD_SEARCH:
2351 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2354 case LFUN_FILE_INSERT_PLAINTEXT:
2355 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2356 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2359 case LFUN_SPELLING_CONTINUOUSLY:
2360 flag.setOnOff(lyxrc.spellcheck_continuously);
2363 case LFUN_CITATION_OPEN:
2372 flag.setEnabled(false);
2378 static FileName selectTemplateFile()
2380 FileDialog dlg(qt_("Select template file"));
2381 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2382 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2384 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2385 QStringList(qt_("LyX Documents (*.lyx)")));
2387 if (result.first == FileDialog::Later)
2389 if (result.second.isEmpty())
2391 return FileName(fromqstr(result.second));
2395 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2399 Buffer * newBuffer = nullptr;
2401 newBuffer = checkAndLoadLyXFile(filename);
2402 } catch (ExceptionMessage const &) {
2409 message(_("Document not loaded."));
2413 setBuffer(newBuffer);
2414 newBuffer->errors("Parse");
2417 theSession().lastFiles().add(filename);
2418 theSession().writeFile();
2425 void GuiView::openDocument(string const & fname)
2427 string initpath = lyxrc.document_path;
2429 if (documentBufferView()) {
2430 string const trypath = documentBufferView()->buffer().filePath();
2431 // If directory is writeable, use this as default.
2432 if (FileName(trypath).isDirWritable())
2438 if (fname.empty()) {
2439 FileDialog dlg(qt_("Select document to open"));
2440 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2441 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2443 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2444 FileDialog::Result result =
2445 dlg.open(toqstr(initpath), filter);
2447 if (result.first == FileDialog::Later)
2450 filename = fromqstr(result.second);
2452 // check selected filename
2453 if (filename.empty()) {
2454 message(_("Canceled."));
2460 // get absolute path of file and add ".lyx" to the filename if
2462 FileName const fullname =
2463 fileSearch(string(), filename, "lyx", support::may_not_exist);
2464 if (!fullname.empty())
2465 filename = fullname.absFileName();
2467 if (!fullname.onlyPath().isDirectory()) {
2468 Alert::warning(_("Invalid filename"),
2469 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2470 from_utf8(fullname.absFileName())));
2474 // if the file doesn't exist and isn't already open (bug 6645),
2475 // let the user create one
2476 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2477 !LyXVC::file_not_found_hook(fullname)) {
2478 // the user specifically chose this name. Believe him.
2479 Buffer * const b = newFile(filename, string(), true);
2485 docstring const disp_fn = makeDisplayPath(filename);
2486 message(bformat(_("Opening document %1$s..."), disp_fn));
2489 Buffer * buf = loadDocument(fullname);
2491 str2 = bformat(_("Document %1$s opened."), disp_fn);
2492 if (buf->lyxvc().inUse())
2493 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2494 " " + _("Version control detected.");
2496 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2501 // FIXME: clean that
2502 static bool import(GuiView * lv, FileName const & filename,
2503 string const & format, ErrorList & errorList)
2505 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2507 string loader_format;
2508 vector<string> loaders = theConverters().loaders();
2509 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2510 vector<string>::const_iterator it = loaders.begin();
2511 vector<string>::const_iterator en = loaders.end();
2512 for (; it != en; ++it) {
2513 if (!theConverters().isReachable(format, *it))
2516 string const tofile =
2517 support::changeExtension(filename.absFileName(),
2518 theFormats().extension(*it));
2519 if (theConverters().convert(nullptr, filename, FileName(tofile),
2520 filename, format, *it, errorList) != Converters::SUCCESS)
2522 loader_format = *it;
2525 if (loader_format.empty()) {
2526 frontend::Alert::error(_("Couldn't import file"),
2527 bformat(_("No information for importing the format %1$s."),
2528 translateIfPossible(theFormats().prettyName(format))));
2532 loader_format = format;
2534 if (loader_format == "lyx") {
2535 Buffer * buf = lv->loadDocument(lyxfile);
2539 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2543 bool as_paragraphs = loader_format == "textparagraph";
2544 string filename2 = (loader_format == format) ? filename.absFileName()
2545 : support::changeExtension(filename.absFileName(),
2546 theFormats().extension(loader_format));
2547 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2549 guiApp->setCurrentView(lv);
2550 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2557 void GuiView::importDocument(string const & argument)
2560 string filename = split(argument, format, ' ');
2562 LYXERR(Debug::INFO, format << " file: " << filename);
2564 // need user interaction
2565 if (filename.empty()) {
2566 string initpath = lyxrc.document_path;
2567 if (documentBufferView()) {
2568 string const trypath = documentBufferView()->buffer().filePath();
2569 // If directory is writeable, use this as default.
2570 if (FileName(trypath).isDirWritable())
2574 docstring const text = bformat(_("Select %1$s file to import"),
2575 translateIfPossible(theFormats().prettyName(format)));
2577 FileDialog dlg(toqstr(text));
2578 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2579 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2581 docstring filter = translateIfPossible(theFormats().prettyName(format));
2584 filter += from_utf8(theFormats().extensions(format));
2587 FileDialog::Result result =
2588 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2590 if (result.first == FileDialog::Later)
2593 filename = fromqstr(result.second);
2595 // check selected filename
2596 if (filename.empty())
2597 message(_("Canceled."));
2600 if (filename.empty())
2603 // get absolute path of file
2604 FileName const fullname(support::makeAbsPath(filename));
2606 // Can happen if the user entered a path into the dialog
2608 if (fullname.onlyFileName().empty()) {
2609 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2610 "Aborting import."),
2611 from_utf8(fullname.absFileName()));
2612 frontend::Alert::error(_("File name error"), msg);
2613 message(_("Canceled."));
2618 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2620 // Check if the document already is open
2621 Buffer * buf = theBufferList().getBuffer(lyxfile);
2624 if (!closeBuffer()) {
2625 message(_("Canceled."));
2630 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2632 // if the file exists already, and we didn't do
2633 // -i lyx thefile.lyx, warn
2634 if (lyxfile.exists() && fullname != lyxfile) {
2636 docstring text = bformat(_("The document %1$s already exists.\n\n"
2637 "Do you want to overwrite that document?"), displaypath);
2638 int const ret = Alert::prompt(_("Overwrite document?"),
2639 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2642 message(_("Canceled."));
2647 message(bformat(_("Importing %1$s..."), displaypath));
2648 ErrorList errorList;
2649 if (import(this, fullname, format, errorList))
2650 message(_("imported."));
2652 message(_("file not imported!"));
2654 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2658 void GuiView::newDocument(string const & filename, string templatefile,
2661 FileName initpath(lyxrc.document_path);
2662 if (documentBufferView()) {
2663 FileName const trypath(documentBufferView()->buffer().filePath());
2664 // If directory is writeable, use this as default.
2665 if (trypath.isDirWritable())
2669 if (from_template) {
2670 if (templatefile.empty())
2671 templatefile = selectTemplateFile().absFileName();
2672 if (templatefile.empty())
2677 if (filename.empty())
2678 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2680 b = newFile(filename, templatefile, true);
2685 // If no new document could be created, it is unsure
2686 // whether there is a valid BufferView.
2687 if (currentBufferView())
2688 // Ensure the cursor is correctly positioned on screen.
2689 currentBufferView()->showCursor();
2693 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2695 BufferView * bv = documentBufferView();
2700 FileName filename(to_utf8(fname));
2701 if (filename.empty()) {
2702 // Launch a file browser
2704 string initpath = lyxrc.document_path;
2705 string const trypath = bv->buffer().filePath();
2706 // If directory is writeable, use this as default.
2707 if (FileName(trypath).isDirWritable())
2711 FileDialog dlg(qt_("Select LyX document to insert"));
2712 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2713 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2715 FileDialog::Result result = dlg.open(toqstr(initpath),
2716 QStringList(qt_("LyX Documents (*.lyx)")));
2718 if (result.first == FileDialog::Later)
2722 filename.set(fromqstr(result.second));
2724 // check selected filename
2725 if (filename.empty()) {
2726 // emit message signal.
2727 message(_("Canceled."));
2732 bv->insertLyXFile(filename, ignorelang);
2733 bv->buffer().errors("Parse");
2737 string const GuiView::getTemplatesPath(Buffer & b)
2739 // We start off with the user's templates path
2740 string result = addPath(package().user_support().absFileName(), "templates");
2741 // Check for the document language
2742 string const langcode = b.params().language->code();
2743 string const shortcode = langcode.substr(0, 2);
2744 if (!langcode.empty() && shortcode != "en") {
2745 string subpath = addPath(result, shortcode);
2746 string subpath_long = addPath(result, langcode);
2747 // If we have a subdirectory for the language already,
2749 FileName sp = FileName(subpath);
2750 if (sp.isDirectory())
2752 else if (FileName(subpath_long).isDirectory())
2753 result = subpath_long;
2755 // Ask whether we should create such a subdirectory
2756 docstring const text =
2757 bformat(_("It is suggested to save the template in a subdirectory\n"
2758 "appropriate to the document language (%1$s).\n"
2759 "This subdirectory does not exists yet.\n"
2760 "Do you want to create it?"),
2761 _(b.params().language->display()));
2762 if (Alert::prompt(_("Create Language Directory?"),
2763 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2764 // If the user agreed, we try to create it and report if this failed.
2765 if (!sp.createDirectory(0777))
2766 Alert::error(_("Subdirectory creation failed!"),
2767 _("Could not create subdirectory.\n"
2768 "The template will be saved in the parent directory."));
2774 // Do we have a layout category?
2775 string const cat = b.params().baseClass() ?
2776 b.params().baseClass()->category()
2779 string subpath = addPath(result, cat);
2780 // If we have a subdirectory for the category already,
2782 FileName sp = FileName(subpath);
2783 if (sp.isDirectory())
2786 // Ask whether we should create such a subdirectory
2787 docstring const text =
2788 bformat(_("It is suggested to save the template in a subdirectory\n"
2789 "appropriate to the layout category (%1$s).\n"
2790 "This subdirectory does not exists yet.\n"
2791 "Do you want to create it?"),
2793 if (Alert::prompt(_("Create Category Directory?"),
2794 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2795 // If the user agreed, we try to create it and report if this failed.
2796 if (!sp.createDirectory(0777))
2797 Alert::error(_("Subdirectory creation failed!"),
2798 _("Could not create subdirectory.\n"
2799 "The template will be saved in the parent directory."));
2809 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2811 FileName fname = b.fileName();
2812 FileName const oldname = fname;
2813 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2815 if (!newname.empty()) {
2818 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2820 fname = support::makeAbsPath(to_utf8(newname),
2821 oldname.onlyPath().absFileName());
2823 // Switch to this Buffer.
2826 // No argument? Ask user through dialog.
2828 QString const title = as_template ? qt_("Choose a filename to save template as")
2829 : qt_("Choose a filename to save document as");
2830 FileDialog dlg(title);
2831 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2832 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2834 if (!isLyXFileName(fname.absFileName()))
2835 fname.changeExtension(".lyx");
2837 string const path = as_template ?
2839 : fname.onlyPath().absFileName();
2840 FileDialog::Result result =
2841 dlg.save(toqstr(path),
2842 QStringList(qt_("LyX Documents (*.lyx)")),
2843 toqstr(fname.onlyFileName()));
2845 if (result.first == FileDialog::Later)
2848 fname.set(fromqstr(result.second));
2853 if (!isLyXFileName(fname.absFileName()))
2854 fname.changeExtension(".lyx");
2857 // fname is now the new Buffer location.
2859 // if there is already a Buffer open with this name, we do not want
2860 // to have another one. (the second test makes sure we're not just
2861 // trying to overwrite ourselves, which is fine.)
2862 if (theBufferList().exists(fname) && fname != oldname
2863 && theBufferList().getBuffer(fname) != &b) {
2864 docstring const text =
2865 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2866 "Please close it before attempting to overwrite it.\n"
2867 "Do you want to choose a new filename?"),
2868 from_utf8(fname.absFileName()));
2869 int const ret = Alert::prompt(_("Chosen File Already Open"),
2870 text, 0, 1, _("&Rename"), _("&Cancel"));
2872 case 0: return renameBuffer(b, docstring(), kind);
2873 case 1: return false;
2878 bool const existsLocal = fname.exists();
2879 bool const existsInVC = LyXVC::fileInVC(fname);
2880 if (existsLocal || existsInVC) {
2881 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2882 if (kind != LV_WRITE_AS && existsInVC) {
2883 // renaming to a name that is already in VC
2885 docstring text = bformat(_("The document %1$s "
2886 "is already registered.\n\n"
2887 "Do you want to choose a new name?"),
2889 docstring const title = (kind == LV_VC_RENAME) ?
2890 _("Rename document?") : _("Copy document?");
2891 docstring const button = (kind == LV_VC_RENAME) ?
2892 _("&Rename") : _("&Copy");
2893 int const ret = Alert::prompt(title, text, 0, 1,
2894 button, _("&Cancel"));
2896 case 0: return renameBuffer(b, docstring(), kind);
2897 case 1: return false;
2902 docstring text = bformat(_("The document %1$s "
2903 "already exists.\n\n"
2904 "Do you want to overwrite that document?"),
2906 int const ret = Alert::prompt(_("Overwrite document?"),
2907 text, 0, 2, _("&Overwrite"),
2908 _("&Rename"), _("&Cancel"));
2911 case 1: return renameBuffer(b, docstring(), kind);
2912 case 2: return false;
2918 case LV_VC_RENAME: {
2919 string msg = b.lyxvc().rename(fname);
2922 message(from_utf8(msg));
2926 string msg = b.lyxvc().copy(fname);
2929 message(from_utf8(msg));
2933 case LV_WRITE_AS_TEMPLATE:
2936 // LyXVC created the file already in case of LV_VC_RENAME or
2937 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2938 // relative paths of included stuff right if we moved e.g. from
2939 // /a/b.lyx to /a/c/b.lyx.
2941 bool const saved = saveBuffer(b, fname);
2948 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2950 FileName fname = b.fileName();
2952 FileDialog dlg(qt_("Choose a filename to export the document as"));
2953 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2956 QString const anyformat = qt_("Guess from extension (*.*)");
2959 vector<Format const *> export_formats;
2960 for (Format const & f : theFormats())
2961 if (f.documentFormat())
2962 export_formats.push_back(&f);
2963 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2964 map<QString, string> fmap;
2967 for (Format const * f : export_formats) {
2968 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2969 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2971 from_ascii(f->extension())));
2972 types << loc_filter;
2973 fmap[loc_filter] = f->name();
2974 if (from_ascii(f->name()) == iformat) {
2975 filter = loc_filter;
2976 ext = f->extension();
2979 string ofname = fname.onlyFileName();
2981 ofname = support::changeExtension(ofname, ext);
2982 FileDialog::Result result =
2983 dlg.save(toqstr(fname.onlyPath().absFileName()),
2987 if (result.first != FileDialog::Chosen)
2991 fname.set(fromqstr(result.second));
2992 if (filter == anyformat)
2993 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2995 fmt_name = fmap[filter];
2996 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2997 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2999 if (fmt_name.empty() || fname.empty())
3002 // fname is now the new Buffer location.
3003 if (FileName(fname).exists()) {
3004 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3005 docstring text = bformat(_("The document %1$s already "
3006 "exists.\n\nDo you want to "
3007 "overwrite that document?"),
3009 int const ret = Alert::prompt(_("Overwrite document?"),
3010 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3013 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3014 case 2: return false;
3018 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3021 return dr.dispatched();
3025 bool GuiView::saveBuffer(Buffer & b)
3027 return saveBuffer(b, FileName());
3031 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3033 if (workArea(b) && workArea(b)->inDialogMode())
3036 if (fn.empty() && b.isUnnamed())
3037 return renameBuffer(b, docstring());
3039 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3041 theSession().lastFiles().add(b.fileName());
3042 theSession().writeFile();
3046 // Switch to this Buffer.
3049 // FIXME: we don't tell the user *WHY* the save failed !!
3050 docstring const file = makeDisplayPath(b.absFileName(), 30);
3051 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3052 "Do you want to rename the document and "
3053 "try again?"), file);
3054 int const ret = Alert::prompt(_("Rename and save?"),
3055 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3058 if (!renameBuffer(b, docstring()))
3067 return saveBuffer(b, fn);
3071 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3073 return closeWorkArea(wa, false);
3077 // We only want to close the buffer if it is not visible in other workareas
3078 // of the same view, nor in other views, and if this is not a child
3079 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3081 Buffer & buf = wa->bufferView().buffer();
3083 bool last_wa = d.countWorkAreasOf(buf) == 1
3084 && !inOtherView(buf) && !buf.parent();
3086 bool close_buffer = last_wa;
3089 if (lyxrc.close_buffer_with_last_view == "yes")
3091 else if (lyxrc.close_buffer_with_last_view == "no")
3092 close_buffer = false;
3095 if (buf.isUnnamed())
3096 file = from_utf8(buf.fileName().onlyFileName());
3098 file = buf.fileName().displayName(30);
3099 docstring const text = bformat(
3100 _("Last view on document %1$s is being closed.\n"
3101 "Would you like to close or hide the document?\n"
3103 "Hidden documents can be displayed back through\n"
3104 "the menu: View->Hidden->...\n"
3106 "To remove this question, set your preference in:\n"
3107 " Tools->Preferences->Look&Feel->UserInterface\n"
3109 int ret = Alert::prompt(_("Close or hide document?"),
3110 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3113 close_buffer = (ret == 0);
3117 return closeWorkArea(wa, close_buffer);
3121 bool GuiView::closeBuffer()
3123 GuiWorkArea * wa = currentMainWorkArea();
3124 // coverity complained about this
3125 // it seems unnecessary, but perhaps is worth the check
3126 LASSERT(wa, return false);
3128 setCurrentWorkArea(wa);
3129 Buffer & buf = wa->bufferView().buffer();
3130 return closeWorkArea(wa, !buf.parent());
3134 void GuiView::writeSession() const {
3135 GuiWorkArea const * active_wa = currentMainWorkArea();
3136 for (int i = 0; i < d.splitter_->count(); ++i) {
3137 TabWorkArea * twa = d.tabWorkArea(i);
3138 for (int j = 0; j < twa->count(); ++j) {
3139 GuiWorkArea * wa = twa->workArea(j);
3140 Buffer & buf = wa->bufferView().buffer();
3141 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3147 bool GuiView::closeBufferAll()
3150 for (auto & buf : theBufferList()) {
3151 if (!saveBufferIfNeeded(*buf, false)) {
3152 // Closing has been cancelled, so abort.
3157 // Close the workareas in all other views
3158 QList<int> const ids = guiApp->viewIds();
3159 for (int i = 0; i != ids.size(); ++i) {
3160 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3164 // Close our own workareas
3165 if (!closeWorkAreaAll())
3172 bool GuiView::closeWorkAreaAll()
3174 setCurrentWorkArea(currentMainWorkArea());
3176 // We might be in a situation that there is still a tabWorkArea, but
3177 // there are no tabs anymore. This can happen when we get here after a
3178 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3179 // many TabWorkArea's have no documents anymore.
3182 // We have to call count() each time, because it can happen that
3183 // more than one splitter will disappear in one iteration (bug 5998).
3184 while (d.splitter_->count() > empty_twa) {
3185 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3187 if (twa->count() == 0)
3190 setCurrentWorkArea(twa->currentWorkArea());
3191 if (!closeTabWorkArea(twa))
3199 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3204 Buffer & buf = wa->bufferView().buffer();
3206 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3207 Alert::warning(_("Close document"),
3208 _("Document could not be closed because it is being processed by LyX."));
3213 return closeBuffer(buf);
3215 if (!inMultiTabs(wa))
3216 if (!saveBufferIfNeeded(buf, true))
3224 bool GuiView::closeBuffer(Buffer & buf)
3226 bool success = true;
3227 ListOfBuffers clist = buf.getChildren();
3228 ListOfBuffers::const_iterator it = clist.begin();
3229 ListOfBuffers::const_iterator const bend = clist.end();
3230 for (; it != bend; ++it) {
3231 Buffer * child_buf = *it;
3232 if (theBufferList().isOthersChild(&buf, child_buf)) {
3233 child_buf->setParent(nullptr);
3237 // FIXME: should we look in other tabworkareas?
3238 // ANSWER: I don't think so. I've tested, and if the child is
3239 // open in some other window, it closes without a problem.
3240 GuiWorkArea * child_wa = workArea(*child_buf);
3243 // If we are in a close_event all children will be closed in some time,
3244 // so no need to do it here. This will ensure that the children end up
3245 // in the session file in the correct order. If we close the master
3246 // buffer, we can close or release the child buffers here too.
3248 success = closeWorkArea(child_wa, true);
3252 // In this case the child buffer is open but hidden.
3253 // Even in this case, children can be dirty (e.g.,
3254 // after a label change in the master, see #11405).
3255 // Therefore, check this
3256 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3257 // If we are in a close_event all children will be closed in some time,
3258 // so no need to do it here. This will ensure that the children end up
3259 // in the session file in the correct order. If we close the master
3260 // buffer, we can close or release the child buffers here too.
3263 // Save dirty buffers also if closing_!
3264 if (saveBufferIfNeeded(*child_buf, false)) {
3265 child_buf->removeAutosaveFile();
3266 theBufferList().release(child_buf);
3268 // Saving of dirty children has been cancelled.
3269 // Cancel the whole process.
3276 // goto bookmark to update bookmark pit.
3277 // FIXME: we should update only the bookmarks related to this buffer!
3278 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3279 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3280 guiApp->gotoBookmark(i + 1, false, false);
3282 if (saveBufferIfNeeded(buf, false)) {
3283 buf.removeAutosaveFile();
3284 theBufferList().release(&buf);
3288 // open all children again to avoid a crash because of dangling
3289 // pointers (bug 6603)
3295 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3297 while (twa == d.currentTabWorkArea()) {
3298 twa->setCurrentIndex(twa->count() - 1);
3300 GuiWorkArea * wa = twa->currentWorkArea();
3301 Buffer & b = wa->bufferView().buffer();
3303 // We only want to close the buffer if the same buffer is not visible
3304 // in another view, and if this is not a child and if we are closing
3305 // a view (not a tabgroup).
3306 bool const close_buffer =
3307 !inOtherView(b) && !b.parent() && closing_;
3309 if (!closeWorkArea(wa, close_buffer))
3316 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3318 if (buf.isClean() || buf.paragraphs().empty())
3321 // Switch to this Buffer.
3327 if (buf.isUnnamed()) {
3328 file = from_utf8(buf.fileName().onlyFileName());
3331 FileName filename = buf.fileName();
3333 file = filename.displayName(30);
3334 exists = filename.exists();
3337 // Bring this window to top before asking questions.
3342 if (hiding && buf.isUnnamed()) {
3343 docstring const text = bformat(_("The document %1$s has not been "
3344 "saved yet.\n\nDo you want to save "
3345 "the document?"), file);
3346 ret = Alert::prompt(_("Save new document?"),
3347 text, 0, 1, _("&Save"), _("&Cancel"));
3351 docstring const text = exists ?
3352 bformat(_("The document %1$s has unsaved changes."
3353 "\n\nDo you want to save the document or "
3354 "discard the changes?"), file) :
3355 bformat(_("The document %1$s has not been saved yet."
3356 "\n\nDo you want to save the document or "
3357 "discard it entirely?"), file);
3358 docstring const title = exists ?
3359 _("Save changed document?") : _("Save document?");
3360 ret = Alert::prompt(title, text, 0, 2,
3361 _("&Save"), _("&Discard"), _("&Cancel"));
3366 if (!saveBuffer(buf))
3370 // If we crash after this we could have no autosave file
3371 // but I guess this is really improbable (Jug).
3372 // Sometimes improbable things happen:
3373 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3374 // buf.removeAutosaveFile();
3376 // revert all changes
3387 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3389 Buffer & buf = wa->bufferView().buffer();
3391 for (int i = 0; i != d.splitter_->count(); ++i) {
3392 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3393 if (wa_ && wa_ != wa)
3396 return inOtherView(buf);
3400 bool GuiView::inOtherView(Buffer & buf)
3402 QList<int> const ids = guiApp->viewIds();
3404 for (int i = 0; i != ids.size(); ++i) {
3408 if (guiApp->view(ids[i]).workArea(buf))
3415 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3417 if (!documentBufferView())
3420 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3421 Buffer * const curbuf = &documentBufferView()->buffer();
3422 int nwa = twa->count();
3423 for (int i = 0; i < nwa; ++i) {
3424 if (&workArea(i)->bufferView().buffer() == curbuf) {
3426 if (np == NEXTBUFFER)
3427 next_index = (i == nwa - 1 ? 0 : i + 1);
3429 next_index = (i == 0 ? nwa - 1 : i - 1);
3431 twa->moveTab(i, next_index);
3433 setBuffer(&workArea(next_index)->bufferView().buffer());
3441 /// make sure the document is saved
3442 static bool ensureBufferClean(Buffer * buffer)
3444 LASSERT(buffer, return false);
3445 if (buffer->isClean() && !buffer->isUnnamed())
3448 docstring const file = buffer->fileName().displayName(30);
3451 if (!buffer->isUnnamed()) {
3452 text = bformat(_("The document %1$s has unsaved "
3453 "changes.\n\nDo you want to save "
3454 "the document?"), file);
3455 title = _("Save changed document?");
3458 text = bformat(_("The document %1$s has not been "
3459 "saved yet.\n\nDo you want to save "
3460 "the document?"), file);
3461 title = _("Save new document?");
3463 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3466 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3468 return buffer->isClean() && !buffer->isUnnamed();
3472 bool GuiView::reloadBuffer(Buffer & buf)
3474 currentBufferView()->cursor().reset();
3475 Buffer::ReadStatus status = buf.reload();
3476 return status == Buffer::ReadSuccess;
3480 void GuiView::checkExternallyModifiedBuffers()
3482 BufferList::iterator bit = theBufferList().begin();
3483 BufferList::iterator const bend = theBufferList().end();
3484 for (; bit != bend; ++bit) {
3485 Buffer * buf = *bit;
3486 if (buf->fileName().exists() && buf->isChecksumModified()) {
3487 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3488 " Reload now? Any local changes will be lost."),
3489 from_utf8(buf->absFileName()));
3490 int const ret = Alert::prompt(_("Reload externally changed document?"),
3491 text, 0, 1, _("&Reload"), _("&Cancel"));
3499 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3501 Buffer * buffer = documentBufferView()
3502 ? &(documentBufferView()->buffer()) : nullptr;
3504 switch (cmd.action()) {
3505 case LFUN_VC_REGISTER:
3506 if (!buffer || !ensureBufferClean(buffer))
3508 if (!buffer->lyxvc().inUse()) {
3509 if (buffer->lyxvc().registrer()) {
3510 reloadBuffer(*buffer);
3511 dr.clearMessageUpdate();
3516 case LFUN_VC_RENAME:
3517 case LFUN_VC_COPY: {
3518 if (!buffer || !ensureBufferClean(buffer))
3520 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3521 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3522 // Some changes are not yet committed.
3523 // We test here and not in getStatus(), since
3524 // this test is expensive.
3526 LyXVC::CommandResult ret =
3527 buffer->lyxvc().checkIn(log);
3529 if (ret == LyXVC::ErrorCommand ||
3530 ret == LyXVC::VCSuccess)
3531 reloadBuffer(*buffer);
3532 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3533 frontend::Alert::error(
3534 _("Revision control error."),
3535 _("Document could not be checked in."));
3539 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3540 LV_VC_RENAME : LV_VC_COPY;
3541 renameBuffer(*buffer, cmd.argument(), kind);
3546 case LFUN_VC_CHECK_IN:
3547 if (!buffer || !ensureBufferClean(buffer))
3549 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3551 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3553 // Only skip reloading if the checkin was cancelled or
3554 // an error occurred before the real checkin VCS command
3555 // was executed, since the VCS might have changed the
3556 // file even if it could not checkin successfully.
3557 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3558 reloadBuffer(*buffer);
3562 case LFUN_VC_CHECK_OUT:
3563 if (!buffer || !ensureBufferClean(buffer))
3565 if (buffer->lyxvc().inUse()) {
3566 dr.setMessage(buffer->lyxvc().checkOut());
3567 reloadBuffer(*buffer);
3571 case LFUN_VC_LOCKING_TOGGLE:
3572 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3574 if (buffer->lyxvc().inUse()) {
3575 string res = buffer->lyxvc().lockingToggle();
3577 frontend::Alert::error(_("Revision control error."),
3578 _("Error when setting the locking property."));
3581 reloadBuffer(*buffer);
3586 case LFUN_VC_REVERT:
3589 if (buffer->lyxvc().revert()) {
3590 reloadBuffer(*buffer);
3591 dr.clearMessageUpdate();
3595 case LFUN_VC_UNDO_LAST:
3598 buffer->lyxvc().undoLast();
3599 reloadBuffer(*buffer);
3600 dr.clearMessageUpdate();
3603 case LFUN_VC_REPO_UPDATE:
3606 if (ensureBufferClean(buffer)) {
3607 dr.setMessage(buffer->lyxvc().repoUpdate());
3608 checkExternallyModifiedBuffers();
3612 case LFUN_VC_COMMAND: {
3613 string flag = cmd.getArg(0);
3614 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3617 if (contains(flag, 'M')) {
3618 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3621 string path = cmd.getArg(1);
3622 if (contains(path, "$$p") && buffer)
3623 path = subst(path, "$$p", buffer->filePath());
3624 LYXERR(Debug::LYXVC, "Directory: " << path);
3626 if (!pp.isReadableDirectory()) {
3627 lyxerr << _("Directory is not accessible.") << endl;
3630 support::PathChanger p(pp);
3632 string command = cmd.getArg(2);
3633 if (command.empty())
3636 command = subst(command, "$$i", buffer->absFileName());
3637 command = subst(command, "$$p", buffer->filePath());
3639 command = subst(command, "$$m", to_utf8(message));
3640 LYXERR(Debug::LYXVC, "Command: " << command);
3642 one.startscript(Systemcall::Wait, command);
3646 if (contains(flag, 'I'))
3647 buffer->markDirty();
3648 if (contains(flag, 'R'))
3649 reloadBuffer(*buffer);
3654 case LFUN_VC_COMPARE: {
3655 if (cmd.argument().empty()) {
3656 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3662 string rev1 = cmd.getArg(0);
3666 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3669 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3670 f2 = buffer->absFileName();
3672 string rev2 = cmd.getArg(1);
3676 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3680 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3681 f1 << "\n" << f2 << "\n" );
3682 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3683 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3693 void GuiView::openChildDocument(string const & fname)
3695 LASSERT(documentBufferView(), return);
3696 Buffer & buffer = documentBufferView()->buffer();
3697 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3698 documentBufferView()->saveBookmark(false);
3699 Buffer * child = nullptr;
3700 if (theBufferList().exists(filename)) {
3701 child = theBufferList().getBuffer(filename);
3704 message(bformat(_("Opening child document %1$s..."),
3705 makeDisplayPath(filename.absFileName())));
3706 child = loadDocument(filename, false);
3708 // Set the parent name of the child document.
3709 // This makes insertion of citations and references in the child work,
3710 // when the target is in the parent or another child document.
3712 child->setParent(&buffer);
3716 bool GuiView::goToFileRow(string const & argument)
3720 size_t i = argument.find_last_of(' ');
3721 if (i != string::npos) {
3722 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3723 istringstream is(argument.substr(i + 1));
3728 if (i == string::npos) {
3729 LYXERR0("Wrong argument: " << argument);
3732 Buffer * buf = nullptr;
3733 string const realtmp = package().temp_dir().realPath();
3734 // We have to use os::path_prefix_is() here, instead of
3735 // simply prefixIs(), because the file name comes from
3736 // an external application and may need case adjustment.
3737 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3738 buf = theBufferList().getBufferFromTmp(file_name, true);
3739 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3740 << (buf ? " success" : " failed"));
3742 // Must replace extension of the file to be .lyx
3743 // and get full path
3744 FileName const s = fileSearch(string(),
3745 support::changeExtension(file_name, ".lyx"), "lyx");
3746 // Either change buffer or load the file
3747 if (theBufferList().exists(s))
3748 buf = theBufferList().getBuffer(s);
3749 else if (s.exists()) {
3750 buf = loadDocument(s);
3755 _("File does not exist: %1$s"),
3756 makeDisplayPath(file_name)));
3762 _("No buffer for file: %1$s."),
3763 makeDisplayPath(file_name))
3768 bool success = documentBufferView()->setCursorFromRow(row);
3770 LYXERR(Debug::LATEX,
3771 "setCursorFromRow: invalid position for row " << row);
3772 frontend::Alert::error(_("Inverse Search Failed"),
3773 _("Invalid position requested by inverse search.\n"
3774 "You may need to update the viewed document."));
3780 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3782 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3783 menu->exec(QCursor::pos());
3788 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3789 Buffer const * orig, Buffer * clone, string const & format)
3791 Buffer::ExportStatus const status = func(format);
3793 // the cloning operation will have produced a clone of the entire set of
3794 // documents, starting from the master. so we must delete those.
3795 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3797 busyBuffers.remove(orig);
3802 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3803 Buffer const * orig, Buffer * clone, string const & format)
3805 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3807 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3811 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3812 Buffer const * orig, Buffer * clone, string const & format)
3814 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3816 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3820 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3821 Buffer const * orig, Buffer * clone, string const & format)
3823 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3825 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3829 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3830 string const & argument,
3831 Buffer const * used_buffer,
3832 docstring const & msg,
3833 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3834 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3835 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3841 string format = argument;
3843 format = used_buffer->params().getDefaultOutputFormat();
3844 processing_format = format;
3846 progress_->clearMessages();
3849 #if EXPORT_in_THREAD
3851 GuiViewPrivate::busyBuffers.insert(used_buffer);
3852 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3853 if (!cloned_buffer) {
3854 Alert::error(_("Export Error"),
3855 _("Error cloning the Buffer."));
3858 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3863 setPreviewFuture(f);
3864 last_export_format = used_buffer->params().bufferFormat();
3867 // We are asynchronous, so we don't know here anything about the success
3870 Buffer::ExportStatus status;
3872 status = (used_buffer->*syncFunc)(format, false);
3873 } else if (previewFunc) {
3874 status = (used_buffer->*previewFunc)(format);
3877 handleExportStatus(gv_, status, format);
3879 return (status == Buffer::ExportSuccess
3880 || status == Buffer::PreviewSuccess);
3884 Buffer::ExportStatus status;
3886 status = (used_buffer->*syncFunc)(format, true);
3887 } else if (previewFunc) {
3888 status = (used_buffer->*previewFunc)(format);
3891 handleExportStatus(gv_, status, format);
3893 return (status == Buffer::ExportSuccess
3894 || status == Buffer::PreviewSuccess);
3898 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3900 BufferView * bv = currentBufferView();
3901 LASSERT(bv, return);
3903 // Let the current BufferView dispatch its own actions.
3904 bv->dispatch(cmd, dr);
3905 if (dr.dispatched()) {
3906 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3907 updateDialog("document", "");
3911 // Try with the document BufferView dispatch if any.
3912 BufferView * doc_bv = documentBufferView();
3913 if (doc_bv && doc_bv != bv) {
3914 doc_bv->dispatch(cmd, dr);
3915 if (dr.dispatched()) {
3916 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3917 updateDialog("document", "");
3922 // Then let the current Cursor dispatch its own actions.
3923 bv->cursor().dispatch(cmd);
3925 // update completion. We do it here and not in
3926 // processKeySym to avoid another redraw just for a
3927 // changed inline completion
3928 if (cmd.origin() == FuncRequest::KEYBOARD) {
3929 if (cmd.action() == LFUN_SELF_INSERT
3930 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3931 updateCompletion(bv->cursor(), true, true);
3932 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3933 updateCompletion(bv->cursor(), false, true);
3935 updateCompletion(bv->cursor(), false, false);
3938 dr = bv->cursor().result();
3942 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3944 BufferView * bv = currentBufferView();
3945 // By default we won't need any update.
3946 dr.screenUpdate(Update::None);
3947 // assume cmd will be dispatched
3948 dr.dispatched(true);
3950 Buffer * doc_buffer = documentBufferView()
3951 ? &(documentBufferView()->buffer()) : nullptr;
3953 if (cmd.origin() == FuncRequest::TOC) {
3954 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3955 toc->doDispatch(bv->cursor(), cmd, dr);
3959 string const argument = to_utf8(cmd.argument());
3961 switch(cmd.action()) {
3962 case LFUN_BUFFER_CHILD_OPEN:
3963 openChildDocument(to_utf8(cmd.argument()));
3966 case LFUN_BUFFER_IMPORT:
3967 importDocument(to_utf8(cmd.argument()));
3970 case LFUN_MASTER_BUFFER_EXPORT:
3972 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3974 case LFUN_BUFFER_EXPORT: {
3977 // GCC only sees strfwd.h when building merged
3978 if (::lyx::operator==(cmd.argument(), "custom")) {
3979 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3980 // so the following test should not be needed.
3981 // In principle, we could try to switch to such a view...
3982 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3983 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3987 string const dest = cmd.getArg(1);
3988 FileName target_dir;
3989 if (!dest.empty() && FileName::isAbsolute(dest))
3990 target_dir = FileName(support::onlyPath(dest));
3992 target_dir = doc_buffer->fileName().onlyPath();
3994 string const format = (argument.empty() || argument == "default") ?
3995 doc_buffer->params().getDefaultOutputFormat() : argument;
3997 if ((dest.empty() && doc_buffer->isUnnamed())
3998 || !target_dir.isDirWritable()) {
3999 exportBufferAs(*doc_buffer, from_utf8(format));
4002 /* TODO/Review: Is it a problem to also export the children?
4003 See the update_unincluded flag */
4004 d.asyncBufferProcessing(format,
4007 &GuiViewPrivate::exportAndDestroy,
4009 nullptr, cmd.allowAsync());
4010 // TODO Inform user about success
4014 case LFUN_BUFFER_EXPORT_AS: {
4015 LASSERT(doc_buffer, break);
4016 docstring f = cmd.argument();
4018 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4019 exportBufferAs(*doc_buffer, f);
4023 case LFUN_BUFFER_UPDATE: {
4024 d.asyncBufferProcessing(argument,
4027 &GuiViewPrivate::compileAndDestroy,
4029 nullptr, cmd.allowAsync());
4032 case LFUN_BUFFER_VIEW: {
4033 d.asyncBufferProcessing(argument,
4035 _("Previewing ..."),
4036 &GuiViewPrivate::previewAndDestroy,
4038 &Buffer::preview, cmd.allowAsync());
4041 case LFUN_MASTER_BUFFER_UPDATE: {
4042 d.asyncBufferProcessing(argument,
4043 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4045 &GuiViewPrivate::compileAndDestroy,
4047 nullptr, cmd.allowAsync());
4050 case LFUN_MASTER_BUFFER_VIEW: {
4051 d.asyncBufferProcessing(argument,
4052 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4054 &GuiViewPrivate::previewAndDestroy,
4055 nullptr, &Buffer::preview, cmd.allowAsync());
4058 case LFUN_EXPORT_CANCEL: {
4059 Systemcall::killscript();
4062 case LFUN_BUFFER_SWITCH: {
4063 string const file_name = to_utf8(cmd.argument());
4064 if (!FileName::isAbsolute(file_name)) {
4066 dr.setMessage(_("Absolute filename expected."));
4070 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4073 dr.setMessage(_("Document not loaded"));
4077 // Do we open or switch to the buffer in this view ?
4078 if (workArea(*buffer)
4079 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4084 // Look for the buffer in other views
4085 QList<int> const ids = guiApp->viewIds();
4087 for (; i != ids.size(); ++i) {
4088 GuiView & gv = guiApp->view(ids[i]);
4089 if (gv.workArea(*buffer)) {
4091 gv.activateWindow();
4093 gv.setBuffer(buffer);
4098 // If necessary, open a new window as a last resort
4099 if (i == ids.size()) {
4100 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4106 case LFUN_BUFFER_NEXT:
4107 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4110 case LFUN_BUFFER_MOVE_NEXT:
4111 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4114 case LFUN_BUFFER_PREVIOUS:
4115 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4118 case LFUN_BUFFER_MOVE_PREVIOUS:
4119 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4122 case LFUN_BUFFER_CHKTEX:
4123 LASSERT(doc_buffer, break);
4124 doc_buffer->runChktex();
4127 case LFUN_COMMAND_EXECUTE: {
4128 command_execute_ = true;
4129 minibuffer_focus_ = true;
4132 case LFUN_DROP_LAYOUTS_CHOICE:
4133 d.layout_->showPopup();
4136 case LFUN_MENU_OPEN:
4137 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4138 menu->exec(QCursor::pos());
4141 case LFUN_FILE_INSERT: {
4142 if (cmd.getArg(1) == "ignorelang")
4143 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4145 insertLyXFile(cmd.argument());
4149 case LFUN_FILE_INSERT_PLAINTEXT:
4150 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4151 string const fname = to_utf8(cmd.argument());
4152 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4153 dr.setMessage(_("Absolute filename expected."));
4157 FileName filename(fname);
4158 if (fname.empty()) {
4159 FileDialog dlg(qt_("Select file to insert"));
4161 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4162 QStringList(qt_("All Files (*)")));
4164 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4165 dr.setMessage(_("Canceled."));
4169 filename.set(fromqstr(result.second));
4173 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4174 bv->dispatch(new_cmd, dr);
4179 case LFUN_BUFFER_RELOAD: {
4180 LASSERT(doc_buffer, break);
4183 bool drop = (cmd.argument() == "dump");
4186 if (!drop && !doc_buffer->isClean()) {
4187 docstring const file =
4188 makeDisplayPath(doc_buffer->absFileName(), 20);
4189 if (doc_buffer->notifiesExternalModification()) {
4190 docstring text = _("The current version will be lost. "
4191 "Are you sure you want to load the version on disk "
4192 "of the document %1$s?");
4193 ret = Alert::prompt(_("Reload saved document?"),
4194 bformat(text, file), 1, 1,
4195 _("&Reload"), _("&Cancel"));
4197 docstring text = _("Any changes will be lost. "
4198 "Are you sure you want to revert to the saved version "
4199 "of the document %1$s?");
4200 ret = Alert::prompt(_("Revert to saved document?"),
4201 bformat(text, file), 1, 1,
4202 _("&Revert"), _("&Cancel"));
4207 doc_buffer->markClean();
4208 reloadBuffer(*doc_buffer);
4209 dr.forceBufferUpdate();
4214 case LFUN_BUFFER_RESET_EXPORT:
4215 LASSERT(doc_buffer, break);
4216 doc_buffer->requireFreshStart(true);
4217 dr.setMessage(_("Buffer export reset."));
4220 case LFUN_BUFFER_WRITE:
4221 LASSERT(doc_buffer, break);
4222 saveBuffer(*doc_buffer);
4225 case LFUN_BUFFER_WRITE_AS:
4226 LASSERT(doc_buffer, break);
4227 renameBuffer(*doc_buffer, cmd.argument());
4230 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4231 LASSERT(doc_buffer, break);
4232 renameBuffer(*doc_buffer, cmd.argument(),
4233 LV_WRITE_AS_TEMPLATE);
4236 case LFUN_BUFFER_WRITE_ALL: {
4237 Buffer * first = theBufferList().first();
4240 message(_("Saving all documents..."));
4241 // We cannot use a for loop as the buffer list cycles.
4244 if (!b->isClean()) {
4246 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4248 b = theBufferList().next(b);
4249 } while (b != first);
4250 dr.setMessage(_("All documents saved."));
4254 case LFUN_MASTER_BUFFER_FORALL: {
4258 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4259 funcToRun.allowAsync(false);
4261 for (Buffer const * buf : doc_buffer->allRelatives()) {
4262 // Switch to other buffer view and resend cmd
4263 lyx::dispatch(FuncRequest(
4264 LFUN_BUFFER_SWITCH, buf->absFileName()));
4265 lyx::dispatch(funcToRun);
4268 lyx::dispatch(FuncRequest(
4269 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4273 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4274 LASSERT(doc_buffer, break);
4275 doc_buffer->clearExternalModification();
4278 case LFUN_BUFFER_CLOSE:
4282 case LFUN_BUFFER_CLOSE_ALL:
4286 case LFUN_DEVEL_MODE_TOGGLE:
4287 devel_mode_ = !devel_mode_;
4289 dr.setMessage(_("Developer mode is now enabled."));
4291 dr.setMessage(_("Developer mode is now disabled."));
4294 case LFUN_TOOLBAR_TOGGLE: {
4295 string const name = cmd.getArg(0);
4296 if (GuiToolbar * t = toolbar(name))
4301 case LFUN_TOOLBAR_MOVABLE: {
4302 string const name = cmd.getArg(0);
4304 // toggle (all) toolbars movablility
4305 toolbarsMovable_ = !toolbarsMovable_;
4306 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4307 GuiToolbar * tb = toolbar(ti.name);
4308 if (tb && tb->isMovable() != toolbarsMovable_)
4309 // toggle toolbar movablity if it does not fit lock
4310 // (all) toolbars positions state silent = true, since
4311 // status bar notifications are slow
4314 if (toolbarsMovable_)
4315 dr.setMessage(_("Toolbars unlocked."));
4317 dr.setMessage(_("Toolbars locked."));
4318 } else if (GuiToolbar * t = toolbar(name)) {
4319 // toggle current toolbar movablity
4321 // update lock (all) toolbars positions
4322 updateLockToolbars();
4327 case LFUN_ICON_SIZE: {
4328 QSize size = d.iconSize(cmd.argument());
4330 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4331 size.width(), size.height()));
4335 case LFUN_DIALOG_UPDATE: {
4336 string const name = to_utf8(cmd.argument());
4337 if (name == "prefs" || name == "document")
4338 updateDialog(name, string());
4339 else if (name == "paragraph")
4340 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4341 else if (currentBufferView()) {
4342 Inset * inset = currentBufferView()->editedInset(name);
4343 // Can only update a dialog connected to an existing inset
4345 // FIXME: get rid of this indirection; GuiView ask the inset
4346 // if he is kind enough to update itself...
4347 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4348 //FIXME: pass DispatchResult here?
4349 inset->dispatch(currentBufferView()->cursor(), fr);
4355 case LFUN_DIALOG_TOGGLE: {
4356 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4357 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4358 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4362 case LFUN_DIALOG_DISCONNECT_INSET:
4363 disconnectDialog(to_utf8(cmd.argument()));
4366 case LFUN_DIALOG_HIDE: {
4367 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4371 case LFUN_DIALOG_SHOW: {
4372 string const name = cmd.getArg(0);
4373 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4375 if (name == "latexlog") {
4376 // getStatus checks that
4377 LASSERT(doc_buffer, break);
4378 Buffer::LogType type;
4379 string const logfile = doc_buffer->logName(&type);
4381 case Buffer::latexlog:
4384 case Buffer::buildlog:
4385 sdata = "literate ";
4388 sdata += Lexer::quoteString(logfile);
4389 showDialog("log", sdata);
4390 } else if (name == "vclog") {
4391 // getStatus checks that
4392 LASSERT(doc_buffer, break);
4393 string const sdata2 = "vc " +
4394 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4395 showDialog("log", sdata2);
4396 } else if (name == "symbols") {
4397 sdata = bv->cursor().getEncoding()->name();
4399 showDialog("symbols", sdata);
4401 } else if (name == "prefs" && isFullScreen()) {
4402 lfunUiToggle("fullscreen");
4403 showDialog("prefs", sdata);
4405 showDialog(name, sdata);
4410 dr.setMessage(cmd.argument());
4413 case LFUN_UI_TOGGLE: {
4414 string arg = cmd.getArg(0);
4415 if (!lfunUiToggle(arg)) {
4416 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4417 dr.setMessage(bformat(msg, from_utf8(arg)));
4419 // Make sure the keyboard focus stays in the work area.
4424 case LFUN_VIEW_SPLIT: {
4425 LASSERT(doc_buffer, break);
4426 string const orientation = cmd.getArg(0);
4427 d.splitter_->setOrientation(orientation == "vertical"
4428 ? Qt::Vertical : Qt::Horizontal);
4429 TabWorkArea * twa = addTabWorkArea();
4430 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4431 setCurrentWorkArea(wa);
4434 case LFUN_TAB_GROUP_CLOSE:
4435 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4436 closeTabWorkArea(twa);
4437 d.current_work_area_ = nullptr;
4438 twa = d.currentTabWorkArea();
4439 // Switch to the next GuiWorkArea in the found TabWorkArea.
4441 // Make sure the work area is up to date.
4442 setCurrentWorkArea(twa->currentWorkArea());
4444 setCurrentWorkArea(nullptr);
4449 case LFUN_VIEW_CLOSE:
4450 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4451 closeWorkArea(twa->currentWorkArea());
4452 d.current_work_area_ = nullptr;
4453 twa = d.currentTabWorkArea();
4454 // Switch to the next GuiWorkArea in the found TabWorkArea.
4456 // Make sure the work area is up to date.
4457 setCurrentWorkArea(twa->currentWorkArea());
4459 setCurrentWorkArea(nullptr);
4464 case LFUN_COMPLETION_INLINE:
4465 if (d.current_work_area_)
4466 d.current_work_area_->completer().showInline();
4469 case LFUN_COMPLETION_POPUP:
4470 if (d.current_work_area_)
4471 d.current_work_area_->completer().showPopup();
4476 if (d.current_work_area_)
4477 d.current_work_area_->completer().tab();
4480 case LFUN_COMPLETION_CANCEL:
4481 if (d.current_work_area_) {
4482 if (d.current_work_area_->completer().popupVisible())
4483 d.current_work_area_->completer().hidePopup();
4485 d.current_work_area_->completer().hideInline();
4489 case LFUN_COMPLETION_ACCEPT:
4490 if (d.current_work_area_)
4491 d.current_work_area_->completer().activate();
4494 case LFUN_BUFFER_ZOOM_IN:
4495 case LFUN_BUFFER_ZOOM_OUT:
4496 case LFUN_BUFFER_ZOOM: {
4497 if (cmd.argument().empty()) {
4498 if (cmd.action() == LFUN_BUFFER_ZOOM)
4500 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4505 if (cmd.action() == LFUN_BUFFER_ZOOM)
4506 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4507 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4508 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4510 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4513 // Actual zoom value: default zoom + fractional extra value
4514 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4515 if (zoom < static_cast<int>(zoom_min_))
4518 lyxrc.currentZoom = zoom;
4520 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4521 lyxrc.currentZoom, lyxrc.defaultZoom));
4523 guiApp->fontLoader().update();
4524 dr.screenUpdate(Update::Force | Update::FitCursor);
4528 case LFUN_VC_REGISTER:
4529 case LFUN_VC_RENAME:
4531 case LFUN_VC_CHECK_IN:
4532 case LFUN_VC_CHECK_OUT:
4533 case LFUN_VC_REPO_UPDATE:
4534 case LFUN_VC_LOCKING_TOGGLE:
4535 case LFUN_VC_REVERT:
4536 case LFUN_VC_UNDO_LAST:
4537 case LFUN_VC_COMMAND:
4538 case LFUN_VC_COMPARE:
4539 dispatchVC(cmd, dr);
4542 case LFUN_SERVER_GOTO_FILE_ROW:
4543 if(goToFileRow(to_utf8(cmd.argument())))
4544 dr.screenUpdate(Update::Force | Update::FitCursor);
4547 case LFUN_LYX_ACTIVATE:
4551 case LFUN_WINDOW_RAISE:
4557 case LFUN_FORWARD_SEARCH: {
4558 // it seems safe to assume we have a document buffer, since
4559 // getStatus wants one.
4560 LASSERT(doc_buffer, break);
4561 Buffer const * doc_master = doc_buffer->masterBuffer();
4562 FileName const path(doc_master->temppath());
4563 string const texname = doc_master->isChild(doc_buffer)
4564 ? DocFileName(changeExtension(
4565 doc_buffer->absFileName(),
4566 "tex")).mangledFileName()
4567 : doc_buffer->latexName();
4568 string const fulltexname =
4569 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4570 string const mastername =
4571 removeExtension(doc_master->latexName());
4572 FileName const dviname(addName(path.absFileName(),
4573 addExtension(mastername, "dvi")));
4574 FileName const pdfname(addName(path.absFileName(),
4575 addExtension(mastername, "pdf")));
4576 bool const have_dvi = dviname.exists();
4577 bool const have_pdf = pdfname.exists();
4578 if (!have_dvi && !have_pdf) {
4579 dr.setMessage(_("Please, preview the document first."));
4582 string outname = dviname.onlyFileName();
4583 string command = lyxrc.forward_search_dvi;
4584 if (!have_dvi || (have_pdf &&
4585 pdfname.lastModified() > dviname.lastModified())) {
4586 outname = pdfname.onlyFileName();
4587 command = lyxrc.forward_search_pdf;
4590 DocIterator cur = bv->cursor();
4591 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4592 LYXERR(Debug::ACTION, "Forward search: row:" << row
4594 if (row == -1 || command.empty()) {
4595 dr.setMessage(_("Couldn't proceed."));
4598 string texrow = convert<string>(row);
4600 command = subst(command, "$$n", texrow);
4601 command = subst(command, "$$f", fulltexname);
4602 command = subst(command, "$$t", texname);
4603 command = subst(command, "$$o", outname);
4605 volatile PathChanger p(path);
4607 one.startscript(Systemcall::DontWait, command);
4611 case LFUN_SPELLING_CONTINUOUSLY:
4612 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4613 dr.screenUpdate(Update::Force);
4616 case LFUN_CITATION_OPEN: {
4618 if (theFormats().getFormat("pdf"))
4619 pdfv = theFormats().getFormat("pdf")->viewer();
4620 if (theFormats().getFormat("ps"))
4621 psv = theFormats().getFormat("ps")->viewer();
4622 frontend::showTarget(argument, pdfv, psv);
4627 // The LFUN must be for one of BufferView, Buffer or Cursor;
4629 dispatchToBufferView(cmd, dr);
4633 // Part of automatic menu appearance feature.
4634 if (isFullScreen()) {
4635 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4639 // Need to update bv because many LFUNs here might have destroyed it
4640 bv = currentBufferView();
4642 // Clear non-empty selections
4643 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4645 Cursor & cur = bv->cursor();
4646 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4647 cur.clearSelection();
4653 bool GuiView::lfunUiToggle(string const & ui_component)
4655 if (ui_component == "scrollbar") {
4656 // hide() is of no help
4657 if (d.current_work_area_->verticalScrollBarPolicy() ==
4658 Qt::ScrollBarAlwaysOff)
4660 d.current_work_area_->setVerticalScrollBarPolicy(
4661 Qt::ScrollBarAsNeeded);
4663 d.current_work_area_->setVerticalScrollBarPolicy(
4664 Qt::ScrollBarAlwaysOff);
4665 } else if (ui_component == "statusbar") {
4666 statusBar()->setVisible(!statusBar()->isVisible());
4667 } else if (ui_component == "menubar") {
4668 menuBar()->setVisible(!menuBar()->isVisible());
4670 if (ui_component == "frame") {
4671 int const l = contentsMargins().left();
4673 //are the frames in default state?
4674 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4676 setContentsMargins(-2, -2, -2, -2);
4678 setContentsMargins(0, 0, 0, 0);
4681 if (ui_component == "fullscreen") {
4689 void GuiView::toggleFullScreen()
4691 setWindowState(windowState() ^ Qt::WindowFullScreen);
4695 Buffer const * GuiView::updateInset(Inset const * inset)
4700 Buffer const * inset_buffer = &(inset->buffer());
4702 for (int i = 0; i != d.splitter_->count(); ++i) {
4703 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4706 Buffer const * buffer = &(wa->bufferView().buffer());
4707 if (inset_buffer == buffer)
4708 wa->scheduleRedraw(true);
4710 return inset_buffer;
4714 void GuiView::restartCaret()
4716 /* When we move around, or type, it's nice to be able to see
4717 * the caret immediately after the keypress.
4719 if (d.current_work_area_)
4720 d.current_work_area_->startBlinkingCaret();
4722 // Take this occasion to update the other GUI elements.
4728 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4730 if (d.current_work_area_)
4731 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4736 // This list should be kept in sync with the list of insets in
4737 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4738 // dialog should have the same name as the inset.
4739 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4740 // docs in LyXAction.cpp.
4742 char const * const dialognames[] = {
4744 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4745 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4746 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4747 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4748 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4749 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4750 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4751 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4753 char const * const * const end_dialognames =
4754 dialognames + (sizeof(dialognames) / sizeof(char *));
4758 cmpCStr(char const * name) : name_(name) {}
4759 bool operator()(char const * other) {
4760 return strcmp(other, name_) == 0;
4767 bool isValidName(string const & name)
4769 return find_if(dialognames, end_dialognames,
4770 cmpCStr(name.c_str())) != end_dialognames;
4776 void GuiView::resetDialogs()
4778 // Make sure that no LFUN uses any GuiView.
4779 guiApp->setCurrentView(nullptr);
4783 constructToolbars();
4784 guiApp->menus().fillMenuBar(menuBar(), this, false);
4785 d.layout_->updateContents(true);
4786 // Now update controls with current buffer.
4787 guiApp->setCurrentView(this);
4793 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4795 for (QObject * child: widget->children()) {
4796 if (child->inherits("QGroupBox")) {
4797 QGroupBox * box = (QGroupBox*) child;
4800 flatGroupBoxes(child, flag);
4806 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4808 if (!isValidName(name))
4811 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4813 if (it != d.dialogs_.end()) {
4815 it->second->hideView();
4816 return it->second.get();
4819 Dialog * dialog = build(name);
4820 d.dialogs_[name].reset(dialog);
4821 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4822 // Force a uniform style for group boxes
4823 // On Mac non-flat works better, on Linux flat is standard
4824 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4826 if (lyxrc.allow_geometry_session)
4827 dialog->restoreSession();
4834 void GuiView::showDialog(string const & name, string const & sdata,
4837 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4841 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4847 const string name = fromqstr(qname);
4848 const string sdata = fromqstr(qdata);
4852 Dialog * dialog = findOrBuild(name, false);
4854 bool const visible = dialog->isVisibleView();
4855 dialog->showData(sdata);
4856 if (currentBufferView())
4857 currentBufferView()->editInset(name, inset);
4858 // We only set the focus to the new dialog if it was not yet
4859 // visible in order not to change the existing previous behaviour
4861 // activateWindow is needed for floating dockviews
4862 dialog->asQWidget()->raise();
4863 dialog->asQWidget()->activateWindow();
4864 dialog->asQWidget()->setFocus();
4868 catch (ExceptionMessage const &) {
4876 bool GuiView::isDialogVisible(string const & name) const
4878 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4879 if (it == d.dialogs_.end())
4881 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4885 void GuiView::hideDialog(string const & name, Inset * inset)
4887 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4888 if (it == d.dialogs_.end())
4892 if (!currentBufferView())
4894 if (inset != currentBufferView()->editedInset(name))
4898 Dialog * const dialog = it->second.get();
4899 if (dialog->isVisibleView())
4901 if (currentBufferView())
4902 currentBufferView()->editInset(name, nullptr);
4906 void GuiView::disconnectDialog(string const & name)
4908 if (!isValidName(name))
4910 if (currentBufferView())
4911 currentBufferView()->editInset(name, nullptr);
4915 void GuiView::hideAll() const
4917 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4918 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4920 for(; it != end; ++it)
4921 it->second->hideView();
4925 void GuiView::updateDialogs()
4927 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4928 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4930 for(; it != end; ++it) {
4931 Dialog * dialog = it->second.get();
4933 if (dialog->needBufferOpen() && !documentBufferView())
4934 hideDialog(fromqstr(dialog->name()), nullptr);
4935 else if (dialog->isVisibleView())
4936 dialog->checkStatus();
4943 Dialog * createDialog(GuiView & lv, string const & name);
4945 // will be replaced by a proper factory...
4946 Dialog * createGuiAbout(GuiView & lv);
4947 Dialog * createGuiBibtex(GuiView & lv);
4948 Dialog * createGuiChanges(GuiView & lv);
4949 Dialog * createGuiCharacter(GuiView & lv);
4950 Dialog * createGuiCitation(GuiView & lv);
4951 Dialog * createGuiCompare(GuiView & lv);
4952 Dialog * createGuiCompareHistory(GuiView & lv);
4953 Dialog * createGuiDelimiter(GuiView & lv);
4954 Dialog * createGuiDocument(GuiView & lv);
4955 Dialog * createGuiErrorList(GuiView & lv);
4956 Dialog * createGuiExternal(GuiView & lv);
4957 Dialog * createGuiGraphics(GuiView & lv);
4958 Dialog * createGuiInclude(GuiView & lv);
4959 Dialog * createGuiIndex(GuiView & lv);
4960 Dialog * createGuiListings(GuiView & lv);
4961 Dialog * createGuiLog(GuiView & lv);
4962 Dialog * createGuiLyXFiles(GuiView & lv);
4963 Dialog * createGuiMathMatrix(GuiView & lv);
4964 Dialog * createGuiNote(GuiView & lv);
4965 Dialog * createGuiParagraph(GuiView & lv);
4966 Dialog * createGuiPhantom(GuiView & lv);
4967 Dialog * createGuiPreferences(GuiView & lv);
4968 Dialog * createGuiPrint(GuiView & lv);
4969 Dialog * createGuiPrintindex(GuiView & lv);
4970 Dialog * createGuiRef(GuiView & lv);
4971 Dialog * createGuiSearch(GuiView & lv);
4972 Dialog * createGuiSearchAdv(GuiView & lv);
4973 Dialog * createGuiSendTo(GuiView & lv);
4974 Dialog * createGuiShowFile(GuiView & lv);
4975 Dialog * createGuiSpellchecker(GuiView & lv);
4976 Dialog * createGuiSymbols(GuiView & lv);
4977 Dialog * createGuiTabularCreate(GuiView & lv);
4978 Dialog * createGuiTexInfo(GuiView & lv);
4979 Dialog * createGuiToc(GuiView & lv);
4980 Dialog * createGuiThesaurus(GuiView & lv);
4981 Dialog * createGuiViewSource(GuiView & lv);
4982 Dialog * createGuiWrap(GuiView & lv);
4983 Dialog * createGuiProgressView(GuiView & lv);
4987 Dialog * GuiView::build(string const & name)
4989 LASSERT(isValidName(name), return nullptr);
4991 Dialog * dialog = createDialog(*this, name);
4995 if (name == "aboutlyx")
4996 return createGuiAbout(*this);
4997 if (name == "bibtex")
4998 return createGuiBibtex(*this);
4999 if (name == "changes")
5000 return createGuiChanges(*this);
5001 if (name == "character")
5002 return createGuiCharacter(*this);
5003 if (name == "citation")
5004 return createGuiCitation(*this);
5005 if (name == "compare")
5006 return createGuiCompare(*this);
5007 if (name == "comparehistory")
5008 return createGuiCompareHistory(*this);
5009 if (name == "document")
5010 return createGuiDocument(*this);
5011 if (name == "errorlist")
5012 return createGuiErrorList(*this);
5013 if (name == "external")
5014 return createGuiExternal(*this);
5016 return createGuiShowFile(*this);
5017 if (name == "findreplace")
5018 return createGuiSearch(*this);
5019 if (name == "findreplaceadv")
5020 return createGuiSearchAdv(*this);
5021 if (name == "graphics")
5022 return createGuiGraphics(*this);
5023 if (name == "include")
5024 return createGuiInclude(*this);
5025 if (name == "index")
5026 return createGuiIndex(*this);
5027 if (name == "index_print")
5028 return createGuiPrintindex(*this);
5029 if (name == "listings")
5030 return createGuiListings(*this);
5032 return createGuiLog(*this);
5033 if (name == "lyxfiles")
5034 return createGuiLyXFiles(*this);
5035 if (name == "mathdelimiter")
5036 return createGuiDelimiter(*this);
5037 if (name == "mathmatrix")
5038 return createGuiMathMatrix(*this);
5040 return createGuiNote(*this);
5041 if (name == "paragraph")
5042 return createGuiParagraph(*this);
5043 if (name == "phantom")
5044 return createGuiPhantom(*this);
5045 if (name == "prefs")
5046 return createGuiPreferences(*this);
5048 return createGuiRef(*this);
5049 if (name == "sendto")
5050 return createGuiSendTo(*this);
5051 if (name == "spellchecker")
5052 return createGuiSpellchecker(*this);
5053 if (name == "symbols")
5054 return createGuiSymbols(*this);
5055 if (name == "tabularcreate")
5056 return createGuiTabularCreate(*this);
5057 if (name == "texinfo")
5058 return createGuiTexInfo(*this);
5059 if (name == "thesaurus")
5060 return createGuiThesaurus(*this);
5062 return createGuiToc(*this);
5063 if (name == "view-source")
5064 return createGuiViewSource(*this);
5066 return createGuiWrap(*this);
5067 if (name == "progress")
5068 return createGuiProgressView(*this);
5074 SEMenu::SEMenu(QWidget * parent)
5076 QAction * action = addAction(qt_("Disable Shell Escape"));
5077 connect(action, SIGNAL(triggered()),
5078 parent, SLOT(disableShellEscape()));
5081 } // namespace frontend
5084 #include "moc_GuiView.cpp"