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 "GuiFontMetrics.h"
25 #include "GuiKeySymbol.h"
27 #include "GuiToolbar.h"
28 #include "GuiWorkArea.h"
29 #include "GuiProgress.h"
30 #include "LayoutBox.h"
34 #include "qt_helpers.h"
35 #include "support/filetools.h"
37 #include "frontends/alert.h"
38 #include "frontends/KeySymbol.h"
40 #include "buffer_funcs.h"
42 #include "BufferList.h"
43 #include "BufferParams.h"
44 #include "BufferView.h"
46 #include "Converter.h"
48 #include "CutAndPaste.h"
50 #include "ErrorList.h"
52 #include "FuncStatus.h"
53 #include "FuncRequest.h"
54 #include "KeySymbol.h"
56 #include "LayoutFile.h"
58 #include "LyXAction.h"
62 #include "Paragraph.h"
63 #include "SpellChecker.h"
70 #include "support/convert.h"
71 #include "support/debug.h"
72 #include "support/ExceptionMessage.h"
73 #include "support/FileName.h"
74 #include "support/gettext.h"
75 #include "support/ForkedCalls.h"
76 #include "support/lassert.h"
77 #include "support/lstrings.h"
78 #include "support/os.h"
79 #include "support/Package.h"
80 #include "support/PathChanger.h"
81 #include "support/Systemcall.h"
82 #include "support/Timeout.h"
83 #include "support/ProgressInterface.h"
86 #include <QApplication>
87 #include <QCloseEvent>
88 #include <QDragEnterEvent>
91 #include <QFutureWatcher>
103 #include <QShowEvent>
105 #include <QStackedWidget>
106 #include <QStatusBar>
107 #include <QSvgRenderer>
108 #include <QtConcurrentRun>
111 #include <QWindowStateChangeEvent>
114 // sync with GuiAlert.cpp
115 #define EXPORT_in_THREAD 1
118 #include "support/bind.h"
122 #ifdef HAVE_SYS_TIME_H
123 # include <sys/time.h>
131 using namespace lyx::support;
135 using support::addExtension;
136 using support::changeExtension;
137 using support::removeExtension;
143 class BackgroundWidget : public QWidget
146 BackgroundWidget(int width, int height)
147 : width_(width), height_(height)
149 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
150 if (!lyxrc.show_banner)
152 /// The text to be written on top of the pixmap
153 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
154 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
155 /// The text to be written on top of the pixmap
156 QString const text = lyx_version ?
157 qt_("version ") + lyx_version : qt_("unknown version");
158 #if QT_VERSION >= 0x050000
159 QString imagedir = "images/";
160 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
161 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
162 if (svgRenderer.isValid()) {
163 splash_ = QPixmap(splashSize());
164 QPainter painter(&splash_);
165 svgRenderer.render(&painter);
166 splash_.setDevicePixelRatio(pixelRatio());
168 splash_ = getPixmap("images/", "banner", "png");
171 splash_ = getPixmap("images/", "banner", "svgz,png");
174 QPainter pain(&splash_);
175 pain.setPen(QColor(0, 0, 0));
176 qreal const fsize = fontSize();
179 qreal locscale = htextsize.toFloat(&ok);
182 QPointF const position = textPosition(false);
183 QPointF const hposition = textPosition(true);
184 QRectF const hrect(hposition, splashSize());
186 "widget pixel ratio: " << pixelRatio() <<
187 " splash pixel ratio: " << splashPixelRatio() <<
188 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
190 // The font used to display the version info
191 font.setStyleHint(QFont::SansSerif);
192 font.setWeight(QFont::Bold);
193 font.setPointSizeF(fsize);
195 pain.drawText(position, text);
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Normal);
199 font.setPointSizeF(hfsize);
200 // Check how long the logo gets with the current font
201 // and adapt if the font is running wider than what
203 GuiFontMetrics fm(font);
204 // Split the title into lines to measure the longest line
205 // in the current l7n.
206 QStringList titlesegs = htext.split('\n');
208 int hline = fm.maxHeight();
209 QStringList::const_iterator sit;
210 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
211 if (fm.width(*sit) > wline)
212 wline = fm.width(*sit);
214 // The longest line in the reference font (for English)
215 // is 180. Calculate scale factor from that.
216 double const wscale = wline > 0 ? (180.0 / wline) : 1;
217 // Now do the same for the height (necessary for condensed fonts)
218 double const hscale = (34.0 / hline);
219 // take the lower of the two scale factors.
220 double const scale = min(wscale, hscale);
221 // Now rescale. Also consider l7n's offset factor.
222 font.setPointSizeF(hfsize * scale * locscale);
225 pain.drawText(hrect, Qt::AlignLeft, htext);
226 setFocusPolicy(Qt::StrongFocus);
229 void paintEvent(QPaintEvent *) override
231 int const w = width_;
232 int const h = height_;
233 int const x = (width() - w) / 2;
234 int const y = (height() - h) / 2;
236 "widget pixel ratio: " << pixelRatio() <<
237 " splash pixel ratio: " << splashPixelRatio() <<
238 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
240 pain.drawPixmap(x, y, w, h, splash_);
243 void keyPressEvent(QKeyEvent * ev) override
246 setKeySymbol(&sym, ev);
248 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
260 /// Current ratio between physical pixels and device-independent pixels
261 double pixelRatio() const {
262 #if QT_VERSION >= 0x050000
263 return qt_scale_factor * devicePixelRatio();
269 qreal fontSize() const {
270 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
273 QPointF textPosition(bool const heading) const {
274 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
275 : QPointF(width_/2 - 18, height_/2 + 45);
278 QSize splashSize() const {
280 static_cast<unsigned int>(width_ * pixelRatio()),
281 static_cast<unsigned int>(height_ * pixelRatio()));
284 /// Ratio between physical pixels and device-independent pixels of splash image
285 double splashPixelRatio() const {
286 #if QT_VERSION >= 0x050000
287 return splash_.devicePixelRatio();
295 /// Toolbar store providing access to individual toolbars by name.
296 typedef map<string, GuiToolbar *> ToolbarMap;
298 typedef shared_ptr<Dialog> DialogPtr;
303 class GuiView::GuiViewPrivate
306 GuiViewPrivate(GuiViewPrivate const &);
307 void operator=(GuiViewPrivate const &);
309 GuiViewPrivate(GuiView * gv)
310 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
311 layout_(nullptr), autosave_timeout_(5000),
314 // hardcode here the platform specific icon size
315 smallIconSize = 16; // scaling problems
316 normalIconSize = 20; // ok, default if iconsize.png is missing
317 bigIconSize = 26; // better for some math icons
318 hugeIconSize = 32; // better for hires displays
321 // if it exists, use width of iconsize.png as normal size
322 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
323 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
325 QImage image(toqstr(fn.absFileName()));
326 if (image.width() < int(smallIconSize))
327 normalIconSize = smallIconSize;
328 else if (image.width() > int(giantIconSize))
329 normalIconSize = giantIconSize;
331 normalIconSize = image.width();
334 splitter_ = new QSplitter;
335 bg_widget_ = new BackgroundWidget(400, 250);
336 stack_widget_ = new QStackedWidget;
337 stack_widget_->addWidget(bg_widget_);
338 stack_widget_->addWidget(splitter_);
341 // TODO cleanup, remove the singleton, handle multiple Windows?
342 progress_ = ProgressInterface::instance();
343 if (!dynamic_cast<GuiProgress*>(progress_)) {
344 progress_ = new GuiProgress; // TODO who deletes it
345 ProgressInterface::setInstance(progress_);
348 dynamic_cast<GuiProgress*>(progress_),
349 SIGNAL(updateStatusBarMessage(QString const&)),
350 gv, SLOT(updateStatusBarMessage(QString const&)));
352 dynamic_cast<GuiProgress*>(progress_),
353 SIGNAL(clearMessageText()),
354 gv, SLOT(clearMessageText()));
361 delete stack_widget_;
366 stack_widget_->setCurrentWidget(bg_widget_);
367 bg_widget_->setUpdatesEnabled(true);
368 bg_widget_->setFocus();
371 int tabWorkAreaCount()
373 return splitter_->count();
376 TabWorkArea * tabWorkArea(int i)
378 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
381 TabWorkArea * currentTabWorkArea()
383 int areas = tabWorkAreaCount();
385 // The first TabWorkArea is always the first one, if any.
386 return tabWorkArea(0);
388 for (int i = 0; i != areas; ++i) {
389 TabWorkArea * twa = tabWorkArea(i);
390 if (current_main_work_area_ == twa->currentWorkArea())
394 // None has the focus so we just take the first one.
395 return tabWorkArea(0);
398 int countWorkAreasOf(Buffer & buf)
400 int areas = tabWorkAreaCount();
402 for (int i = 0; i != areas; ++i) {
403 TabWorkArea * twa = tabWorkArea(i);
404 if (twa->workArea(buf))
410 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
412 if (processing_thread_watcher_.isRunning()) {
413 // we prefer to cancel this preview in order to keep a snappy
417 processing_thread_watcher_.setFuture(f);
420 QSize iconSize(docstring const & icon_size)
423 if (icon_size == "small")
424 size = smallIconSize;
425 else if (icon_size == "normal")
426 size = normalIconSize;
427 else if (icon_size == "big")
429 else if (icon_size == "huge")
431 else if (icon_size == "giant")
432 size = giantIconSize;
434 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
436 if (size < smallIconSize)
437 size = smallIconSize;
439 return QSize(size, size);
442 QSize iconSize(QString const & icon_size)
444 return iconSize(qstring_to_ucs4(icon_size));
447 string & iconSize(QSize const & qsize)
449 LATTEST(qsize.width() == qsize.height());
451 static string icon_size;
453 unsigned int size = qsize.width();
455 if (size < smallIconSize)
456 size = smallIconSize;
458 if (size == smallIconSize)
460 else if (size == normalIconSize)
461 icon_size = "normal";
462 else if (size == bigIconSize)
464 else if (size == hugeIconSize)
466 else if (size == giantIconSize)
469 icon_size = convert<string>(size);
474 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
475 Buffer * buffer, string const & format);
476 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
483 static Buffer::ExportStatus runAndDestroy(const T& func,
484 Buffer const * orig, Buffer * buffer, string const & format);
486 // TODO syncFunc/previewFunc: use bind
487 bool asyncBufferProcessing(string const & argument,
488 Buffer const * used_buffer,
489 docstring const & msg,
490 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
491 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
492 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 QVector<GuiWorkArea*> guiWorkAreas();
499 GuiWorkArea * current_work_area_;
500 GuiWorkArea * current_main_work_area_;
501 QSplitter * splitter_;
502 QStackedWidget * stack_widget_;
503 BackgroundWidget * bg_widget_;
505 ToolbarMap toolbars_;
506 ProgressInterface* progress_;
507 /// The main layout box.
509 * \warning Don't Delete! The layout box is actually owned by
510 * whichever toolbar contains it. All the GuiView class needs is a
511 * means of accessing it.
513 * FIXME: replace that with a proper model so that we are not limited
514 * to only one dialog.
519 map<string, DialogPtr> dialogs_;
522 QTimer statusbar_timer_;
523 /// auto-saving of buffers
524 Timeout autosave_timeout_;
527 TocModels toc_models_;
530 QFutureWatcher<docstring> autosave_watcher_;
531 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
533 string last_export_format;
534 string processing_format;
536 static QSet<Buffer const *> busyBuffers;
538 unsigned int smallIconSize;
539 unsigned int normalIconSize;
540 unsigned int bigIconSize;
541 unsigned int hugeIconSize;
542 unsigned int giantIconSize;
544 /// flag against a race condition due to multiclicks, see bug #1119
548 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
551 GuiView::GuiView(int id)
552 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
553 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
556 connect(this, SIGNAL(bufferViewChanged()),
557 this, SLOT(onBufferViewChanged()));
559 // GuiToolbars *must* be initialised before the menu bar.
560 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
563 // set ourself as the current view. This is needed for the menu bar
564 // filling, at least for the static special menu item on Mac. Otherwise
565 // they are greyed out.
566 guiApp->setCurrentView(this);
568 // Fill up the menu bar.
569 guiApp->menus().fillMenuBar(menuBar(), this, true);
571 setCentralWidget(d.stack_widget_);
573 // Start autosave timer
574 if (lyxrc.autosave) {
575 // The connection is closed when this is destroyed.
576 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
577 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
578 d.autosave_timeout_.start();
580 connect(&d.statusbar_timer_, SIGNAL(timeout()),
581 this, SLOT(clearMessage()));
583 // We don't want to keep the window in memory if it is closed.
584 setAttribute(Qt::WA_DeleteOnClose, true);
586 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
587 // QIcon::fromTheme was introduced in Qt 4.6
588 #if (QT_VERSION >= 0x040600)
589 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
590 // since the icon is provided in the application bundle. We use a themed
591 // version when available and use the bundled one as fallback.
592 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
594 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
600 // use tabbed dock area for multiple docks
601 // (such as "source" and "messages")
602 setDockOptions(QMainWindow::ForceTabbedDocks);
605 setAcceptDrops(true);
607 // add busy indicator to statusbar
608 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
609 statusBar()->addPermanentWidget(busylabel);
610 search_mode mode = theGuiApp()->imageSearchMode();
611 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
612 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
613 busylabel->setMovie(busyanim);
617 connect(&d.processing_thread_watcher_, SIGNAL(started()),
618 busylabel, SLOT(show()));
619 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
620 busylabel, SLOT(hide()));
621 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
623 QFontMetrics const fm(statusBar()->fontMetrics());
624 int const iconheight = max(int(d.normalIconSize), fm.height());
625 QSize const iconsize(iconheight, iconheight);
627 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
628 shell_escape_ = new QLabel(statusBar());
629 shell_escape_->setPixmap(shellescape);
630 shell_escape_->setScaledContents(true);
631 shell_escape_->setAlignment(Qt::AlignCenter);
632 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
633 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
634 "external commands for this document. "
635 "Right click to change."));
636 SEMenu * menu = new SEMenu(this);
637 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
638 menu, SLOT(showMenu(QPoint)));
639 shell_escape_->hide();
640 statusBar()->addPermanentWidget(shell_escape_);
642 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
643 read_only_ = new QLabel(statusBar());
644 read_only_->setPixmap(readonly);
645 read_only_->setScaledContents(true);
646 read_only_->setAlignment(Qt::AlignCenter);
648 statusBar()->addPermanentWidget(read_only_);
650 version_control_ = new QLabel(statusBar());
651 version_control_->setAlignment(Qt::AlignCenter);
652 version_control_->setFrameStyle(QFrame::StyledPanel);
653 version_control_->hide();
654 statusBar()->addPermanentWidget(version_control_);
656 statusBar()->setSizeGripEnabled(true);
659 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
660 SLOT(autoSaveThreadFinished()));
662 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
663 SLOT(processingThreadStarted()));
664 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
665 SLOT(processingThreadFinished()));
667 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
668 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
670 // set custom application bars context menu, e.g. tool bar and menu bar
671 setContextMenuPolicy(Qt::CustomContextMenu);
672 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
673 SLOT(toolBarPopup(const QPoint &)));
675 // Forbid too small unresizable window because it can happen
676 // with some window manager under X11.
677 setMinimumSize(300, 200);
679 if (lyxrc.allow_geometry_session) {
680 // Now take care of session management.
685 // no session handling, default to a sane size.
686 setGeometry(50, 50, 690, 510);
689 // clear session data if any.
691 settings.remove("views");
701 void GuiView::disableShellEscape()
703 BufferView * bv = documentBufferView();
706 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
707 bv->buffer().params().shell_escape = false;
708 bv->processUpdateFlags(Update::Force);
712 void GuiView::checkCancelBackground()
714 docstring const ttl = _("Cancel Export?");
715 docstring const msg = _("Do you want to cancel the background export process?");
717 Alert::prompt(ttl, msg, 1, 1,
718 _("&Cancel export"), _("Co&ntinue"));
720 Systemcall::killscript();
724 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
726 QVector<GuiWorkArea*> areas;
727 for (int i = 0; i < tabWorkAreaCount(); i++) {
728 TabWorkArea* ta = tabWorkArea(i);
729 for (int u = 0; u < ta->count(); u++) {
730 areas << ta->workArea(u);
736 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
737 string const & format)
739 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
742 case Buffer::ExportSuccess:
743 msg = bformat(_("Successful export to format: %1$s"), fmt);
745 case Buffer::ExportCancel:
746 msg = _("Document export cancelled.");
748 case Buffer::ExportError:
749 case Buffer::ExportNoPathToFormat:
750 case Buffer::ExportTexPathHasSpaces:
751 case Buffer::ExportConverterError:
752 msg = bformat(_("Error while exporting format: %1$s"), fmt);
754 case Buffer::PreviewSuccess:
755 msg = bformat(_("Successful preview of format: %1$s"), fmt);
757 case Buffer::PreviewError:
758 msg = bformat(_("Error while previewing format: %1$s"), fmt);
760 case Buffer::ExportKilled:
761 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
768 void GuiView::processingThreadStarted()
773 void GuiView::processingThreadFinished()
775 QFutureWatcher<Buffer::ExportStatus> const * watcher =
776 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
778 Buffer::ExportStatus const status = watcher->result();
779 handleExportStatus(this, status, d.processing_format);
782 BufferView const * const bv = currentBufferView();
783 if (bv && !bv->buffer().errorList("Export").empty()) {
788 bool const error = (status != Buffer::ExportSuccess &&
789 status != Buffer::PreviewSuccess &&
790 status != Buffer::ExportCancel);
792 ErrorList & el = bv->buffer().errorList(d.last_export_format);
793 // at this point, we do not know if buffer-view or
794 // master-buffer-view was called. If there was an export error,
795 // and the current buffer's error log is empty, we guess that
796 // it must be master-buffer-view that was called so we set
798 errors(d.last_export_format, el.empty());
803 void GuiView::autoSaveThreadFinished()
805 QFutureWatcher<docstring> const * watcher =
806 static_cast<QFutureWatcher<docstring> const *>(sender());
807 message(watcher->result());
812 void GuiView::saveLayout() const
815 settings.setValue("zoom_ratio", zoom_ratio_);
816 settings.setValue("devel_mode", devel_mode_);
817 settings.beginGroup("views");
818 settings.beginGroup(QString::number(id_));
819 #if defined(Q_WS_X11) || defined(QPA_XCB)
820 settings.setValue("pos", pos());
821 settings.setValue("size", size());
823 settings.setValue("geometry", saveGeometry());
825 settings.setValue("layout", saveState(0));
826 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
830 void GuiView::saveUISettings() const
834 // Save the toolbar private states
835 ToolbarMap::iterator end = d.toolbars_.end();
836 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
837 it->second->saveSession(settings);
838 // Now take care of all other dialogs
839 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
840 for (; it!= d.dialogs_.end(); ++it)
841 it->second->saveSession(settings);
845 bool GuiView::restoreLayout()
848 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
849 // Actual zoom value: default zoom + fractional offset
850 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
851 if (zoom < static_cast<int>(zoom_min_))
853 lyxrc.currentZoom = zoom;
854 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
855 settings.beginGroup("views");
856 settings.beginGroup(QString::number(id_));
857 QString const icon_key = "icon_size";
858 if (!settings.contains(icon_key))
861 //code below is skipped when when ~/.config/LyX is (re)created
862 setIconSize(d.iconSize(settings.value(icon_key).toString()));
864 #if defined(Q_WS_X11) || defined(QPA_XCB)
865 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
866 QSize size = settings.value("size", QSize(690, 510)).toSize();
870 // Work-around for bug #6034: the window ends up in an undetermined
871 // state when trying to restore a maximized window when it is
872 // already maximized.
873 if (!(windowState() & Qt::WindowMaximized))
874 if (!restoreGeometry(settings.value("geometry").toByteArray()))
875 setGeometry(50, 50, 690, 510);
877 // Make sure layout is correctly oriented.
878 setLayoutDirection(qApp->layoutDirection());
880 // Allow the toc and view-source dock widget to be restored if needed.
882 if ((dialog = findOrBuild("toc", true)))
883 // see bug 5082. At least setup title and enabled state.
884 // Visibility will be adjusted by restoreState below.
885 dialog->prepareView();
886 if ((dialog = findOrBuild("view-source", true)))
887 dialog->prepareView();
888 if ((dialog = findOrBuild("progress", true)))
889 dialog->prepareView();
891 if (!restoreState(settings.value("layout").toByteArray(), 0))
894 // init the toolbars that have not been restored
895 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
896 Toolbars::Infos::iterator end = guiApp->toolbars().end();
897 for (; cit != end; ++cit) {
898 GuiToolbar * tb = toolbar(cit->name);
899 if (tb && !tb->isRestored())
900 initToolbar(cit->name);
903 // update lock (all) toolbars positions
904 updateLockToolbars();
911 GuiToolbar * GuiView::toolbar(string const & name)
913 ToolbarMap::iterator it = d.toolbars_.find(name);
914 if (it != d.toolbars_.end())
917 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
922 void GuiView::updateLockToolbars()
924 toolbarsMovable_ = false;
925 for (ToolbarInfo const & info : guiApp->toolbars()) {
926 GuiToolbar * tb = toolbar(info.name);
927 if (tb && tb->isMovable())
928 toolbarsMovable_ = true;
933 void GuiView::constructToolbars()
935 ToolbarMap::iterator it = d.toolbars_.begin();
936 for (; it != d.toolbars_.end(); ++it)
940 // I don't like doing this here, but the standard toolbar
941 // destroys this object when it's destroyed itself (vfr)
942 d.layout_ = new LayoutBox(*this);
943 d.stack_widget_->addWidget(d.layout_);
944 d.layout_->move(0,0);
946 // extracts the toolbars from the backend
947 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
948 Toolbars::Infos::iterator end = guiApp->toolbars().end();
949 for (; cit != end; ++cit)
950 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
954 void GuiView::initToolbars()
956 // extracts the toolbars from the backend
957 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
958 Toolbars::Infos::iterator end = guiApp->toolbars().end();
959 for (; cit != end; ++cit)
960 initToolbar(cit->name);
964 void GuiView::initToolbar(string const & name)
966 GuiToolbar * tb = toolbar(name);
969 int const visibility = guiApp->toolbars().defaultVisibility(name);
970 bool newline = !(visibility & Toolbars::SAMEROW);
971 tb->setVisible(false);
972 tb->setVisibility(visibility);
974 if (visibility & Toolbars::TOP) {
976 addToolBarBreak(Qt::TopToolBarArea);
977 addToolBar(Qt::TopToolBarArea, tb);
980 if (visibility & Toolbars::BOTTOM) {
982 addToolBarBreak(Qt::BottomToolBarArea);
983 addToolBar(Qt::BottomToolBarArea, tb);
986 if (visibility & Toolbars::LEFT) {
988 addToolBarBreak(Qt::LeftToolBarArea);
989 addToolBar(Qt::LeftToolBarArea, tb);
992 if (visibility & Toolbars::RIGHT) {
994 addToolBarBreak(Qt::RightToolBarArea);
995 addToolBar(Qt::RightToolBarArea, tb);
998 if (visibility & Toolbars::ON)
999 tb->setVisible(true);
1001 tb->setMovable(true);
1005 TocModels & GuiView::tocModels()
1007 return d.toc_models_;
1011 void GuiView::setFocus()
1013 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1014 QMainWindow::setFocus();
1018 bool GuiView::hasFocus() const
1020 if (currentWorkArea())
1021 return currentWorkArea()->hasFocus();
1022 if (currentMainWorkArea())
1023 return currentMainWorkArea()->hasFocus();
1024 return d.bg_widget_->hasFocus();
1028 void GuiView::focusInEvent(QFocusEvent * e)
1030 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1031 QMainWindow::focusInEvent(e);
1032 // Make sure guiApp points to the correct view.
1033 guiApp->setCurrentView(this);
1034 if (currentWorkArea())
1035 currentWorkArea()->setFocus();
1036 else if (currentMainWorkArea())
1037 currentMainWorkArea()->setFocus();
1039 d.bg_widget_->setFocus();
1043 void GuiView::showEvent(QShowEvent * e)
1045 LYXERR(Debug::GUI, "Passed Geometry "
1046 << size().height() << "x" << size().width()
1047 << "+" << pos().x() << "+" << pos().y());
1049 if (d.splitter_->count() == 0)
1050 // No work area, switch to the background widget.
1054 QMainWindow::showEvent(e);
1058 bool GuiView::closeScheduled()
1065 bool GuiView::prepareAllBuffersForLogout()
1067 Buffer * first = theBufferList().first();
1071 // First, iterate over all buffers and ask the users if unsaved
1072 // changes should be saved.
1073 // We cannot use a for loop as the buffer list cycles.
1076 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1078 b = theBufferList().next(b);
1079 } while (b != first);
1081 // Next, save session state
1082 // When a view/window was closed before without quitting LyX, there
1083 // are already entries in the lastOpened list.
1084 theSession().lastOpened().clear();
1091 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1092 ** is responsibility of the container (e.g., dialog)
1094 void GuiView::closeEvent(QCloseEvent * close_event)
1096 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1098 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1099 Alert::warning(_("Exit LyX"),
1100 _("LyX could not be closed because documents are being processed by LyX."));
1101 close_event->setAccepted(false);
1105 // If the user pressed the x (so we didn't call closeView
1106 // programmatically), we want to clear all existing entries.
1108 theSession().lastOpened().clear();
1113 // it can happen that this event arrives without selecting the view,
1114 // e.g. when clicking the close button on a background window.
1116 if (!closeWorkAreaAll()) {
1118 close_event->ignore();
1122 // Make sure that nothing will use this to be closed View.
1123 guiApp->unregisterView(this);
1125 if (isFullScreen()) {
1126 // Switch off fullscreen before closing.
1131 // Make sure the timer time out will not trigger a statusbar update.
1132 d.statusbar_timer_.stop();
1134 // Saving fullscreen requires additional tweaks in the toolbar code.
1135 // It wouldn't also work under linux natively.
1136 if (lyxrc.allow_geometry_session) {
1141 close_event->accept();
1145 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1147 if (event->mimeData()->hasUrls())
1149 /// \todo Ask lyx-devel is this is enough:
1150 /// if (event->mimeData()->hasFormat("text/plain"))
1151 /// event->acceptProposedAction();
1155 void GuiView::dropEvent(QDropEvent * event)
1157 QList<QUrl> files = event->mimeData()->urls();
1158 if (files.isEmpty())
1161 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1162 for (int i = 0; i != files.size(); ++i) {
1163 string const file = os::internal_path(fromqstr(
1164 files.at(i).toLocalFile()));
1168 string const ext = support::getExtension(file);
1169 vector<const Format *> found_formats;
1171 // Find all formats that have the correct extension.
1172 vector<const Format *> const & import_formats
1173 = theConverters().importableFormats();
1174 vector<const Format *>::const_iterator it = import_formats.begin();
1175 for (; it != import_formats.end(); ++it)
1176 if ((*it)->hasExtension(ext))
1177 found_formats.push_back(*it);
1180 if (!found_formats.empty()) {
1181 if (found_formats.size() > 1) {
1182 //FIXME: show a dialog to choose the correct importable format
1183 LYXERR(Debug::FILES,
1184 "Multiple importable formats found, selecting first");
1186 string const arg = found_formats[0]->name() + " " + file;
1187 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1190 //FIXME: do we have to explicitly check whether it's a lyx file?
1191 LYXERR(Debug::FILES,
1192 "No formats found, trying to open it as a lyx file");
1193 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1195 // add the functions to the queue
1196 guiApp->addToFuncRequestQueue(cmd);
1199 // now process the collected functions. We perform the events
1200 // asynchronously. This prevents potential problems in case the
1201 // BufferView is closed within an event.
1202 guiApp->processFuncRequestQueueAsync();
1206 void GuiView::message(docstring const & str)
1208 if (ForkedProcess::iAmAChild())
1211 // call is moved to GUI-thread by GuiProgress
1212 d.progress_->appendMessage(toqstr(str));
1216 void GuiView::clearMessageText()
1218 message(docstring());
1222 void GuiView::updateStatusBarMessage(QString const & str)
1224 statusBar()->showMessage(str);
1225 d.statusbar_timer_.stop();
1226 d.statusbar_timer_.start(3000);
1230 void GuiView::clearMessage()
1232 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1233 // the hasFocus function mostly returns false, even if the focus is on
1234 // a workarea in this view.
1238 d.statusbar_timer_.stop();
1242 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1244 if (wa != d.current_work_area_
1245 || wa->bufferView().buffer().isInternal())
1247 Buffer const & buf = wa->bufferView().buffer();
1248 // Set the windows title
1249 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1250 if (buf.notifiesExternalModification()) {
1251 title = bformat(_("%1$s (modified externally)"), title);
1252 // If the external modification status has changed, then maybe the status of
1253 // buffer-save has changed too.
1257 title += from_ascii(" - LyX");
1259 setWindowTitle(toqstr(title));
1260 // Sets the path for the window: this is used by OSX to
1261 // allow a context click on the title bar showing a menu
1262 // with the path up to the file
1263 setWindowFilePath(toqstr(buf.absFileName()));
1264 // Tell Qt whether the current document is changed
1265 setWindowModified(!buf.isClean());
1267 if (buf.params().shell_escape)
1268 shell_escape_->show();
1270 shell_escape_->hide();
1272 if (buf.hasReadonlyFlag())
1277 if (buf.lyxvc().inUse()) {
1278 version_control_->show();
1279 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1281 version_control_->hide();
1285 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1287 if (d.current_work_area_)
1288 // disconnect the current work area from all slots
1289 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1291 disconnectBufferView();
1292 connectBufferView(wa->bufferView());
1293 connectBuffer(wa->bufferView().buffer());
1294 d.current_work_area_ = wa;
1295 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1296 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1297 QObject::connect(wa, SIGNAL(busy(bool)),
1298 this, SLOT(setBusy(bool)));
1299 // connection of a signal to a signal
1300 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1301 this, SIGNAL(bufferViewChanged()));
1302 Q_EMIT updateWindowTitle(wa);
1303 Q_EMIT bufferViewChanged();
1307 void GuiView::onBufferViewChanged()
1310 // Buffer-dependent dialogs must be updated. This is done here because
1311 // some dialogs require buffer()->text.
1316 void GuiView::on_lastWorkAreaRemoved()
1319 // We already are in a close event. Nothing more to do.
1322 if (d.splitter_->count() > 1)
1323 // We have a splitter so don't close anything.
1326 // Reset and updates the dialogs.
1327 Q_EMIT bufferViewChanged();
1332 if (lyxrc.open_buffers_in_tabs)
1333 // Nothing more to do, the window should stay open.
1336 if (guiApp->viewIds().size() > 1) {
1342 // On Mac we also close the last window because the application stay
1343 // resident in memory. On other platforms we don't close the last
1344 // window because this would quit the application.
1350 void GuiView::updateStatusBar()
1352 // let the user see the explicit message
1353 if (d.statusbar_timer_.isActive())
1360 void GuiView::showMessage()
1364 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1365 if (msg.isEmpty()) {
1366 BufferView const * bv = currentBufferView();
1368 msg = toqstr(bv->cursor().currentState(devel_mode_));
1370 msg = qt_("Welcome to LyX!");
1372 statusBar()->showMessage(msg);
1376 bool GuiView::event(QEvent * e)
1380 // Useful debug code:
1381 //case QEvent::ActivationChange:
1382 //case QEvent::WindowDeactivate:
1383 //case QEvent::Paint:
1384 //case QEvent::Enter:
1385 //case QEvent::Leave:
1386 //case QEvent::HoverEnter:
1387 //case QEvent::HoverLeave:
1388 //case QEvent::HoverMove:
1389 //case QEvent::StatusTip:
1390 //case QEvent::DragEnter:
1391 //case QEvent::DragLeave:
1392 //case QEvent::Drop:
1395 case QEvent::WindowStateChange: {
1396 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1397 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1398 bool result = QMainWindow::event(e);
1399 bool nfstate = (windowState() & Qt::WindowFullScreen);
1400 if (!ofstate && nfstate) {
1401 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1402 // switch to full-screen state
1403 if (lyxrc.full_screen_statusbar)
1404 statusBar()->hide();
1405 if (lyxrc.full_screen_menubar)
1407 if (lyxrc.full_screen_toolbars) {
1408 ToolbarMap::iterator end = d.toolbars_.end();
1409 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1410 if (it->second->isVisibiltyOn() && it->second->isVisible())
1413 for (int i = 0; i != d.splitter_->count(); ++i)
1414 d.tabWorkArea(i)->setFullScreen(true);
1415 setContentsMargins(-2, -2, -2, -2);
1417 hideDialogs("prefs", nullptr);
1418 } else if (ofstate && !nfstate) {
1419 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1420 // switch back from full-screen state
1421 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1422 statusBar()->show();
1423 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1425 if (lyxrc.full_screen_toolbars) {
1426 ToolbarMap::iterator end = d.toolbars_.end();
1427 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1428 if (it->second->isVisibiltyOn() && !it->second->isVisible())
1432 for (int i = 0; i != d.splitter_->count(); ++i)
1433 d.tabWorkArea(i)->setFullScreen(false);
1434 setContentsMargins(0, 0, 0, 0);
1438 case QEvent::WindowActivate: {
1439 GuiView * old_view = guiApp->currentView();
1440 if (this == old_view) {
1442 return QMainWindow::event(e);
1444 if (old_view && old_view->currentBufferView()) {
1445 // save current selection to the selection buffer to allow
1446 // middle-button paste in this window.
1447 cap::saveSelection(old_view->currentBufferView()->cursor());
1449 guiApp->setCurrentView(this);
1450 if (d.current_work_area_)
1451 on_currentWorkAreaChanged(d.current_work_area_);
1455 return QMainWindow::event(e);
1458 case QEvent::ShortcutOverride: {
1460 if (isFullScreen() && menuBar()->isHidden()) {
1461 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1462 // FIXME: we should also try to detect special LyX shortcut such as
1463 // Alt-P and Alt-M. Right now there is a hack in
1464 // GuiWorkArea::processKeySym() that hides again the menubar for
1466 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1468 return QMainWindow::event(e);
1471 return QMainWindow::event(e);
1475 return QMainWindow::event(e);
1479 void GuiView::resetWindowTitle()
1481 setWindowTitle(qt_("LyX"));
1484 bool GuiView::focusNextPrevChild(bool /*next*/)
1491 bool GuiView::busy() const
1497 void GuiView::setBusy(bool busy)
1499 bool const busy_before = busy_ > 0;
1500 busy ? ++busy_ : --busy_;
1501 if ((busy_ > 0) == busy_before)
1502 // busy state didn't change
1506 QApplication::setOverrideCursor(Qt::WaitCursor);
1509 QApplication::restoreOverrideCursor();
1514 void GuiView::resetCommandExecute()
1516 command_execute_ = false;
1521 double GuiView::pixelRatio() const
1523 #if QT_VERSION >= 0x050000
1524 return qt_scale_factor * devicePixelRatio();
1531 GuiWorkArea * GuiView::workArea(int index)
1533 if (TabWorkArea * twa = d.currentTabWorkArea())
1534 if (index < twa->count())
1535 return twa->workArea(index);
1540 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1542 if (currentWorkArea()
1543 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1544 return currentWorkArea();
1545 if (TabWorkArea * twa = d.currentTabWorkArea())
1546 return twa->workArea(buffer);
1551 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1553 // Automatically create a TabWorkArea if there are none yet.
1554 TabWorkArea * tab_widget = d.splitter_->count()
1555 ? d.currentTabWorkArea() : addTabWorkArea();
1556 return tab_widget->addWorkArea(buffer, *this);
1560 TabWorkArea * GuiView::addTabWorkArea()
1562 TabWorkArea * twa = new TabWorkArea;
1563 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1564 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1565 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1566 this, SLOT(on_lastWorkAreaRemoved()));
1568 d.splitter_->addWidget(twa);
1569 d.stack_widget_->setCurrentWidget(d.splitter_);
1574 GuiWorkArea const * GuiView::currentWorkArea() const
1576 return d.current_work_area_;
1580 GuiWorkArea * GuiView::currentWorkArea()
1582 return d.current_work_area_;
1586 GuiWorkArea const * GuiView::currentMainWorkArea() const
1588 if (!d.currentTabWorkArea())
1590 return d.currentTabWorkArea()->currentWorkArea();
1594 GuiWorkArea * GuiView::currentMainWorkArea()
1596 if (!d.currentTabWorkArea())
1598 return d.currentTabWorkArea()->currentWorkArea();
1602 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1604 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1606 d.current_work_area_ = nullptr;
1608 Q_EMIT bufferViewChanged();
1612 // FIXME: I've no clue why this is here and why it accesses
1613 // theGuiApp()->currentView, which might be 0 (bug 6464).
1614 // See also 27525 (vfr).
1615 if (theGuiApp()->currentView() == this
1616 && theGuiApp()->currentView()->currentWorkArea() == wa)
1619 if (currentBufferView())
1620 cap::saveSelection(currentBufferView()->cursor());
1622 theGuiApp()->setCurrentView(this);
1623 d.current_work_area_ = wa;
1625 // We need to reset this now, because it will need to be
1626 // right if the tabWorkArea gets reset in the for loop. We
1627 // will change it back if we aren't in that case.
1628 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1629 d.current_main_work_area_ = wa;
1631 for (int i = 0; i != d.splitter_->count(); ++i) {
1632 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1633 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1634 << ", Current main wa: " << currentMainWorkArea());
1639 d.current_main_work_area_ = old_cmwa;
1641 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1642 on_currentWorkAreaChanged(wa);
1643 BufferView & bv = wa->bufferView();
1644 bv.cursor().fixIfBroken();
1646 wa->setUpdatesEnabled(true);
1647 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1651 void GuiView::removeWorkArea(GuiWorkArea * wa)
1653 LASSERT(wa, return);
1654 if (wa == d.current_work_area_) {
1656 disconnectBufferView();
1657 d.current_work_area_ = nullptr;
1658 d.current_main_work_area_ = nullptr;
1661 bool found_twa = false;
1662 for (int i = 0; i != d.splitter_->count(); ++i) {
1663 TabWorkArea * twa = d.tabWorkArea(i);
1664 if (twa->removeWorkArea(wa)) {
1665 // Found in this tab group, and deleted the GuiWorkArea.
1667 if (twa->count() != 0) {
1668 if (d.current_work_area_ == nullptr)
1669 // This means that we are closing the current GuiWorkArea, so
1670 // switch to the next GuiWorkArea in the found TabWorkArea.
1671 setCurrentWorkArea(twa->currentWorkArea());
1673 // No more WorkAreas in this tab group, so delete it.
1680 // It is not a tabbed work area (i.e., the search work area), so it
1681 // should be deleted by other means.
1682 LASSERT(found_twa, return);
1684 if (d.current_work_area_ == nullptr) {
1685 if (d.splitter_->count() != 0) {
1686 TabWorkArea * twa = d.currentTabWorkArea();
1687 setCurrentWorkArea(twa->currentWorkArea());
1689 // No more work areas, switch to the background widget.
1690 setCurrentWorkArea(nullptr);
1696 LayoutBox * GuiView::getLayoutDialog() const
1702 void GuiView::updateLayoutList()
1705 d.layout_->updateContents(false);
1709 void GuiView::updateToolbars()
1711 ToolbarMap::iterator end = d.toolbars_.end();
1712 if (d.current_work_area_) {
1714 if (d.current_work_area_->bufferView().cursor().inMathed()
1715 && !d.current_work_area_->bufferView().cursor().inRegexped())
1716 context |= Toolbars::MATH;
1717 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1718 context |= Toolbars::TABLE;
1719 if (currentBufferView()->buffer().areChangesPresent()
1720 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1721 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1722 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1723 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1724 context |= Toolbars::REVIEW;
1725 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1726 context |= Toolbars::MATHMACROTEMPLATE;
1727 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1728 context |= Toolbars::IPA;
1729 if (command_execute_)
1730 context |= Toolbars::MINIBUFFER;
1731 if (minibuffer_focus_) {
1732 context |= Toolbars::MINIBUFFER_FOCUS;
1733 minibuffer_focus_ = false;
1736 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1737 it->second->update(context);
1739 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1740 it->second->update();
1744 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1746 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1747 LASSERT(newBuffer, return);
1749 GuiWorkArea * wa = workArea(*newBuffer);
1750 if (wa == nullptr) {
1752 newBuffer->masterBuffer()->updateBuffer();
1754 wa = addWorkArea(*newBuffer);
1755 // scroll to the position when the BufferView was last closed
1756 if (lyxrc.use_lastfilepos) {
1757 LastFilePosSection::FilePos filepos =
1758 theSession().lastFilePos().load(newBuffer->fileName());
1759 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1762 //Disconnect the old buffer...there's no new one.
1765 connectBuffer(*newBuffer);
1766 connectBufferView(wa->bufferView());
1768 setCurrentWorkArea(wa);
1772 void GuiView::connectBuffer(Buffer & buf)
1774 buf.setGuiDelegate(this);
1778 void GuiView::disconnectBuffer()
1780 if (d.current_work_area_)
1781 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1785 void GuiView::connectBufferView(BufferView & bv)
1787 bv.setGuiDelegate(this);
1791 void GuiView::disconnectBufferView()
1793 if (d.current_work_area_)
1794 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1798 void GuiView::errors(string const & error_type, bool from_master)
1800 BufferView const * const bv = currentBufferView();
1804 ErrorList const & el = from_master ?
1805 bv->buffer().masterBuffer()->errorList(error_type) :
1806 bv->buffer().errorList(error_type);
1811 string err = error_type;
1813 err = "from_master|" + error_type;
1814 showDialog("errorlist", err);
1818 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1820 d.toc_models_.updateItem(toqstr(type), dit);
1824 void GuiView::structureChanged()
1826 // This is called from the Buffer, which has no way to ensure that cursors
1827 // in BufferView remain valid.
1828 if (documentBufferView())
1829 documentBufferView()->cursor().sanitize();
1830 // FIXME: This is slightly expensive, though less than the tocBackend update
1831 // (#9880). This also resets the view in the Toc Widget (#6675).
1832 d.toc_models_.reset(documentBufferView());
1833 // Navigator needs more than a simple update in this case. It needs to be
1835 updateDialog("toc", "");
1839 void GuiView::updateDialog(string const & name, string const & sdata)
1841 if (!isDialogVisible(name))
1844 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1845 if (it == d.dialogs_.end())
1848 Dialog * const dialog = it->second.get();
1849 if (dialog->isVisibleView())
1850 dialog->initialiseParams(sdata);
1854 BufferView * GuiView::documentBufferView()
1856 return currentMainWorkArea()
1857 ? ¤tMainWorkArea()->bufferView()
1862 BufferView const * GuiView::documentBufferView() const
1864 return currentMainWorkArea()
1865 ? ¤tMainWorkArea()->bufferView()
1870 BufferView * GuiView::currentBufferView()
1872 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1876 BufferView const * GuiView::currentBufferView() const
1878 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1882 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1883 Buffer const * orig, Buffer * clone)
1885 bool const success = clone->autoSave();
1887 busyBuffers.remove(orig);
1889 ? _("Automatic save done.")
1890 : _("Automatic save failed!");
1894 void GuiView::autoSave()
1896 LYXERR(Debug::INFO, "Running autoSave()");
1898 Buffer * buffer = documentBufferView()
1899 ? &documentBufferView()->buffer() : nullptr;
1901 resetAutosaveTimers();
1905 GuiViewPrivate::busyBuffers.insert(buffer);
1906 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1907 buffer, buffer->cloneBufferOnly());
1908 d.autosave_watcher_.setFuture(f);
1909 resetAutosaveTimers();
1913 void GuiView::resetAutosaveTimers()
1916 d.autosave_timeout_.restart();
1920 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1923 Buffer * buf = currentBufferView()
1924 ? ¤tBufferView()->buffer() : nullptr;
1925 Buffer * doc_buffer = documentBufferView()
1926 ? &(documentBufferView()->buffer()) : nullptr;
1929 /* In LyX/Mac, when a dialog is open, the menus of the
1930 application can still be accessed without giving focus to
1931 the main window. In this case, we want to disable the menu
1932 entries that are buffer-related.
1933 This code must not be used on Linux and Windows, since it
1934 would disable buffer-related entries when hovering over the
1935 menu (see bug #9574).
1937 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1943 // Check whether we need a buffer
1944 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1945 // no, exit directly
1946 flag.message(from_utf8(N_("Command not allowed with"
1947 "out any document open")));
1948 flag.setEnabled(false);
1952 if (cmd.origin() == FuncRequest::TOC) {
1953 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1954 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1955 flag.setEnabled(false);
1959 switch(cmd.action()) {
1960 case LFUN_BUFFER_IMPORT:
1963 case LFUN_MASTER_BUFFER_EXPORT:
1965 && (doc_buffer->parent() != nullptr
1966 || doc_buffer->hasChildren())
1967 && !d.processing_thread_watcher_.isRunning()
1968 // this launches a dialog, which would be in the wrong Buffer
1969 && !(::lyx::operator==(cmd.argument(), "custom"));
1972 case LFUN_MASTER_BUFFER_UPDATE:
1973 case LFUN_MASTER_BUFFER_VIEW:
1975 && (doc_buffer->parent() != nullptr
1976 || doc_buffer->hasChildren())
1977 && !d.processing_thread_watcher_.isRunning();
1980 case LFUN_BUFFER_UPDATE:
1981 case LFUN_BUFFER_VIEW: {
1982 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1986 string format = to_utf8(cmd.argument());
1987 if (cmd.argument().empty())
1988 format = doc_buffer->params().getDefaultOutputFormat();
1989 enable = doc_buffer->params().isExportable(format, true);
1993 case LFUN_BUFFER_RELOAD:
1994 enable = doc_buffer && !doc_buffer->isUnnamed()
1995 && doc_buffer->fileName().exists()
1996 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1999 case LFUN_BUFFER_RESET_EXPORT:
2000 enable = doc_buffer != nullptr;
2003 case LFUN_BUFFER_CHILD_OPEN:
2004 enable = doc_buffer != nullptr;
2007 case LFUN_MASTER_BUFFER_FORALL: {
2008 if (doc_buffer == nullptr) {
2009 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2013 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2014 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2015 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2020 for (Buffer * buf : doc_buffer->allRelatives()) {
2021 GuiWorkArea * wa = workArea(*buf);
2024 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2025 enable = flag.enabled();
2032 case LFUN_BUFFER_WRITE:
2033 enable = doc_buffer && (doc_buffer->isUnnamed()
2034 || (!doc_buffer->isClean()
2035 || cmd.argument() == "force"));
2038 //FIXME: This LFUN should be moved to GuiApplication.
2039 case LFUN_BUFFER_WRITE_ALL: {
2040 // We enable the command only if there are some modified buffers
2041 Buffer * first = theBufferList().first();
2046 // We cannot use a for loop as the buffer list is a cycle.
2048 if (!b->isClean()) {
2052 b = theBufferList().next(b);
2053 } while (b != first);
2057 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2058 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2061 case LFUN_BUFFER_EXPORT: {
2062 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2066 return doc_buffer->getStatus(cmd, flag);
2069 case LFUN_BUFFER_EXPORT_AS:
2070 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2075 case LFUN_BUFFER_WRITE_AS:
2076 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2077 enable = doc_buffer != nullptr;
2080 case LFUN_EXPORT_CANCEL:
2081 enable = d.processing_thread_watcher_.isRunning();
2084 case LFUN_BUFFER_CLOSE:
2085 case LFUN_VIEW_CLOSE:
2086 enable = doc_buffer != nullptr;
2089 case LFUN_BUFFER_CLOSE_ALL:
2090 enable = theBufferList().last() != theBufferList().first();
2093 case LFUN_BUFFER_CHKTEX: {
2094 // hide if we have no checktex command
2095 if (lyxrc.chktex_command.empty()) {
2096 flag.setUnknown(true);
2100 if (!doc_buffer || !doc_buffer->params().isLatex()
2101 || d.processing_thread_watcher_.isRunning()) {
2102 // grey out, don't hide
2110 case LFUN_VIEW_SPLIT:
2111 if (cmd.getArg(0) == "vertical")
2112 enable = doc_buffer && (d.splitter_->count() == 1 ||
2113 d.splitter_->orientation() == Qt::Vertical);
2115 enable = doc_buffer && (d.splitter_->count() == 1 ||
2116 d.splitter_->orientation() == Qt::Horizontal);
2119 case LFUN_TAB_GROUP_CLOSE:
2120 enable = d.tabWorkAreaCount() > 1;
2123 case LFUN_DEVEL_MODE_TOGGLE:
2124 flag.setOnOff(devel_mode_);
2127 case LFUN_TOOLBAR_TOGGLE: {
2128 string const name = cmd.getArg(0);
2129 if (GuiToolbar * t = toolbar(name))
2130 flag.setOnOff(t->isVisible());
2133 docstring const msg =
2134 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2140 case LFUN_TOOLBAR_MOVABLE: {
2141 string const name = cmd.getArg(0);
2142 // use negation since locked == !movable
2144 // toolbar name * locks all toolbars
2145 flag.setOnOff(!toolbarsMovable_);
2146 else if (GuiToolbar * t = toolbar(name))
2147 flag.setOnOff(!(t->isMovable()));
2150 docstring const msg =
2151 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2157 case LFUN_ICON_SIZE:
2158 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2161 case LFUN_DROP_LAYOUTS_CHOICE:
2162 enable = buf != nullptr;
2165 case LFUN_UI_TOGGLE:
2166 flag.setOnOff(isFullScreen());
2169 case LFUN_DIALOG_DISCONNECT_INSET:
2172 case LFUN_DIALOG_HIDE:
2173 // FIXME: should we check if the dialog is shown?
2176 case LFUN_DIALOG_TOGGLE:
2177 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2180 case LFUN_DIALOG_SHOW: {
2181 string const name = cmd.getArg(0);
2183 enable = name == "aboutlyx"
2184 || name == "file" //FIXME: should be removed.
2185 || name == "lyxfiles"
2187 || name == "texinfo"
2188 || name == "progress"
2189 || name == "compare";
2190 else if (name == "character" || name == "symbols"
2191 || name == "mathdelimiter" || name == "mathmatrix") {
2192 if (!buf || buf->isReadonly())
2195 Cursor const & cur = currentBufferView()->cursor();
2196 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2199 else if (name == "latexlog")
2200 enable = FileName(doc_buffer->logName()).isReadableFile();
2201 else if (name == "spellchecker")
2202 enable = theSpellChecker()
2203 && !doc_buffer->isReadonly()
2204 && !doc_buffer->text().empty();
2205 else if (name == "vclog")
2206 enable = doc_buffer->lyxvc().inUse();
2210 case LFUN_DIALOG_UPDATE: {
2211 string const name = cmd.getArg(0);
2213 enable = name == "prefs";
2217 case LFUN_COMMAND_EXECUTE:
2219 case LFUN_MENU_OPEN:
2220 // Nothing to check.
2223 case LFUN_COMPLETION_INLINE:
2224 if (!d.current_work_area_
2225 || !d.current_work_area_->completer().inlinePossible(
2226 currentBufferView()->cursor()))
2230 case LFUN_COMPLETION_POPUP:
2231 if (!d.current_work_area_
2232 || !d.current_work_area_->completer().popupPossible(
2233 currentBufferView()->cursor()))
2238 if (!d.current_work_area_
2239 || !d.current_work_area_->completer().inlinePossible(
2240 currentBufferView()->cursor()))
2244 case LFUN_COMPLETION_ACCEPT:
2245 if (!d.current_work_area_
2246 || (!d.current_work_area_->completer().popupVisible()
2247 && !d.current_work_area_->completer().inlineVisible()
2248 && !d.current_work_area_->completer().completionAvailable()))
2252 case LFUN_COMPLETION_CANCEL:
2253 if (!d.current_work_area_
2254 || (!d.current_work_area_->completer().popupVisible()
2255 && !d.current_work_area_->completer().inlineVisible()))
2259 case LFUN_BUFFER_ZOOM_OUT:
2260 case LFUN_BUFFER_ZOOM_IN: {
2261 // only diff between these two is that the default for ZOOM_OUT
2263 bool const neg_zoom =
2264 convert<int>(cmd.argument()) < 0 ||
2265 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2266 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2267 docstring const msg =
2268 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2272 enable = doc_buffer;
2276 case LFUN_BUFFER_ZOOM: {
2277 bool const less_than_min_zoom =
2278 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2279 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2280 docstring const msg =
2281 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2286 enable = doc_buffer;
2290 case LFUN_BUFFER_MOVE_NEXT:
2291 case LFUN_BUFFER_MOVE_PREVIOUS:
2292 // we do not cycle when moving
2293 case LFUN_BUFFER_NEXT:
2294 case LFUN_BUFFER_PREVIOUS:
2295 // because we cycle, it doesn't matter whether on first or last
2296 enable = (d.currentTabWorkArea()->count() > 1);
2298 case LFUN_BUFFER_SWITCH:
2299 // toggle on the current buffer, but do not toggle off
2300 // the other ones (is that a good idea?)
2302 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2303 flag.setOnOff(true);
2306 case LFUN_VC_REGISTER:
2307 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2309 case LFUN_VC_RENAME:
2310 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2313 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2315 case LFUN_VC_CHECK_IN:
2316 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2318 case LFUN_VC_CHECK_OUT:
2319 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2321 case LFUN_VC_LOCKING_TOGGLE:
2322 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2323 && doc_buffer->lyxvc().lockingToggleEnabled();
2324 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2326 case LFUN_VC_REVERT:
2327 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2328 && !doc_buffer->hasReadonlyFlag();
2330 case LFUN_VC_UNDO_LAST:
2331 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2333 case LFUN_VC_REPO_UPDATE:
2334 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2336 case LFUN_VC_COMMAND: {
2337 if (cmd.argument().empty())
2339 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2343 case LFUN_VC_COMPARE:
2344 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2347 case LFUN_SERVER_GOTO_FILE_ROW:
2348 case LFUN_LYX_ACTIVATE:
2349 case LFUN_WINDOW_RAISE:
2351 case LFUN_FORWARD_SEARCH:
2352 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2355 case LFUN_FILE_INSERT_PLAINTEXT:
2356 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2357 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2360 case LFUN_SPELLING_CONTINUOUSLY:
2361 flag.setOnOff(lyxrc.spellcheck_continuously);
2364 case LFUN_CITATION_OPEN:
2373 flag.setEnabled(false);
2379 static FileName selectTemplateFile()
2381 FileDialog dlg(qt_("Select template file"));
2382 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2383 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2385 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2386 QStringList(qt_("LyX Documents (*.lyx)")));
2388 if (result.first == FileDialog::Later)
2390 if (result.second.isEmpty())
2392 return FileName(fromqstr(result.second));
2396 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2400 Buffer * newBuffer = nullptr;
2402 newBuffer = checkAndLoadLyXFile(filename);
2403 } catch (ExceptionMessage const &) {
2410 message(_("Document not loaded."));
2414 setBuffer(newBuffer);
2415 newBuffer->errors("Parse");
2418 theSession().lastFiles().add(filename);
2419 theSession().writeFile();
2426 void GuiView::openDocument(string const & fname)
2428 string initpath = lyxrc.document_path;
2430 if (documentBufferView()) {
2431 string const trypath = documentBufferView()->buffer().filePath();
2432 // If directory is writeable, use this as default.
2433 if (FileName(trypath).isDirWritable())
2439 if (fname.empty()) {
2440 FileDialog dlg(qt_("Select document to open"));
2441 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2442 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2444 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2445 FileDialog::Result result =
2446 dlg.open(toqstr(initpath), filter);
2448 if (result.first == FileDialog::Later)
2451 filename = fromqstr(result.second);
2453 // check selected filename
2454 if (filename.empty()) {
2455 message(_("Canceled."));
2461 // get absolute path of file and add ".lyx" to the filename if
2463 FileName const fullname =
2464 fileSearch(string(), filename, "lyx", support::may_not_exist);
2465 if (!fullname.empty())
2466 filename = fullname.absFileName();
2468 if (!fullname.onlyPath().isDirectory()) {
2469 Alert::warning(_("Invalid filename"),
2470 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2471 from_utf8(fullname.absFileName())));
2475 // if the file doesn't exist and isn't already open (bug 6645),
2476 // let the user create one
2477 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2478 !LyXVC::file_not_found_hook(fullname)) {
2479 // the user specifically chose this name. Believe him.
2480 Buffer * const b = newFile(filename, string(), true);
2486 docstring const disp_fn = makeDisplayPath(filename);
2487 message(bformat(_("Opening document %1$s..."), disp_fn));
2490 Buffer * buf = loadDocument(fullname);
2492 str2 = bformat(_("Document %1$s opened."), disp_fn);
2493 if (buf->lyxvc().inUse())
2494 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2495 " " + _("Version control detected.");
2497 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2502 // FIXME: clean that
2503 static bool import(GuiView * lv, FileName const & filename,
2504 string const & format, ErrorList & errorList)
2506 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2508 string loader_format;
2509 vector<string> loaders = theConverters().loaders();
2510 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2511 vector<string>::const_iterator it = loaders.begin();
2512 vector<string>::const_iterator en = loaders.end();
2513 for (; it != en; ++it) {
2514 if (!theConverters().isReachable(format, *it))
2517 string const tofile =
2518 support::changeExtension(filename.absFileName(),
2519 theFormats().extension(*it));
2520 if (theConverters().convert(nullptr, filename, FileName(tofile),
2521 filename, format, *it, errorList) != Converters::SUCCESS)
2523 loader_format = *it;
2526 if (loader_format.empty()) {
2527 frontend::Alert::error(_("Couldn't import file"),
2528 bformat(_("No information for importing the format %1$s."),
2529 translateIfPossible(theFormats().prettyName(format))));
2533 loader_format = format;
2535 if (loader_format == "lyx") {
2536 Buffer * buf = lv->loadDocument(lyxfile);
2540 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2544 bool as_paragraphs = loader_format == "textparagraph";
2545 string filename2 = (loader_format == format) ? filename.absFileName()
2546 : support::changeExtension(filename.absFileName(),
2547 theFormats().extension(loader_format));
2548 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2550 guiApp->setCurrentView(lv);
2551 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2558 void GuiView::importDocument(string const & argument)
2561 string filename = split(argument, format, ' ');
2563 LYXERR(Debug::INFO, format << " file: " << filename);
2565 // need user interaction
2566 if (filename.empty()) {
2567 string initpath = lyxrc.document_path;
2568 if (documentBufferView()) {
2569 string const trypath = documentBufferView()->buffer().filePath();
2570 // If directory is writeable, use this as default.
2571 if (FileName(trypath).isDirWritable())
2575 docstring const text = bformat(_("Select %1$s file to import"),
2576 translateIfPossible(theFormats().prettyName(format)));
2578 FileDialog dlg(toqstr(text));
2579 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2580 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2582 docstring filter = translateIfPossible(theFormats().prettyName(format));
2585 filter += from_utf8(theFormats().extensions(format));
2588 FileDialog::Result result =
2589 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2591 if (result.first == FileDialog::Later)
2594 filename = fromqstr(result.second);
2596 // check selected filename
2597 if (filename.empty())
2598 message(_("Canceled."));
2601 if (filename.empty())
2604 // get absolute path of file
2605 FileName const fullname(support::makeAbsPath(filename));
2607 // Can happen if the user entered a path into the dialog
2609 if (fullname.onlyFileName().empty()) {
2610 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2611 "Aborting import."),
2612 from_utf8(fullname.absFileName()));
2613 frontend::Alert::error(_("File name error"), msg);
2614 message(_("Canceled."));
2619 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2621 // Check if the document already is open
2622 Buffer * buf = theBufferList().getBuffer(lyxfile);
2625 if (!closeBuffer()) {
2626 message(_("Canceled."));
2631 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2633 // if the file exists already, and we didn't do
2634 // -i lyx thefile.lyx, warn
2635 if (lyxfile.exists() && fullname != lyxfile) {
2637 docstring text = bformat(_("The document %1$s already exists.\n\n"
2638 "Do you want to overwrite that document?"), displaypath);
2639 int const ret = Alert::prompt(_("Overwrite document?"),
2640 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2643 message(_("Canceled."));
2648 message(bformat(_("Importing %1$s..."), displaypath));
2649 ErrorList errorList;
2650 if (import(this, fullname, format, errorList))
2651 message(_("imported."));
2653 message(_("file not imported!"));
2655 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2659 void GuiView::newDocument(string const & filename, string templatefile,
2662 FileName initpath(lyxrc.document_path);
2663 if (documentBufferView()) {
2664 FileName const trypath(documentBufferView()->buffer().filePath());
2665 // If directory is writeable, use this as default.
2666 if (trypath.isDirWritable())
2670 if (from_template) {
2671 if (templatefile.empty())
2672 templatefile = selectTemplateFile().absFileName();
2673 if (templatefile.empty())
2678 if (filename.empty())
2679 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2681 b = newFile(filename, templatefile, true);
2686 // If no new document could be created, it is unsure
2687 // whether there is a valid BufferView.
2688 if (currentBufferView())
2689 // Ensure the cursor is correctly positioned on screen.
2690 currentBufferView()->showCursor();
2694 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2696 BufferView * bv = documentBufferView();
2701 FileName filename(to_utf8(fname));
2702 if (filename.empty()) {
2703 // Launch a file browser
2705 string initpath = lyxrc.document_path;
2706 string const trypath = bv->buffer().filePath();
2707 // If directory is writeable, use this as default.
2708 if (FileName(trypath).isDirWritable())
2712 FileDialog dlg(qt_("Select LyX document to insert"));
2713 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2714 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2716 FileDialog::Result result = dlg.open(toqstr(initpath),
2717 QStringList(qt_("LyX Documents (*.lyx)")));
2719 if (result.first == FileDialog::Later)
2723 filename.set(fromqstr(result.second));
2725 // check selected filename
2726 if (filename.empty()) {
2727 // emit message signal.
2728 message(_("Canceled."));
2733 bv->insertLyXFile(filename, ignorelang);
2734 bv->buffer().errors("Parse");
2738 string const GuiView::getTemplatesPath(Buffer & b)
2740 // We start off with the user's templates path
2741 string result = addPath(package().user_support().absFileName(), "templates");
2742 // Check for the document language
2743 string const langcode = b.params().language->code();
2744 string const shortcode = langcode.substr(0, 2);
2745 if (!langcode.empty() && shortcode != "en") {
2746 string subpath = addPath(result, shortcode);
2747 string subpath_long = addPath(result, langcode);
2748 // If we have a subdirectory for the language already,
2750 FileName sp = FileName(subpath);
2751 if (sp.isDirectory())
2753 else if (FileName(subpath_long).isDirectory())
2754 result = subpath_long;
2756 // Ask whether we should create such a subdirectory
2757 docstring const text =
2758 bformat(_("It is suggested to save the template in a subdirectory\n"
2759 "appropriate to the document language (%1$s).\n"
2760 "This subdirectory does not exists yet.\n"
2761 "Do you want to create it?"),
2762 _(b.params().language->display()));
2763 if (Alert::prompt(_("Create Language Directory?"),
2764 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2765 // If the user agreed, we try to create it and report if this failed.
2766 if (!sp.createDirectory(0777))
2767 Alert::error(_("Subdirectory creation failed!"),
2768 _("Could not create subdirectory.\n"
2769 "The template will be saved in the parent directory."));
2775 // Do we have a layout category?
2776 string const cat = b.params().baseClass() ?
2777 b.params().baseClass()->category()
2780 string subpath = addPath(result, cat);
2781 // If we have a subdirectory for the category already,
2783 FileName sp = FileName(subpath);
2784 if (sp.isDirectory())
2787 // Ask whether we should create such a subdirectory
2788 docstring const text =
2789 bformat(_("It is suggested to save the template in a subdirectory\n"
2790 "appropriate to the layout category (%1$s).\n"
2791 "This subdirectory does not exists yet.\n"
2792 "Do you want to create it?"),
2794 if (Alert::prompt(_("Create Category Directory?"),
2795 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2796 // If the user agreed, we try to create it and report if this failed.
2797 if (!sp.createDirectory(0777))
2798 Alert::error(_("Subdirectory creation failed!"),
2799 _("Could not create subdirectory.\n"
2800 "The template will be saved in the parent directory."));
2810 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2812 FileName fname = b.fileName();
2813 FileName const oldname = fname;
2814 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2816 if (!newname.empty()) {
2819 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2821 fname = support::makeAbsPath(to_utf8(newname),
2822 oldname.onlyPath().absFileName());
2824 // Switch to this Buffer.
2827 // No argument? Ask user through dialog.
2829 QString const title = as_template ? qt_("Choose a filename to save template as")
2830 : qt_("Choose a filename to save document as");
2831 FileDialog dlg(title);
2832 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2833 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2835 if (!isLyXFileName(fname.absFileName()))
2836 fname.changeExtension(".lyx");
2838 string const path = as_template ?
2840 : fname.onlyPath().absFileName();
2841 FileDialog::Result result =
2842 dlg.save(toqstr(path),
2843 QStringList(qt_("LyX Documents (*.lyx)")),
2844 toqstr(fname.onlyFileName()));
2846 if (result.first == FileDialog::Later)
2849 fname.set(fromqstr(result.second));
2854 if (!isLyXFileName(fname.absFileName()))
2855 fname.changeExtension(".lyx");
2858 // fname is now the new Buffer location.
2860 // if there is already a Buffer open with this name, we do not want
2861 // to have another one. (the second test makes sure we're not just
2862 // trying to overwrite ourselves, which is fine.)
2863 if (theBufferList().exists(fname) && fname != oldname
2864 && theBufferList().getBuffer(fname) != &b) {
2865 docstring const text =
2866 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2867 "Please close it before attempting to overwrite it.\n"
2868 "Do you want to choose a new filename?"),
2869 from_utf8(fname.absFileName()));
2870 int const ret = Alert::prompt(_("Chosen File Already Open"),
2871 text, 0, 1, _("&Rename"), _("&Cancel"));
2873 case 0: return renameBuffer(b, docstring(), kind);
2874 case 1: return false;
2879 bool const existsLocal = fname.exists();
2880 bool const existsInVC = LyXVC::fileInVC(fname);
2881 if (existsLocal || existsInVC) {
2882 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2883 if (kind != LV_WRITE_AS && existsInVC) {
2884 // renaming to a name that is already in VC
2886 docstring text = bformat(_("The document %1$s "
2887 "is already registered.\n\n"
2888 "Do you want to choose a new name?"),
2890 docstring const title = (kind == LV_VC_RENAME) ?
2891 _("Rename document?") : _("Copy document?");
2892 docstring const button = (kind == LV_VC_RENAME) ?
2893 _("&Rename") : _("&Copy");
2894 int const ret = Alert::prompt(title, text, 0, 1,
2895 button, _("&Cancel"));
2897 case 0: return renameBuffer(b, docstring(), kind);
2898 case 1: return false;
2903 docstring text = bformat(_("The document %1$s "
2904 "already exists.\n\n"
2905 "Do you want to overwrite that document?"),
2907 int const ret = Alert::prompt(_("Overwrite document?"),
2908 text, 0, 2, _("&Overwrite"),
2909 _("&Rename"), _("&Cancel"));
2912 case 1: return renameBuffer(b, docstring(), kind);
2913 case 2: return false;
2919 case LV_VC_RENAME: {
2920 string msg = b.lyxvc().rename(fname);
2923 message(from_utf8(msg));
2927 string msg = b.lyxvc().copy(fname);
2930 message(from_utf8(msg));
2934 case LV_WRITE_AS_TEMPLATE:
2937 // LyXVC created the file already in case of LV_VC_RENAME or
2938 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2939 // relative paths of included stuff right if we moved e.g. from
2940 // /a/b.lyx to /a/c/b.lyx.
2942 bool const saved = saveBuffer(b, fname);
2949 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2951 FileName fname = b.fileName();
2953 FileDialog dlg(qt_("Choose a filename to export the document as"));
2954 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2957 QString const anyformat = qt_("Guess from extension (*.*)");
2960 vector<Format const *> export_formats;
2961 for (Format const & f : theFormats())
2962 if (f.documentFormat())
2963 export_formats.push_back(&f);
2964 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2965 map<QString, string> fmap;
2968 for (Format const * f : export_formats) {
2969 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2970 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2972 from_ascii(f->extension())));
2973 types << loc_filter;
2974 fmap[loc_filter] = f->name();
2975 if (from_ascii(f->name()) == iformat) {
2976 filter = loc_filter;
2977 ext = f->extension();
2980 string ofname = fname.onlyFileName();
2982 ofname = support::changeExtension(ofname, ext);
2983 FileDialog::Result result =
2984 dlg.save(toqstr(fname.onlyPath().absFileName()),
2988 if (result.first != FileDialog::Chosen)
2992 fname.set(fromqstr(result.second));
2993 if (filter == anyformat)
2994 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2996 fmt_name = fmap[filter];
2997 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2998 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3000 if (fmt_name.empty() || fname.empty())
3003 // fname is now the new Buffer location.
3004 if (FileName(fname).exists()) {
3005 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3006 docstring text = bformat(_("The document %1$s already "
3007 "exists.\n\nDo you want to "
3008 "overwrite that document?"),
3010 int const ret = Alert::prompt(_("Overwrite document?"),
3011 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3014 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3015 case 2: return false;
3019 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3022 return dr.dispatched();
3026 bool GuiView::saveBuffer(Buffer & b)
3028 return saveBuffer(b, FileName());
3032 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3034 if (workArea(b) && workArea(b)->inDialogMode())
3037 if (fn.empty() && b.isUnnamed())
3038 return renameBuffer(b, docstring());
3040 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3042 theSession().lastFiles().add(b.fileName());
3043 theSession().writeFile();
3047 // Switch to this Buffer.
3050 // FIXME: we don't tell the user *WHY* the save failed !!
3051 docstring const file = makeDisplayPath(b.absFileName(), 30);
3052 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3053 "Do you want to rename the document and "
3054 "try again?"), file);
3055 int const ret = Alert::prompt(_("Rename and save?"),
3056 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3059 if (!renameBuffer(b, docstring()))
3068 return saveBuffer(b, fn);
3072 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3074 return closeWorkArea(wa, false);
3078 // We only want to close the buffer if it is not visible in other workareas
3079 // of the same view, nor in other views, and if this is not a child
3080 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3082 Buffer & buf = wa->bufferView().buffer();
3084 bool last_wa = d.countWorkAreasOf(buf) == 1
3085 && !inOtherView(buf) && !buf.parent();
3087 bool close_buffer = last_wa;
3090 if (lyxrc.close_buffer_with_last_view == "yes")
3092 else if (lyxrc.close_buffer_with_last_view == "no")
3093 close_buffer = false;
3096 if (buf.isUnnamed())
3097 file = from_utf8(buf.fileName().onlyFileName());
3099 file = buf.fileName().displayName(30);
3100 docstring const text = bformat(
3101 _("Last view on document %1$s is being closed.\n"
3102 "Would you like to close or hide the document?\n"
3104 "Hidden documents can be displayed back through\n"
3105 "the menu: View->Hidden->...\n"
3107 "To remove this question, set your preference in:\n"
3108 " Tools->Preferences->Look&Feel->UserInterface\n"
3110 int ret = Alert::prompt(_("Close or hide document?"),
3111 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3114 close_buffer = (ret == 0);
3118 return closeWorkArea(wa, close_buffer);
3122 bool GuiView::closeBuffer()
3124 GuiWorkArea * wa = currentMainWorkArea();
3125 // coverity complained about this
3126 // it seems unnecessary, but perhaps is worth the check
3127 LASSERT(wa, return false);
3129 setCurrentWorkArea(wa);
3130 Buffer & buf = wa->bufferView().buffer();
3131 return closeWorkArea(wa, !buf.parent());
3135 void GuiView::writeSession() const {
3136 GuiWorkArea const * active_wa = currentMainWorkArea();
3137 for (int i = 0; i < d.splitter_->count(); ++i) {
3138 TabWorkArea * twa = d.tabWorkArea(i);
3139 for (int j = 0; j < twa->count(); ++j) {
3140 GuiWorkArea * wa = twa->workArea(j);
3141 Buffer & buf = wa->bufferView().buffer();
3142 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3148 bool GuiView::closeBufferAll()
3151 for (auto & buf : theBufferList()) {
3152 if (!saveBufferIfNeeded(*buf, false)) {
3153 // Closing has been cancelled, so abort.
3158 // Close the workareas in all other views
3159 QList<int> const ids = guiApp->viewIds();
3160 for (int i = 0; i != ids.size(); ++i) {
3161 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3165 // Close our own workareas
3166 if (!closeWorkAreaAll())
3173 bool GuiView::closeWorkAreaAll()
3175 setCurrentWorkArea(currentMainWorkArea());
3177 // We might be in a situation that there is still a tabWorkArea, but
3178 // there are no tabs anymore. This can happen when we get here after a
3179 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3180 // many TabWorkArea's have no documents anymore.
3183 // We have to call count() each time, because it can happen that
3184 // more than one splitter will disappear in one iteration (bug 5998).
3185 while (d.splitter_->count() > empty_twa) {
3186 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3188 if (twa->count() == 0)
3191 setCurrentWorkArea(twa->currentWorkArea());
3192 if (!closeTabWorkArea(twa))
3200 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3205 Buffer & buf = wa->bufferView().buffer();
3207 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3208 Alert::warning(_("Close document"),
3209 _("Document could not be closed because it is being processed by LyX."));
3214 return closeBuffer(buf);
3216 if (!inMultiTabs(wa))
3217 if (!saveBufferIfNeeded(buf, true))
3225 bool GuiView::closeBuffer(Buffer & buf)
3227 bool success = true;
3228 ListOfBuffers clist = buf.getChildren();
3229 ListOfBuffers::const_iterator it = clist.begin();
3230 ListOfBuffers::const_iterator const bend = clist.end();
3231 for (; it != bend; ++it) {
3232 Buffer * child_buf = *it;
3233 if (theBufferList().isOthersChild(&buf, child_buf)) {
3234 child_buf->setParent(nullptr);
3238 // FIXME: should we look in other tabworkareas?
3239 // ANSWER: I don't think so. I've tested, and if the child is
3240 // open in some other window, it closes without a problem.
3241 GuiWorkArea * child_wa = workArea(*child_buf);
3244 // If we are in a close_event all children will be closed in some time,
3245 // so no need to do it here. This will ensure that the children end up
3246 // in the session file in the correct order. If we close the master
3247 // buffer, we can close or release the child buffers here too.
3249 success = closeWorkArea(child_wa, true);
3253 // In this case the child buffer is open but hidden.
3254 // Even in this case, children can be dirty (e.g.,
3255 // after a label change in the master, see #11405).
3256 // Therefore, check this
3257 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3258 // If we are in a close_event all children will be closed in some time,
3259 // so no need to do it here. This will ensure that the children end up
3260 // in the session file in the correct order. If we close the master
3261 // buffer, we can close or release the child buffers here too.
3264 // Save dirty buffers also if closing_!
3265 if (saveBufferIfNeeded(*child_buf, false)) {
3266 child_buf->removeAutosaveFile();
3267 theBufferList().release(child_buf);
3269 // Saving of dirty children has been cancelled.
3270 // Cancel the whole process.
3277 // goto bookmark to update bookmark pit.
3278 // FIXME: we should update only the bookmarks related to this buffer!
3279 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3280 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3281 guiApp->gotoBookmark(i + 1, false, false);
3283 if (saveBufferIfNeeded(buf, false)) {
3284 buf.removeAutosaveFile();
3285 theBufferList().release(&buf);
3289 // open all children again to avoid a crash because of dangling
3290 // pointers (bug 6603)
3296 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3298 while (twa == d.currentTabWorkArea()) {
3299 twa->setCurrentIndex(twa->count() - 1);
3301 GuiWorkArea * wa = twa->currentWorkArea();
3302 Buffer & b = wa->bufferView().buffer();
3304 // We only want to close the buffer if the same buffer is not visible
3305 // in another view, and if this is not a child and if we are closing
3306 // a view (not a tabgroup).
3307 bool const close_buffer =
3308 !inOtherView(b) && !b.parent() && closing_;
3310 if (!closeWorkArea(wa, close_buffer))
3317 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3319 if (buf.isClean() || buf.paragraphs().empty())
3322 // Switch to this Buffer.
3328 if (buf.isUnnamed()) {
3329 file = from_utf8(buf.fileName().onlyFileName());
3332 FileName filename = buf.fileName();
3334 file = filename.displayName(30);
3335 exists = filename.exists();
3338 // Bring this window to top before asking questions.
3343 if (hiding && buf.isUnnamed()) {
3344 docstring const text = bformat(_("The document %1$s has not been "
3345 "saved yet.\n\nDo you want to save "
3346 "the document?"), file);
3347 ret = Alert::prompt(_("Save new document?"),
3348 text, 0, 1, _("&Save"), _("&Cancel"));
3352 docstring const text = exists ?
3353 bformat(_("The document %1$s has unsaved changes."
3354 "\n\nDo you want to save the document or "
3355 "discard the changes?"), file) :
3356 bformat(_("The document %1$s has not been saved yet."
3357 "\n\nDo you want to save the document or "
3358 "discard it entirely?"), file);
3359 docstring const title = exists ?
3360 _("Save changed document?") : _("Save document?");
3361 ret = Alert::prompt(title, text, 0, 2,
3362 _("&Save"), _("&Discard"), _("&Cancel"));
3367 if (!saveBuffer(buf))
3371 // If we crash after this we could have no autosave file
3372 // but I guess this is really improbable (Jug).
3373 // Sometimes improbable things happen:
3374 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3375 // buf.removeAutosaveFile();
3377 // revert all changes
3388 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3390 Buffer & buf = wa->bufferView().buffer();
3392 for (int i = 0; i != d.splitter_->count(); ++i) {
3393 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3394 if (wa_ && wa_ != wa)
3397 return inOtherView(buf);
3401 bool GuiView::inOtherView(Buffer & buf)
3403 QList<int> const ids = guiApp->viewIds();
3405 for (int i = 0; i != ids.size(); ++i) {
3409 if (guiApp->view(ids[i]).workArea(buf))
3416 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3418 if (!documentBufferView())
3421 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3422 Buffer * const curbuf = &documentBufferView()->buffer();
3423 int nwa = twa->count();
3424 for (int i = 0; i < nwa; ++i) {
3425 if (&workArea(i)->bufferView().buffer() == curbuf) {
3427 if (np == NEXTBUFFER)
3428 next_index = (i == nwa - 1 ? 0 : i + 1);
3430 next_index = (i == 0 ? nwa - 1 : i - 1);
3432 twa->moveTab(i, next_index);
3434 setBuffer(&workArea(next_index)->bufferView().buffer());
3442 /// make sure the document is saved
3443 static bool ensureBufferClean(Buffer * buffer)
3445 LASSERT(buffer, return false);
3446 if (buffer->isClean() && !buffer->isUnnamed())
3449 docstring const file = buffer->fileName().displayName(30);
3452 if (!buffer->isUnnamed()) {
3453 text = bformat(_("The document %1$s has unsaved "
3454 "changes.\n\nDo you want to save "
3455 "the document?"), file);
3456 title = _("Save changed document?");
3459 text = bformat(_("The document %1$s has not been "
3460 "saved yet.\n\nDo you want to save "
3461 "the document?"), file);
3462 title = _("Save new document?");
3464 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3467 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3469 return buffer->isClean() && !buffer->isUnnamed();
3473 bool GuiView::reloadBuffer(Buffer & buf)
3475 currentBufferView()->cursor().reset();
3476 Buffer::ReadStatus status = buf.reload();
3477 return status == Buffer::ReadSuccess;
3481 void GuiView::checkExternallyModifiedBuffers()
3483 BufferList::iterator bit = theBufferList().begin();
3484 BufferList::iterator const bend = theBufferList().end();
3485 for (; bit != bend; ++bit) {
3486 Buffer * buf = *bit;
3487 if (buf->fileName().exists() && buf->isChecksumModified()) {
3488 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3489 " Reload now? Any local changes will be lost."),
3490 from_utf8(buf->absFileName()));
3491 int const ret = Alert::prompt(_("Reload externally changed document?"),
3492 text, 0, 1, _("&Reload"), _("&Cancel"));
3500 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3502 Buffer * buffer = documentBufferView()
3503 ? &(documentBufferView()->buffer()) : nullptr;
3505 switch (cmd.action()) {
3506 case LFUN_VC_REGISTER:
3507 if (!buffer || !ensureBufferClean(buffer))
3509 if (!buffer->lyxvc().inUse()) {
3510 if (buffer->lyxvc().registrer()) {
3511 reloadBuffer(*buffer);
3512 dr.clearMessageUpdate();
3517 case LFUN_VC_RENAME:
3518 case LFUN_VC_COPY: {
3519 if (!buffer || !ensureBufferClean(buffer))
3521 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3522 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3523 // Some changes are not yet committed.
3524 // We test here and not in getStatus(), since
3525 // this test is expensive.
3527 LyXVC::CommandResult ret =
3528 buffer->lyxvc().checkIn(log);
3530 if (ret == LyXVC::ErrorCommand ||
3531 ret == LyXVC::VCSuccess)
3532 reloadBuffer(*buffer);
3533 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3534 frontend::Alert::error(
3535 _("Revision control error."),
3536 _("Document could not be checked in."));
3540 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3541 LV_VC_RENAME : LV_VC_COPY;
3542 renameBuffer(*buffer, cmd.argument(), kind);
3547 case LFUN_VC_CHECK_IN:
3548 if (!buffer || !ensureBufferClean(buffer))
3550 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3552 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3554 // Only skip reloading if the checkin was cancelled or
3555 // an error occurred before the real checkin VCS command
3556 // was executed, since the VCS might have changed the
3557 // file even if it could not checkin successfully.
3558 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3559 reloadBuffer(*buffer);
3563 case LFUN_VC_CHECK_OUT:
3564 if (!buffer || !ensureBufferClean(buffer))
3566 if (buffer->lyxvc().inUse()) {
3567 dr.setMessage(buffer->lyxvc().checkOut());
3568 reloadBuffer(*buffer);
3572 case LFUN_VC_LOCKING_TOGGLE:
3573 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3575 if (buffer->lyxvc().inUse()) {
3576 string res = buffer->lyxvc().lockingToggle();
3578 frontend::Alert::error(_("Revision control error."),
3579 _("Error when setting the locking property."));
3582 reloadBuffer(*buffer);
3587 case LFUN_VC_REVERT:
3590 if (buffer->lyxvc().revert()) {
3591 reloadBuffer(*buffer);
3592 dr.clearMessageUpdate();
3596 case LFUN_VC_UNDO_LAST:
3599 buffer->lyxvc().undoLast();
3600 reloadBuffer(*buffer);
3601 dr.clearMessageUpdate();
3604 case LFUN_VC_REPO_UPDATE:
3607 if (ensureBufferClean(buffer)) {
3608 dr.setMessage(buffer->lyxvc().repoUpdate());
3609 checkExternallyModifiedBuffers();
3613 case LFUN_VC_COMMAND: {
3614 string flag = cmd.getArg(0);
3615 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3618 if (contains(flag, 'M')) {
3619 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3622 string path = cmd.getArg(1);
3623 if (contains(path, "$$p") && buffer)
3624 path = subst(path, "$$p", buffer->filePath());
3625 LYXERR(Debug::LYXVC, "Directory: " << path);
3627 if (!pp.isReadableDirectory()) {
3628 lyxerr << _("Directory is not accessible.") << endl;
3631 support::PathChanger p(pp);
3633 string command = cmd.getArg(2);
3634 if (command.empty())
3637 command = subst(command, "$$i", buffer->absFileName());
3638 command = subst(command, "$$p", buffer->filePath());
3640 command = subst(command, "$$m", to_utf8(message));
3641 LYXERR(Debug::LYXVC, "Command: " << command);
3643 one.startscript(Systemcall::Wait, command);
3647 if (contains(flag, 'I'))
3648 buffer->markDirty();
3649 if (contains(flag, 'R'))
3650 reloadBuffer(*buffer);
3655 case LFUN_VC_COMPARE: {
3656 if (cmd.argument().empty()) {
3657 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3663 string rev1 = cmd.getArg(0);
3667 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3670 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3671 f2 = buffer->absFileName();
3673 string rev2 = cmd.getArg(1);
3677 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3681 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3682 f1 << "\n" << f2 << "\n" );
3683 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3684 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3694 void GuiView::openChildDocument(string const & fname)
3696 LASSERT(documentBufferView(), return);
3697 Buffer & buffer = documentBufferView()->buffer();
3698 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3699 documentBufferView()->saveBookmark(false);
3700 Buffer * child = nullptr;
3701 if (theBufferList().exists(filename)) {
3702 child = theBufferList().getBuffer(filename);
3705 message(bformat(_("Opening child document %1$s..."),
3706 makeDisplayPath(filename.absFileName())));
3707 child = loadDocument(filename, false);
3709 // Set the parent name of the child document.
3710 // This makes insertion of citations and references in the child work,
3711 // when the target is in the parent or another child document.
3713 child->setParent(&buffer);
3717 bool GuiView::goToFileRow(string const & argument)
3721 size_t i = argument.find_last_of(' ');
3722 if (i != string::npos) {
3723 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3724 istringstream is(argument.substr(i + 1));
3729 if (i == string::npos) {
3730 LYXERR0("Wrong argument: " << argument);
3733 Buffer * buf = nullptr;
3734 string const realtmp = package().temp_dir().realPath();
3735 // We have to use os::path_prefix_is() here, instead of
3736 // simply prefixIs(), because the file name comes from
3737 // an external application and may need case adjustment.
3738 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3739 buf = theBufferList().getBufferFromTmp(file_name, true);
3740 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3741 << (buf ? " success" : " failed"));
3743 // Must replace extension of the file to be .lyx
3744 // and get full path
3745 FileName const s = fileSearch(string(),
3746 support::changeExtension(file_name, ".lyx"), "lyx");
3747 // Either change buffer or load the file
3748 if (theBufferList().exists(s))
3749 buf = theBufferList().getBuffer(s);
3750 else if (s.exists()) {
3751 buf = loadDocument(s);
3756 _("File does not exist: %1$s"),
3757 makeDisplayPath(file_name)));
3763 _("No buffer for file: %1$s."),
3764 makeDisplayPath(file_name))
3769 bool success = documentBufferView()->setCursorFromRow(row);
3771 LYXERR(Debug::LATEX,
3772 "setCursorFromRow: invalid position for row " << row);
3773 frontend::Alert::error(_("Inverse Search Failed"),
3774 _("Invalid position requested by inverse search.\n"
3775 "You may need to update the viewed document."));
3781 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3783 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3784 menu->exec(QCursor::pos());
3789 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3790 Buffer const * orig, Buffer * clone, string const & format)
3792 Buffer::ExportStatus const status = func(format);
3794 // the cloning operation will have produced a clone of the entire set of
3795 // documents, starting from the master. so we must delete those.
3796 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3798 busyBuffers.remove(orig);
3803 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3804 Buffer const * orig, Buffer * clone, string const & format)
3806 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3808 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3812 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3813 Buffer const * orig, Buffer * clone, string const & format)
3815 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3817 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3821 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3822 Buffer const * orig, Buffer * clone, string const & format)
3824 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3826 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3830 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3831 string const & argument,
3832 Buffer const * used_buffer,
3833 docstring const & msg,
3834 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3835 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3836 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3842 string format = argument;
3844 format = used_buffer->params().getDefaultOutputFormat();
3845 processing_format = format;
3847 progress_->clearMessages();
3850 #if EXPORT_in_THREAD
3852 GuiViewPrivate::busyBuffers.insert(used_buffer);
3853 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3854 if (!cloned_buffer) {
3855 Alert::error(_("Export Error"),
3856 _("Error cloning the Buffer."));
3859 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3864 setPreviewFuture(f);
3865 last_export_format = used_buffer->params().bufferFormat();
3868 // We are asynchronous, so we don't know here anything about the success
3871 Buffer::ExportStatus status;
3873 status = (used_buffer->*syncFunc)(format, false);
3874 } else if (previewFunc) {
3875 status = (used_buffer->*previewFunc)(format);
3878 handleExportStatus(gv_, status, format);
3880 return (status == Buffer::ExportSuccess
3881 || status == Buffer::PreviewSuccess);
3885 Buffer::ExportStatus status;
3887 status = (used_buffer->*syncFunc)(format, true);
3888 } else if (previewFunc) {
3889 status = (used_buffer->*previewFunc)(format);
3892 handleExportStatus(gv_, status, format);
3894 return (status == Buffer::ExportSuccess
3895 || status == Buffer::PreviewSuccess);
3899 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3901 BufferView * bv = currentBufferView();
3902 LASSERT(bv, return);
3904 // Let the current BufferView dispatch its own actions.
3905 bv->dispatch(cmd, dr);
3906 if (dr.dispatched()) {
3907 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3908 updateDialog("document", "");
3912 // Try with the document BufferView dispatch if any.
3913 BufferView * doc_bv = documentBufferView();
3914 if (doc_bv && doc_bv != bv) {
3915 doc_bv->dispatch(cmd, dr);
3916 if (dr.dispatched()) {
3917 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3918 updateDialog("document", "");
3923 // Then let the current Cursor dispatch its own actions.
3924 bv->cursor().dispatch(cmd);
3926 // update completion. We do it here and not in
3927 // processKeySym to avoid another redraw just for a
3928 // changed inline completion
3929 if (cmd.origin() == FuncRequest::KEYBOARD) {
3930 if (cmd.action() == LFUN_SELF_INSERT
3931 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3932 updateCompletion(bv->cursor(), true, true);
3933 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3934 updateCompletion(bv->cursor(), false, true);
3936 updateCompletion(bv->cursor(), false, false);
3939 dr = bv->cursor().result();
3943 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3945 BufferView * bv = currentBufferView();
3946 // By default we won't need any update.
3947 dr.screenUpdate(Update::None);
3948 // assume cmd will be dispatched
3949 dr.dispatched(true);
3951 Buffer * doc_buffer = documentBufferView()
3952 ? &(documentBufferView()->buffer()) : nullptr;
3954 if (cmd.origin() == FuncRequest::TOC) {
3955 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3956 toc->doDispatch(bv->cursor(), cmd, dr);
3960 string const argument = to_utf8(cmd.argument());
3962 switch(cmd.action()) {
3963 case LFUN_BUFFER_CHILD_OPEN:
3964 openChildDocument(to_utf8(cmd.argument()));
3967 case LFUN_BUFFER_IMPORT:
3968 importDocument(to_utf8(cmd.argument()));
3971 case LFUN_MASTER_BUFFER_EXPORT:
3973 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3975 case LFUN_BUFFER_EXPORT: {
3978 // GCC only sees strfwd.h when building merged
3979 if (::lyx::operator==(cmd.argument(), "custom")) {
3980 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3981 // so the following test should not be needed.
3982 // In principle, we could try to switch to such a view...
3983 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3984 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3988 string const dest = cmd.getArg(1);
3989 FileName target_dir;
3990 if (!dest.empty() && FileName::isAbsolute(dest))
3991 target_dir = FileName(support::onlyPath(dest));
3993 target_dir = doc_buffer->fileName().onlyPath();
3995 string const format = (argument.empty() || argument == "default") ?
3996 doc_buffer->params().getDefaultOutputFormat() : argument;
3998 if ((dest.empty() && doc_buffer->isUnnamed())
3999 || !target_dir.isDirWritable()) {
4000 exportBufferAs(*doc_buffer, from_utf8(format));
4003 /* TODO/Review: Is it a problem to also export the children?
4004 See the update_unincluded flag */
4005 d.asyncBufferProcessing(format,
4008 &GuiViewPrivate::exportAndDestroy,
4010 nullptr, cmd.allowAsync());
4011 // TODO Inform user about success
4015 case LFUN_BUFFER_EXPORT_AS: {
4016 LASSERT(doc_buffer, break);
4017 docstring f = cmd.argument();
4019 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4020 exportBufferAs(*doc_buffer, f);
4024 case LFUN_BUFFER_UPDATE: {
4025 d.asyncBufferProcessing(argument,
4028 &GuiViewPrivate::compileAndDestroy,
4030 nullptr, cmd.allowAsync());
4033 case LFUN_BUFFER_VIEW: {
4034 d.asyncBufferProcessing(argument,
4036 _("Previewing ..."),
4037 &GuiViewPrivate::previewAndDestroy,
4039 &Buffer::preview, cmd.allowAsync());
4042 case LFUN_MASTER_BUFFER_UPDATE: {
4043 d.asyncBufferProcessing(argument,
4044 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4046 &GuiViewPrivate::compileAndDestroy,
4048 nullptr, cmd.allowAsync());
4051 case LFUN_MASTER_BUFFER_VIEW: {
4052 d.asyncBufferProcessing(argument,
4053 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4055 &GuiViewPrivate::previewAndDestroy,
4056 nullptr, &Buffer::preview, cmd.allowAsync());
4059 case LFUN_EXPORT_CANCEL: {
4060 Systemcall::killscript();
4063 case LFUN_BUFFER_SWITCH: {
4064 string const file_name = to_utf8(cmd.argument());
4065 if (!FileName::isAbsolute(file_name)) {
4067 dr.setMessage(_("Absolute filename expected."));
4071 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4074 dr.setMessage(_("Document not loaded"));
4078 // Do we open or switch to the buffer in this view ?
4079 if (workArea(*buffer)
4080 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4085 // Look for the buffer in other views
4086 QList<int> const ids = guiApp->viewIds();
4088 for (; i != ids.size(); ++i) {
4089 GuiView & gv = guiApp->view(ids[i]);
4090 if (gv.workArea(*buffer)) {
4092 gv.activateWindow();
4094 gv.setBuffer(buffer);
4099 // If necessary, open a new window as a last resort
4100 if (i == ids.size()) {
4101 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4107 case LFUN_BUFFER_NEXT:
4108 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4111 case LFUN_BUFFER_MOVE_NEXT:
4112 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4115 case LFUN_BUFFER_PREVIOUS:
4116 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4119 case LFUN_BUFFER_MOVE_PREVIOUS:
4120 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4123 case LFUN_BUFFER_CHKTEX:
4124 LASSERT(doc_buffer, break);
4125 doc_buffer->runChktex();
4128 case LFUN_COMMAND_EXECUTE: {
4129 command_execute_ = true;
4130 minibuffer_focus_ = true;
4133 case LFUN_DROP_LAYOUTS_CHOICE:
4134 d.layout_->showPopup();
4137 case LFUN_MENU_OPEN:
4138 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4139 menu->exec(QCursor::pos());
4142 case LFUN_FILE_INSERT: {
4143 if (cmd.getArg(1) == "ignorelang")
4144 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4146 insertLyXFile(cmd.argument());
4150 case LFUN_FILE_INSERT_PLAINTEXT:
4151 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4152 string const fname = to_utf8(cmd.argument());
4153 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4154 dr.setMessage(_("Absolute filename expected."));
4158 FileName filename(fname);
4159 if (fname.empty()) {
4160 FileDialog dlg(qt_("Select file to insert"));
4162 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4163 QStringList(qt_("All Files (*)")));
4165 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4166 dr.setMessage(_("Canceled."));
4170 filename.set(fromqstr(result.second));
4174 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4175 bv->dispatch(new_cmd, dr);
4180 case LFUN_BUFFER_RELOAD: {
4181 LASSERT(doc_buffer, break);
4184 bool drop = (cmd.argument() == "dump");
4187 if (!drop && !doc_buffer->isClean()) {
4188 docstring const file =
4189 makeDisplayPath(doc_buffer->absFileName(), 20);
4190 if (doc_buffer->notifiesExternalModification()) {
4191 docstring text = _("The current version will be lost. "
4192 "Are you sure you want to load the version on disk "
4193 "of the document %1$s?");
4194 ret = Alert::prompt(_("Reload saved document?"),
4195 bformat(text, file), 1, 1,
4196 _("&Reload"), _("&Cancel"));
4198 docstring text = _("Any changes will be lost. "
4199 "Are you sure you want to revert to the saved version "
4200 "of the document %1$s?");
4201 ret = Alert::prompt(_("Revert to saved document?"),
4202 bformat(text, file), 1, 1,
4203 _("&Revert"), _("&Cancel"));
4208 doc_buffer->markClean();
4209 reloadBuffer(*doc_buffer);
4210 dr.forceBufferUpdate();
4215 case LFUN_BUFFER_RESET_EXPORT:
4216 LASSERT(doc_buffer, break);
4217 doc_buffer->requireFreshStart(true);
4218 dr.setMessage(_("Buffer export reset."));
4221 case LFUN_BUFFER_WRITE:
4222 LASSERT(doc_buffer, break);
4223 saveBuffer(*doc_buffer);
4226 case LFUN_BUFFER_WRITE_AS:
4227 LASSERT(doc_buffer, break);
4228 renameBuffer(*doc_buffer, cmd.argument());
4231 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4232 LASSERT(doc_buffer, break);
4233 renameBuffer(*doc_buffer, cmd.argument(),
4234 LV_WRITE_AS_TEMPLATE);
4237 case LFUN_BUFFER_WRITE_ALL: {
4238 Buffer * first = theBufferList().first();
4241 message(_("Saving all documents..."));
4242 // We cannot use a for loop as the buffer list cycles.
4245 if (!b->isClean()) {
4247 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4249 b = theBufferList().next(b);
4250 } while (b != first);
4251 dr.setMessage(_("All documents saved."));
4255 case LFUN_MASTER_BUFFER_FORALL: {
4259 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4260 funcToRun.allowAsync(false);
4262 for (Buffer const * buf : doc_buffer->allRelatives()) {
4263 // Switch to other buffer view and resend cmd
4264 lyx::dispatch(FuncRequest(
4265 LFUN_BUFFER_SWITCH, buf->absFileName()));
4266 lyx::dispatch(funcToRun);
4269 lyx::dispatch(FuncRequest(
4270 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4274 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4275 LASSERT(doc_buffer, break);
4276 doc_buffer->clearExternalModification();
4279 case LFUN_BUFFER_CLOSE:
4283 case LFUN_BUFFER_CLOSE_ALL:
4287 case LFUN_DEVEL_MODE_TOGGLE:
4288 devel_mode_ = !devel_mode_;
4290 dr.setMessage(_("Developer mode is now enabled."));
4292 dr.setMessage(_("Developer mode is now disabled."));
4295 case LFUN_TOOLBAR_TOGGLE: {
4296 string const name = cmd.getArg(0);
4297 if (GuiToolbar * t = toolbar(name))
4302 case LFUN_TOOLBAR_MOVABLE: {
4303 string const name = cmd.getArg(0);
4305 // toggle (all) toolbars movablility
4306 toolbarsMovable_ = !toolbarsMovable_;
4307 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4308 GuiToolbar * tb = toolbar(ti.name);
4309 if (tb && tb->isMovable() != toolbarsMovable_)
4310 // toggle toolbar movablity if it does not fit lock
4311 // (all) toolbars positions state silent = true, since
4312 // status bar notifications are slow
4315 if (toolbarsMovable_)
4316 dr.setMessage(_("Toolbars unlocked."));
4318 dr.setMessage(_("Toolbars locked."));
4319 } else if (GuiToolbar * t = toolbar(name)) {
4320 // toggle current toolbar movablity
4322 // update lock (all) toolbars positions
4323 updateLockToolbars();
4328 case LFUN_ICON_SIZE: {
4329 QSize size = d.iconSize(cmd.argument());
4331 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4332 size.width(), size.height()));
4336 case LFUN_DIALOG_UPDATE: {
4337 string const name = to_utf8(cmd.argument());
4338 if (name == "prefs" || name == "document")
4339 updateDialog(name, string());
4340 else if (name == "paragraph")
4341 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4342 else if (currentBufferView()) {
4343 Inset * inset = currentBufferView()->editedInset(name);
4344 // Can only update a dialog connected to an existing inset
4346 // FIXME: get rid of this indirection; GuiView ask the inset
4347 // if he is kind enough to update itself...
4348 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4349 //FIXME: pass DispatchResult here?
4350 inset->dispatch(currentBufferView()->cursor(), fr);
4356 case LFUN_DIALOG_TOGGLE: {
4357 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4358 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4359 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4363 case LFUN_DIALOG_DISCONNECT_INSET:
4364 disconnectDialog(to_utf8(cmd.argument()));
4367 case LFUN_DIALOG_HIDE: {
4368 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4372 case LFUN_DIALOG_SHOW: {
4373 string const name = cmd.getArg(0);
4374 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4376 if (name == "latexlog") {
4377 // getStatus checks that
4378 LASSERT(doc_buffer, break);
4379 Buffer::LogType type;
4380 string const logfile = doc_buffer->logName(&type);
4382 case Buffer::latexlog:
4385 case Buffer::buildlog:
4386 sdata = "literate ";
4389 sdata += Lexer::quoteString(logfile);
4390 showDialog("log", sdata);
4391 } else if (name == "vclog") {
4392 // getStatus checks that
4393 LASSERT(doc_buffer, break);
4394 string const sdata2 = "vc " +
4395 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4396 showDialog("log", sdata2);
4397 } else if (name == "symbols") {
4398 sdata = bv->cursor().getEncoding()->name();
4400 showDialog("symbols", sdata);
4402 } else if (name == "prefs" && isFullScreen()) {
4403 lfunUiToggle("fullscreen");
4404 showDialog("prefs", sdata);
4406 showDialog(name, sdata);
4411 dr.setMessage(cmd.argument());
4414 case LFUN_UI_TOGGLE: {
4415 string arg = cmd.getArg(0);
4416 if (!lfunUiToggle(arg)) {
4417 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4418 dr.setMessage(bformat(msg, from_utf8(arg)));
4420 // Make sure the keyboard focus stays in the work area.
4425 case LFUN_VIEW_SPLIT: {
4426 LASSERT(doc_buffer, break);
4427 string const orientation = cmd.getArg(0);
4428 d.splitter_->setOrientation(orientation == "vertical"
4429 ? Qt::Vertical : Qt::Horizontal);
4430 TabWorkArea * twa = addTabWorkArea();
4431 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4432 setCurrentWorkArea(wa);
4435 case LFUN_TAB_GROUP_CLOSE:
4436 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4437 closeTabWorkArea(twa);
4438 d.current_work_area_ = nullptr;
4439 twa = d.currentTabWorkArea();
4440 // Switch to the next GuiWorkArea in the found TabWorkArea.
4442 // Make sure the work area is up to date.
4443 setCurrentWorkArea(twa->currentWorkArea());
4445 setCurrentWorkArea(nullptr);
4450 case LFUN_VIEW_CLOSE:
4451 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4452 closeWorkArea(twa->currentWorkArea());
4453 d.current_work_area_ = nullptr;
4454 twa = d.currentTabWorkArea();
4455 // Switch to the next GuiWorkArea in the found TabWorkArea.
4457 // Make sure the work area is up to date.
4458 setCurrentWorkArea(twa->currentWorkArea());
4460 setCurrentWorkArea(nullptr);
4465 case LFUN_COMPLETION_INLINE:
4466 if (d.current_work_area_)
4467 d.current_work_area_->completer().showInline();
4470 case LFUN_COMPLETION_POPUP:
4471 if (d.current_work_area_)
4472 d.current_work_area_->completer().showPopup();
4477 if (d.current_work_area_)
4478 d.current_work_area_->completer().tab();
4481 case LFUN_COMPLETION_CANCEL:
4482 if (d.current_work_area_) {
4483 if (d.current_work_area_->completer().popupVisible())
4484 d.current_work_area_->completer().hidePopup();
4486 d.current_work_area_->completer().hideInline();
4490 case LFUN_COMPLETION_ACCEPT:
4491 if (d.current_work_area_)
4492 d.current_work_area_->completer().activate();
4495 case LFUN_BUFFER_ZOOM_IN:
4496 case LFUN_BUFFER_ZOOM_OUT:
4497 case LFUN_BUFFER_ZOOM: {
4498 if (cmd.argument().empty()) {
4499 if (cmd.action() == LFUN_BUFFER_ZOOM)
4501 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4506 if (cmd.action() == LFUN_BUFFER_ZOOM)
4507 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4508 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4509 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4511 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4514 // Actual zoom value: default zoom + fractional extra value
4515 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4516 if (zoom < static_cast<int>(zoom_min_))
4519 lyxrc.currentZoom = zoom;
4521 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4522 lyxrc.currentZoom, lyxrc.defaultZoom));
4524 guiApp->fontLoader().update();
4525 dr.screenUpdate(Update::Force | Update::FitCursor);
4529 case LFUN_VC_REGISTER:
4530 case LFUN_VC_RENAME:
4532 case LFUN_VC_CHECK_IN:
4533 case LFUN_VC_CHECK_OUT:
4534 case LFUN_VC_REPO_UPDATE:
4535 case LFUN_VC_LOCKING_TOGGLE:
4536 case LFUN_VC_REVERT:
4537 case LFUN_VC_UNDO_LAST:
4538 case LFUN_VC_COMMAND:
4539 case LFUN_VC_COMPARE:
4540 dispatchVC(cmd, dr);
4543 case LFUN_SERVER_GOTO_FILE_ROW:
4544 if(goToFileRow(to_utf8(cmd.argument())))
4545 dr.screenUpdate(Update::Force | Update::FitCursor);
4548 case LFUN_LYX_ACTIVATE:
4552 case LFUN_WINDOW_RAISE:
4558 case LFUN_FORWARD_SEARCH: {
4559 // it seems safe to assume we have a document buffer, since
4560 // getStatus wants one.
4561 LASSERT(doc_buffer, break);
4562 Buffer const * doc_master = doc_buffer->masterBuffer();
4563 FileName const path(doc_master->temppath());
4564 string const texname = doc_master->isChild(doc_buffer)
4565 ? DocFileName(changeExtension(
4566 doc_buffer->absFileName(),
4567 "tex")).mangledFileName()
4568 : doc_buffer->latexName();
4569 string const fulltexname =
4570 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4571 string const mastername =
4572 removeExtension(doc_master->latexName());
4573 FileName const dviname(addName(path.absFileName(),
4574 addExtension(mastername, "dvi")));
4575 FileName const pdfname(addName(path.absFileName(),
4576 addExtension(mastername, "pdf")));
4577 bool const have_dvi = dviname.exists();
4578 bool const have_pdf = pdfname.exists();
4579 if (!have_dvi && !have_pdf) {
4580 dr.setMessage(_("Please, preview the document first."));
4583 string outname = dviname.onlyFileName();
4584 string command = lyxrc.forward_search_dvi;
4585 if (!have_dvi || (have_pdf &&
4586 pdfname.lastModified() > dviname.lastModified())) {
4587 outname = pdfname.onlyFileName();
4588 command = lyxrc.forward_search_pdf;
4591 DocIterator cur = bv->cursor();
4592 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4593 LYXERR(Debug::ACTION, "Forward search: row:" << row
4595 if (row == -1 || command.empty()) {
4596 dr.setMessage(_("Couldn't proceed."));
4599 string texrow = convert<string>(row);
4601 command = subst(command, "$$n", texrow);
4602 command = subst(command, "$$f", fulltexname);
4603 command = subst(command, "$$t", texname);
4604 command = subst(command, "$$o", outname);
4606 volatile PathChanger p(path);
4608 one.startscript(Systemcall::DontWait, command);
4612 case LFUN_SPELLING_CONTINUOUSLY:
4613 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4614 dr.screenUpdate(Update::Force);
4617 case LFUN_CITATION_OPEN: {
4619 if (theFormats().getFormat("pdf"))
4620 pdfv = theFormats().getFormat("pdf")->viewer();
4621 if (theFormats().getFormat("ps"))
4622 psv = theFormats().getFormat("ps")->viewer();
4623 frontend::showTarget(argument, pdfv, psv);
4628 // The LFUN must be for one of BufferView, Buffer or Cursor;
4630 dispatchToBufferView(cmd, dr);
4634 // Part of automatic menu appearance feature.
4635 if (isFullScreen()) {
4636 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4640 // Need to update bv because many LFUNs here might have destroyed it
4641 bv = currentBufferView();
4643 // Clear non-empty selections
4644 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4646 Cursor & cur = bv->cursor();
4647 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4648 cur.clearSelection();
4654 bool GuiView::lfunUiToggle(string const & ui_component)
4656 if (ui_component == "scrollbar") {
4657 // hide() is of no help
4658 if (d.current_work_area_->verticalScrollBarPolicy() ==
4659 Qt::ScrollBarAlwaysOff)
4661 d.current_work_area_->setVerticalScrollBarPolicy(
4662 Qt::ScrollBarAsNeeded);
4664 d.current_work_area_->setVerticalScrollBarPolicy(
4665 Qt::ScrollBarAlwaysOff);
4666 } else if (ui_component == "statusbar") {
4667 statusBar()->setVisible(!statusBar()->isVisible());
4668 } else if (ui_component == "menubar") {
4669 menuBar()->setVisible(!menuBar()->isVisible());
4671 if (ui_component == "frame") {
4672 int const l = contentsMargins().left();
4674 //are the frames in default state?
4675 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4677 setContentsMargins(-2, -2, -2, -2);
4679 setContentsMargins(0, 0, 0, 0);
4682 if (ui_component == "fullscreen") {
4690 void GuiView::toggleFullScreen()
4692 setWindowState(windowState() ^ Qt::WindowFullScreen);
4696 Buffer const * GuiView::updateInset(Inset const * inset)
4701 Buffer const * inset_buffer = &(inset->buffer());
4703 for (int i = 0; i != d.splitter_->count(); ++i) {
4704 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4707 Buffer const * buffer = &(wa->bufferView().buffer());
4708 if (inset_buffer == buffer)
4709 wa->scheduleRedraw(true);
4711 return inset_buffer;
4715 void GuiView::restartCaret()
4717 /* When we move around, or type, it's nice to be able to see
4718 * the caret immediately after the keypress.
4720 if (d.current_work_area_)
4721 d.current_work_area_->startBlinkingCaret();
4723 // Take this occasion to update the other GUI elements.
4729 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4731 if (d.current_work_area_)
4732 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4737 // This list should be kept in sync with the list of insets in
4738 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4739 // dialog should have the same name as the inset.
4740 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4741 // docs in LyXAction.cpp.
4743 char const * const dialognames[] = {
4745 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4746 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4747 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4748 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4749 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4750 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4751 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4752 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4754 char const * const * const end_dialognames =
4755 dialognames + (sizeof(dialognames) / sizeof(char *));
4759 cmpCStr(char const * name) : name_(name) {}
4760 bool operator()(char const * other) {
4761 return strcmp(other, name_) == 0;
4768 bool isValidName(string const & name)
4770 return find_if(dialognames, end_dialognames,
4771 cmpCStr(name.c_str())) != end_dialognames;
4777 void GuiView::resetDialogs()
4779 // Make sure that no LFUN uses any GuiView.
4780 guiApp->setCurrentView(nullptr);
4784 constructToolbars();
4785 guiApp->menus().fillMenuBar(menuBar(), this, false);
4786 d.layout_->updateContents(true);
4787 // Now update controls with current buffer.
4788 guiApp->setCurrentView(this);
4794 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4796 for (QObject * child: widget->children()) {
4797 if (child->inherits("QGroupBox")) {
4798 QGroupBox * box = (QGroupBox*) child;
4801 flatGroupBoxes(child, flag);
4807 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4809 if (!isValidName(name))
4812 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4814 if (it != d.dialogs_.end()) {
4816 it->second->hideView();
4817 return it->second.get();
4820 Dialog * dialog = build(name);
4821 d.dialogs_[name].reset(dialog);
4822 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4823 // Force a uniform style for group boxes
4824 // On Mac non-flat works better, on Linux flat is standard
4825 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4827 if (lyxrc.allow_geometry_session)
4828 dialog->restoreSession();
4835 void GuiView::showDialog(string const & name, string const & sdata,
4838 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4842 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4848 const string name = fromqstr(qname);
4849 const string sdata = fromqstr(qdata);
4853 Dialog * dialog = findOrBuild(name, false);
4855 bool const visible = dialog->isVisibleView();
4856 dialog->showData(sdata);
4857 if (currentBufferView())
4858 currentBufferView()->editInset(name, inset);
4859 // We only set the focus to the new dialog if it was not yet
4860 // visible in order not to change the existing previous behaviour
4862 // activateWindow is needed for floating dockviews
4863 dialog->asQWidget()->raise();
4864 dialog->asQWidget()->activateWindow();
4865 dialog->asQWidget()->setFocus();
4869 catch (ExceptionMessage const &) {
4877 bool GuiView::isDialogVisible(string const & name) const
4879 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4880 if (it == d.dialogs_.end())
4882 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4886 void GuiView::hideDialog(string const & name, Inset * inset)
4888 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4889 if (it == d.dialogs_.end())
4893 if (!currentBufferView())
4895 if (inset != currentBufferView()->editedInset(name))
4899 Dialog * const dialog = it->second.get();
4900 if (dialog->isVisibleView())
4902 if (currentBufferView())
4903 currentBufferView()->editInset(name, nullptr);
4907 void GuiView::disconnectDialog(string const & name)
4909 if (!isValidName(name))
4911 if (currentBufferView())
4912 currentBufferView()->editInset(name, nullptr);
4916 void GuiView::hideAll() const
4918 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4919 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4921 for(; it != end; ++it)
4922 it->second->hideView();
4926 void GuiView::updateDialogs()
4928 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4929 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4931 for(; it != end; ++it) {
4932 Dialog * dialog = it->second.get();
4934 if (dialog->needBufferOpen() && !documentBufferView())
4935 hideDialog(fromqstr(dialog->name()), nullptr);
4936 else if (dialog->isVisibleView())
4937 dialog->checkStatus();
4944 Dialog * createDialog(GuiView & lv, string const & name);
4946 // will be replaced by a proper factory...
4947 Dialog * createGuiAbout(GuiView & lv);
4948 Dialog * createGuiBibtex(GuiView & lv);
4949 Dialog * createGuiChanges(GuiView & lv);
4950 Dialog * createGuiCharacter(GuiView & lv);
4951 Dialog * createGuiCitation(GuiView & lv);
4952 Dialog * createGuiCompare(GuiView & lv);
4953 Dialog * createGuiCompareHistory(GuiView & lv);
4954 Dialog * createGuiDelimiter(GuiView & lv);
4955 Dialog * createGuiDocument(GuiView & lv);
4956 Dialog * createGuiErrorList(GuiView & lv);
4957 Dialog * createGuiExternal(GuiView & lv);
4958 Dialog * createGuiGraphics(GuiView & lv);
4959 Dialog * createGuiInclude(GuiView & lv);
4960 Dialog * createGuiIndex(GuiView & lv);
4961 Dialog * createGuiListings(GuiView & lv);
4962 Dialog * createGuiLog(GuiView & lv);
4963 Dialog * createGuiLyXFiles(GuiView & lv);
4964 Dialog * createGuiMathMatrix(GuiView & lv);
4965 Dialog * createGuiNote(GuiView & lv);
4966 Dialog * createGuiParagraph(GuiView & lv);
4967 Dialog * createGuiPhantom(GuiView & lv);
4968 Dialog * createGuiPreferences(GuiView & lv);
4969 Dialog * createGuiPrint(GuiView & lv);
4970 Dialog * createGuiPrintindex(GuiView & lv);
4971 Dialog * createGuiRef(GuiView & lv);
4972 Dialog * createGuiSearch(GuiView & lv);
4973 Dialog * createGuiSearchAdv(GuiView & lv);
4974 Dialog * createGuiSendTo(GuiView & lv);
4975 Dialog * createGuiShowFile(GuiView & lv);
4976 Dialog * createGuiSpellchecker(GuiView & lv);
4977 Dialog * createGuiSymbols(GuiView & lv);
4978 Dialog * createGuiTabularCreate(GuiView & lv);
4979 Dialog * createGuiTexInfo(GuiView & lv);
4980 Dialog * createGuiToc(GuiView & lv);
4981 Dialog * createGuiThesaurus(GuiView & lv);
4982 Dialog * createGuiViewSource(GuiView & lv);
4983 Dialog * createGuiWrap(GuiView & lv);
4984 Dialog * createGuiProgressView(GuiView & lv);
4988 Dialog * GuiView::build(string const & name)
4990 LASSERT(isValidName(name), return nullptr);
4992 Dialog * dialog = createDialog(*this, name);
4996 if (name == "aboutlyx")
4997 return createGuiAbout(*this);
4998 if (name == "bibtex")
4999 return createGuiBibtex(*this);
5000 if (name == "changes")
5001 return createGuiChanges(*this);
5002 if (name == "character")
5003 return createGuiCharacter(*this);
5004 if (name == "citation")
5005 return createGuiCitation(*this);
5006 if (name == "compare")
5007 return createGuiCompare(*this);
5008 if (name == "comparehistory")
5009 return createGuiCompareHistory(*this);
5010 if (name == "document")
5011 return createGuiDocument(*this);
5012 if (name == "errorlist")
5013 return createGuiErrorList(*this);
5014 if (name == "external")
5015 return createGuiExternal(*this);
5017 return createGuiShowFile(*this);
5018 if (name == "findreplace")
5019 return createGuiSearch(*this);
5020 if (name == "findreplaceadv")
5021 return createGuiSearchAdv(*this);
5022 if (name == "graphics")
5023 return createGuiGraphics(*this);
5024 if (name == "include")
5025 return createGuiInclude(*this);
5026 if (name == "index")
5027 return createGuiIndex(*this);
5028 if (name == "index_print")
5029 return createGuiPrintindex(*this);
5030 if (name == "listings")
5031 return createGuiListings(*this);
5033 return createGuiLog(*this);
5034 if (name == "lyxfiles")
5035 return createGuiLyXFiles(*this);
5036 if (name == "mathdelimiter")
5037 return createGuiDelimiter(*this);
5038 if (name == "mathmatrix")
5039 return createGuiMathMatrix(*this);
5041 return createGuiNote(*this);
5042 if (name == "paragraph")
5043 return createGuiParagraph(*this);
5044 if (name == "phantom")
5045 return createGuiPhantom(*this);
5046 if (name == "prefs")
5047 return createGuiPreferences(*this);
5049 return createGuiRef(*this);
5050 if (name == "sendto")
5051 return createGuiSendTo(*this);
5052 if (name == "spellchecker")
5053 return createGuiSpellchecker(*this);
5054 if (name == "symbols")
5055 return createGuiSymbols(*this);
5056 if (name == "tabularcreate")
5057 return createGuiTabularCreate(*this);
5058 if (name == "texinfo")
5059 return createGuiTexInfo(*this);
5060 if (name == "thesaurus")
5061 return createGuiThesaurus(*this);
5063 return createGuiToc(*this);
5064 if (name == "view-source")
5065 return createGuiViewSource(*this);
5067 return createGuiWrap(*this);
5068 if (name == "progress")
5069 return createGuiProgressView(*this);
5075 SEMenu::SEMenu(QWidget * parent)
5077 QAction * action = addAction(qt_("Disable Shell Escape"));
5078 connect(action, SIGNAL(triggered()),
5079 parent, SLOT(disableShellEscape()));
5082 } // namespace frontend
5085 #include "moc_GuiView.cpp"