3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiClickableLabel.h"
23 #include "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
54 #include "LayoutFile.h"
56 #include "LyXAction.h"
60 #include "Paragraph.h"
61 #include "SpellChecker.h"
68 #include "support/convert.h"
69 #include "support/debug.h"
70 #include "support/ExceptionMessage.h"
71 #include "support/FileName.h"
72 #include "support/gettext.h"
73 #include "support/ForkedCalls.h"
74 #include "support/lassert.h"
75 #include "support/lstrings.h"
76 #include "support/os.h"
77 #include "support/Package.h"
78 #include "support/PathChanger.h"
79 #include "support/Systemcall.h"
80 #include "support/Timeout.h"
81 #include "support/ProgressInterface.h"
84 #include <QApplication>
85 #include <QCloseEvent>
86 #include <QDragEnterEvent>
89 #include <QFutureWatcher>
101 #include <QShowEvent>
103 #include <QStackedWidget>
104 #include <QStatusBar>
105 #include <QSvgRenderer>
106 #include <QtConcurrentRun>
109 #include <QWindowStateChangeEvent>
112 // sync with GuiAlert.cpp
113 #define EXPORT_in_THREAD 1
116 #include "support/bind.h"
120 #ifdef HAVE_SYS_TIME_H
121 # include <sys/time.h>
129 using namespace lyx::support;
133 using support::addExtension;
134 using support::changeExtension;
135 using support::removeExtension;
141 class BackgroundWidget : public QWidget
144 BackgroundWidget(int width, int height)
145 : width_(width), height_(height)
147 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
148 if (!lyxrc.show_banner)
150 /// The text to be written on top of the pixmap
151 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
152 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
153 /// The text to be written on top of the pixmap
154 QString const text = lyx_version ?
155 qt_("version ") + lyx_version : qt_("unknown version");
156 #if QT_VERSION >= 0x050000
157 QString imagedir = "images/";
158 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
159 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
160 if (svgRenderer.isValid()) {
161 splash_ = QPixmap(splashSize());
162 QPainter painter(&splash_);
163 svgRenderer.render(&painter);
164 splash_.setDevicePixelRatio(pixelRatio());
166 splash_ = getPixmap("images/", "banner", "png");
169 splash_ = getPixmap("images/", "banner", "svgz,png");
172 QPainter pain(&splash_);
173 pain.setPen(QColor(0, 0, 0));
174 qreal const fsize = fontSize();
177 qreal locscale = htextsize.toFloat(&ok);
180 QPointF const position = textPosition(false);
181 QPointF const hposition = textPosition(true);
182 QRectF const hrect(hposition, splashSize());
184 "widget pixel ratio: " << pixelRatio() <<
185 " splash pixel ratio: " << splashPixelRatio() <<
186 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
188 // The font used to display the version info
189 font.setStyleHint(QFont::SansSerif);
190 font.setWeight(QFont::Bold);
191 font.setPointSizeF(fsize);
193 pain.drawText(position, text);
194 // The font used to display the version info
195 font.setStyleHint(QFont::SansSerif);
196 font.setWeight(QFont::Normal);
197 font.setPointSizeF(hfsize);
198 // Check how long the logo gets with the current font
199 // and adapt if the font is running wider than what
201 QFontMetrics fm(font);
202 // Split the title into lines to measure the longest line
203 // in the current l7n.
204 QStringList titlesegs = htext.split('\n');
206 int hline = fm.height();
207 QStringList::const_iterator sit;
208 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
209 if (fm.width(*sit) > wline)
210 wline = fm.width(*sit);
212 // The longest line in the reference font (for English)
213 // is 180. Calculate scale factor from that.
214 double const wscale = wline > 0 ? (180.0 / wline) : 1;
215 // Now do the same for the height (necessary for condensed fonts)
216 double const hscale = (34.0 / hline);
217 // take the lower of the two scale factors.
218 double const scale = min(wscale, hscale);
219 // Now rescale. Also consider l7n's offset factor.
220 font.setPointSizeF(hfsize * scale * locscale);
223 pain.drawText(hrect, Qt::AlignLeft, htext);
224 setFocusPolicy(Qt::StrongFocus);
227 void paintEvent(QPaintEvent *) override
229 int const w = width_;
230 int const h = height_;
231 int const x = (width() - w) / 2;
232 int const y = (height() - h) / 2;
234 "widget pixel ratio: " << pixelRatio() <<
235 " splash pixel ratio: " << splashPixelRatio() <<
236 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
238 pain.drawPixmap(x, y, w, h, splash_);
241 void keyPressEvent(QKeyEvent * ev) override
244 setKeySymbol(&sym, ev);
246 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
258 /// Current ratio between physical pixels and device-independent pixels
259 double pixelRatio() const {
260 #if QT_VERSION >= 0x050000
261 return qt_scale_factor * devicePixelRatio();
267 qreal fontSize() const {
268 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
271 QPointF textPosition(bool const heading) const {
272 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
273 : QPointF(width_/2 - 18, height_/2 + 45);
276 QSize splashSize() const {
278 static_cast<unsigned int>(width_ * pixelRatio()),
279 static_cast<unsigned int>(height_ * pixelRatio()));
282 /// Ratio between physical pixels and device-independent pixels of splash image
283 double splashPixelRatio() const {
284 #if QT_VERSION >= 0x050000
285 return splash_.devicePixelRatio();
293 /// Toolbar store providing access to individual toolbars by name.
294 typedef map<string, GuiToolbar *> ToolbarMap;
296 typedef shared_ptr<Dialog> DialogPtr;
301 class GuiView::GuiViewPrivate
304 GuiViewPrivate(GuiViewPrivate const &);
305 void operator=(GuiViewPrivate const &);
307 GuiViewPrivate(GuiView * gv)
308 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
309 layout_(nullptr), autosave_timeout_(5000),
312 // hardcode here the platform specific icon size
313 smallIconSize = 16; // scaling problems
314 normalIconSize = 20; // ok, default if iconsize.png is missing
315 bigIconSize = 26; // better for some math icons
316 hugeIconSize = 32; // better for hires displays
319 // if it exists, use width of iconsize.png as normal size
320 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
321 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
323 QImage image(toqstr(fn.absFileName()));
324 if (image.width() < int(smallIconSize))
325 normalIconSize = smallIconSize;
326 else if (image.width() > int(giantIconSize))
327 normalIconSize = giantIconSize;
329 normalIconSize = image.width();
332 splitter_ = new QSplitter;
333 bg_widget_ = new BackgroundWidget(400, 250);
334 stack_widget_ = new QStackedWidget;
335 stack_widget_->addWidget(bg_widget_);
336 stack_widget_->addWidget(splitter_);
339 // TODO cleanup, remove the singleton, handle multiple Windows?
340 progress_ = ProgressInterface::instance();
341 if (!dynamic_cast<GuiProgress*>(progress_)) {
342 progress_ = new GuiProgress; // TODO who deletes it
343 ProgressInterface::setInstance(progress_);
346 dynamic_cast<GuiProgress*>(progress_),
347 SIGNAL(updateStatusBarMessage(QString const&)),
348 gv, SLOT(updateStatusBarMessage(QString const&)));
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(clearMessageText()),
352 gv, SLOT(clearMessageText()));
359 delete stack_widget_;
364 stack_widget_->setCurrentWidget(bg_widget_);
365 bg_widget_->setUpdatesEnabled(true);
366 bg_widget_->setFocus();
369 int tabWorkAreaCount()
371 return splitter_->count();
374 TabWorkArea * tabWorkArea(int i)
376 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
379 TabWorkArea * currentTabWorkArea()
381 int areas = tabWorkAreaCount();
383 // The first TabWorkArea is always the first one, if any.
384 return tabWorkArea(0);
386 for (int i = 0; i != areas; ++i) {
387 TabWorkArea * twa = tabWorkArea(i);
388 if (current_main_work_area_ == twa->currentWorkArea())
392 // None has the focus so we just take the first one.
393 return tabWorkArea(0);
396 int countWorkAreasOf(Buffer & buf)
398 int areas = tabWorkAreaCount();
400 for (int i = 0; i != areas; ++i) {
401 TabWorkArea * twa = tabWorkArea(i);
402 if (twa->workArea(buf))
408 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
410 if (processing_thread_watcher_.isRunning()) {
411 // we prefer to cancel this preview in order to keep a snappy
415 processing_thread_watcher_.setFuture(f);
418 QSize iconSize(docstring const & icon_size)
421 if (icon_size == "small")
422 size = smallIconSize;
423 else if (icon_size == "normal")
424 size = normalIconSize;
425 else if (icon_size == "big")
427 else if (icon_size == "huge")
429 else if (icon_size == "giant")
430 size = giantIconSize;
432 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
434 if (size < smallIconSize)
435 size = smallIconSize;
437 return QSize(size, size);
440 QSize iconSize(QString const & icon_size)
442 return iconSize(qstring_to_ucs4(icon_size));
445 string & iconSize(QSize const & qsize)
447 LATTEST(qsize.width() == qsize.height());
449 static string icon_size;
451 unsigned int size = qsize.width();
453 if (size < smallIconSize)
454 size = smallIconSize;
456 if (size == smallIconSize)
458 else if (size == normalIconSize)
459 icon_size = "normal";
460 else if (size == bigIconSize)
462 else if (size == hugeIconSize)
464 else if (size == giantIconSize)
467 icon_size = convert<string>(size);
472 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
473 Buffer * buffer, string const & format);
474 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
475 Buffer * buffer, string const & format);
476 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
481 static Buffer::ExportStatus runAndDestroy(const T& func,
482 Buffer const * orig, Buffer * buffer, string const & format);
484 // TODO syncFunc/previewFunc: use bind
485 bool asyncBufferProcessing(string const & argument,
486 Buffer const * used_buffer,
487 docstring const & msg,
488 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
489 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
490 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
493 QVector<GuiWorkArea*> guiWorkAreas();
497 GuiWorkArea * current_work_area_;
498 GuiWorkArea * current_main_work_area_;
499 QSplitter * splitter_;
500 QStackedWidget * stack_widget_;
501 BackgroundWidget * bg_widget_;
503 ToolbarMap toolbars_;
504 ProgressInterface* progress_;
505 /// The main layout box.
507 * \warning Don't Delete! The layout box is actually owned by
508 * whichever toolbar contains it. All the GuiView class needs is a
509 * means of accessing it.
511 * FIXME: replace that with a proper model so that we are not limited
512 * to only one dialog.
517 map<string, DialogPtr> dialogs_;
520 QTimer statusbar_timer_;
521 /// auto-saving of buffers
522 Timeout autosave_timeout_;
525 TocModels toc_models_;
528 QFutureWatcher<docstring> autosave_watcher_;
529 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
531 string last_export_format;
532 string processing_format;
534 static QSet<Buffer const *> busyBuffers;
536 unsigned int smallIconSize;
537 unsigned int normalIconSize;
538 unsigned int bigIconSize;
539 unsigned int hugeIconSize;
540 unsigned int giantIconSize;
542 /// flag against a race condition due to multiclicks, see bug #1119
546 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
549 GuiView::GuiView(int id)
550 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
551 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
554 connect(this, SIGNAL(bufferViewChanged()),
555 this, SLOT(onBufferViewChanged()));
557 // GuiToolbars *must* be initialised before the menu bar.
558 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
561 // set ourself as the current view. This is needed for the menu bar
562 // filling, at least for the static special menu item on Mac. Otherwise
563 // they are greyed out.
564 guiApp->setCurrentView(this);
566 // Fill up the menu bar.
567 guiApp->menus().fillMenuBar(menuBar(), this, true);
569 setCentralWidget(d.stack_widget_);
571 // Start autosave timer
572 if (lyxrc.autosave) {
573 // The connection is closed when this is destroyed.
574 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
575 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
576 d.autosave_timeout_.start();
578 connect(&d.statusbar_timer_, SIGNAL(timeout()),
579 this, SLOT(clearMessage()));
581 // We don't want to keep the window in memory if it is closed.
582 setAttribute(Qt::WA_DeleteOnClose, true);
584 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
585 // QIcon::fromTheme was introduced in Qt 4.6
586 #if (QT_VERSION >= 0x040600)
587 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
588 // since the icon is provided in the application bundle. We use a themed
589 // version when available and use the bundled one as fallback.
590 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
592 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
598 // use tabbed dock area for multiple docks
599 // (such as "source" and "messages")
600 setDockOptions(QMainWindow::ForceTabbedDocks);
603 setAcceptDrops(true);
605 // add busy indicator to statusbar
606 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
607 statusBar()->addPermanentWidget(busylabel);
608 search_mode mode = theGuiApp()->imageSearchMode();
609 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
610 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
611 busylabel->setMovie(busyanim);
615 connect(&d.processing_thread_watcher_, SIGNAL(started()),
616 busylabel, SLOT(show()));
617 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
618 busylabel, SLOT(hide()));
619 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
621 QFontMetrics const fm(statusBar()->fontMetrics());
622 int const iconheight = max(int(d.normalIconSize), fm.height());
623 QSize const iconsize(iconheight, iconheight);
625 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
626 shell_escape_ = new QLabel(statusBar());
627 shell_escape_->setPixmap(shellescape);
628 shell_escape_->setScaledContents(true);
629 shell_escape_->setAlignment(Qt::AlignCenter);
630 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
631 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
632 "external commands for this document. "
633 "Right click to change."));
634 SEMenu * menu = new SEMenu(this);
635 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
636 menu, SLOT(showMenu(QPoint)));
637 shell_escape_->hide();
638 statusBar()->addPermanentWidget(shell_escape_);
640 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
641 read_only_ = new QLabel(statusBar());
642 read_only_->setPixmap(readonly);
643 read_only_->setScaledContents(true);
644 read_only_->setAlignment(Qt::AlignCenter);
646 statusBar()->addPermanentWidget(read_only_);
648 version_control_ = new QLabel(statusBar());
649 version_control_->setAlignment(Qt::AlignCenter);
650 version_control_->setFrameStyle(QFrame::StyledPanel);
651 version_control_->hide();
652 statusBar()->addPermanentWidget(version_control_);
654 statusBar()->setSizeGripEnabled(true);
657 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
658 SLOT(autoSaveThreadFinished()));
660 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
661 SLOT(processingThreadStarted()));
662 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
663 SLOT(processingThreadFinished()));
665 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
666 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
668 // set custom application bars context menu, e.g. tool bar and menu bar
669 setContextMenuPolicy(Qt::CustomContextMenu);
670 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
671 SLOT(toolBarPopup(const QPoint &)));
673 // Forbid too small unresizable window because it can happen
674 // with some window manager under X11.
675 setMinimumSize(300, 200);
677 if (lyxrc.allow_geometry_session) {
678 // Now take care of session management.
683 // no session handling, default to a sane size.
684 setGeometry(50, 50, 690, 510);
687 // clear session data if any.
689 settings.remove("views");
699 void GuiView::disableShellEscape()
701 BufferView * bv = documentBufferView();
704 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
705 bv->buffer().params().shell_escape = false;
706 bv->processUpdateFlags(Update::Force);
710 void GuiView::checkCancelBackground()
712 docstring const ttl = _("Cancel Export?");
713 docstring const msg = _("Do you want to cancel the background export process?");
715 Alert::prompt(ttl, msg, 1, 1,
716 _("&Cancel export"), _("Co&ntinue"));
718 Systemcall::killscript();
722 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
724 QVector<GuiWorkArea*> areas;
725 for (int i = 0; i < tabWorkAreaCount(); i++) {
726 TabWorkArea* ta = tabWorkArea(i);
727 for (int u = 0; u < ta->count(); u++) {
728 areas << ta->workArea(u);
734 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
735 string const & format)
737 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
740 case Buffer::ExportSuccess:
741 msg = bformat(_("Successful export to format: %1$s"), fmt);
743 case Buffer::ExportCancel:
744 msg = _("Document export cancelled.");
746 case Buffer::ExportError:
747 case Buffer::ExportNoPathToFormat:
748 case Buffer::ExportTexPathHasSpaces:
749 case Buffer::ExportConverterError:
750 msg = bformat(_("Error while exporting format: %1$s"), fmt);
752 case Buffer::PreviewSuccess:
753 msg = bformat(_("Successful preview of format: %1$s"), fmt);
755 case Buffer::PreviewError:
756 msg = bformat(_("Error while previewing format: %1$s"), fmt);
758 case Buffer::ExportKilled:
759 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
766 void GuiView::processingThreadStarted()
771 void GuiView::processingThreadFinished()
773 QFutureWatcher<Buffer::ExportStatus> const * watcher =
774 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
776 Buffer::ExportStatus const status = watcher->result();
777 handleExportStatus(this, status, d.processing_format);
780 BufferView const * const bv = currentBufferView();
781 if (bv && !bv->buffer().errorList("Export").empty()) {
786 bool const error = (status != Buffer::ExportSuccess &&
787 status != Buffer::PreviewSuccess &&
788 status != Buffer::ExportCancel);
790 ErrorList & el = bv->buffer().errorList(d.last_export_format);
791 // at this point, we do not know if buffer-view or
792 // master-buffer-view was called. If there was an export error,
793 // and the current buffer's error log is empty, we guess that
794 // it must be master-buffer-view that was called so we set
796 errors(d.last_export_format, el.empty());
801 void GuiView::autoSaveThreadFinished()
803 QFutureWatcher<docstring> const * watcher =
804 static_cast<QFutureWatcher<docstring> const *>(sender());
805 message(watcher->result());
810 void GuiView::saveLayout() const
813 settings.setValue("zoom_ratio", zoom_ratio_);
814 settings.setValue("devel_mode", devel_mode_);
815 settings.beginGroup("views");
816 settings.beginGroup(QString::number(id_));
817 #if defined(Q_WS_X11) || defined(QPA_XCB)
818 settings.setValue("pos", pos());
819 settings.setValue("size", size());
821 settings.setValue("geometry", saveGeometry());
823 settings.setValue("layout", saveState(0));
824 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
828 void GuiView::saveUISettings() const
832 // Save the toolbar private states
833 ToolbarMap::iterator end = d.toolbars_.end();
834 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
835 it->second->saveSession(settings);
836 // Now take care of all other dialogs
837 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
838 for (; it!= d.dialogs_.end(); ++it)
839 it->second->saveSession(settings);
843 bool GuiView::restoreLayout()
846 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
847 // Actual zoom value: default zoom + fractional offset
848 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
849 if (zoom < static_cast<int>(zoom_min_))
851 lyxrc.currentZoom = zoom;
852 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
853 settings.beginGroup("views");
854 settings.beginGroup(QString::number(id_));
855 QString const icon_key = "icon_size";
856 if (!settings.contains(icon_key))
859 //code below is skipped when when ~/.config/LyX is (re)created
860 setIconSize(d.iconSize(settings.value(icon_key).toString()));
862 #if defined(Q_WS_X11) || defined(QPA_XCB)
863 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
864 QSize size = settings.value("size", QSize(690, 510)).toSize();
868 // Work-around for bug #6034: the window ends up in an undetermined
869 // state when trying to restore a maximized window when it is
870 // already maximized.
871 if (!(windowState() & Qt::WindowMaximized))
872 if (!restoreGeometry(settings.value("geometry").toByteArray()))
873 setGeometry(50, 50, 690, 510);
875 // Make sure layout is correctly oriented.
876 setLayoutDirection(qApp->layoutDirection());
878 // Allow the toc and view-source dock widget to be restored if needed.
880 if ((dialog = findOrBuild("toc", true)))
881 // see bug 5082. At least setup title and enabled state.
882 // Visibility will be adjusted by restoreState below.
883 dialog->prepareView();
884 if ((dialog = findOrBuild("view-source", true)))
885 dialog->prepareView();
886 if ((dialog = findOrBuild("progress", true)))
887 dialog->prepareView();
889 if (!restoreState(settings.value("layout").toByteArray(), 0))
892 // init the toolbars that have not been restored
893 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
894 Toolbars::Infos::iterator end = guiApp->toolbars().end();
895 for (; cit != end; ++cit) {
896 GuiToolbar * tb = toolbar(cit->name);
897 if (tb && !tb->isRestored())
898 initToolbar(cit->name);
901 // update lock (all) toolbars positions
902 updateLockToolbars();
909 GuiToolbar * GuiView::toolbar(string const & name)
911 ToolbarMap::iterator it = d.toolbars_.find(name);
912 if (it != d.toolbars_.end())
915 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
920 void GuiView::updateLockToolbars()
922 toolbarsMovable_ = false;
923 for (ToolbarInfo const & info : guiApp->toolbars()) {
924 GuiToolbar * tb = toolbar(info.name);
925 if (tb && tb->isMovable())
926 toolbarsMovable_ = true;
931 void GuiView::constructToolbars()
933 ToolbarMap::iterator it = d.toolbars_.begin();
934 for (; it != d.toolbars_.end(); ++it)
938 // I don't like doing this here, but the standard toolbar
939 // destroys this object when it's destroyed itself (vfr)
940 d.layout_ = new LayoutBox(*this);
941 d.stack_widget_->addWidget(d.layout_);
942 d.layout_->move(0,0);
944 // extracts the toolbars from the backend
945 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
946 Toolbars::Infos::iterator end = guiApp->toolbars().end();
947 for (; cit != end; ++cit)
948 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
952 void GuiView::initToolbars()
954 // extracts the toolbars from the backend
955 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
956 Toolbars::Infos::iterator end = guiApp->toolbars().end();
957 for (; cit != end; ++cit)
958 initToolbar(cit->name);
962 void GuiView::initToolbar(string const & name)
964 GuiToolbar * tb = toolbar(name);
967 int const visibility = guiApp->toolbars().defaultVisibility(name);
968 bool newline = !(visibility & Toolbars::SAMEROW);
969 tb->setVisible(false);
970 tb->setVisibility(visibility);
972 if (visibility & Toolbars::TOP) {
974 addToolBarBreak(Qt::TopToolBarArea);
975 addToolBar(Qt::TopToolBarArea, tb);
978 if (visibility & Toolbars::BOTTOM) {
980 addToolBarBreak(Qt::BottomToolBarArea);
981 addToolBar(Qt::BottomToolBarArea, tb);
984 if (visibility & Toolbars::LEFT) {
986 addToolBarBreak(Qt::LeftToolBarArea);
987 addToolBar(Qt::LeftToolBarArea, tb);
990 if (visibility & Toolbars::RIGHT) {
992 addToolBarBreak(Qt::RightToolBarArea);
993 addToolBar(Qt::RightToolBarArea, tb);
996 if (visibility & Toolbars::ON)
997 tb->setVisible(true);
999 tb->setMovable(true);
1003 TocModels & GuiView::tocModels()
1005 return d.toc_models_;
1009 void GuiView::setFocus()
1011 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1012 QMainWindow::setFocus();
1016 bool GuiView::hasFocus() const
1018 if (currentWorkArea())
1019 return currentWorkArea()->hasFocus();
1020 if (currentMainWorkArea())
1021 return currentMainWorkArea()->hasFocus();
1022 return d.bg_widget_->hasFocus();
1026 void GuiView::focusInEvent(QFocusEvent * e)
1028 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1029 QMainWindow::focusInEvent(e);
1030 // Make sure guiApp points to the correct view.
1031 guiApp->setCurrentView(this);
1032 if (currentWorkArea())
1033 currentWorkArea()->setFocus();
1034 else if (currentMainWorkArea())
1035 currentMainWorkArea()->setFocus();
1037 d.bg_widget_->setFocus();
1041 void GuiView::showEvent(QShowEvent * e)
1043 LYXERR(Debug::GUI, "Passed Geometry "
1044 << size().height() << "x" << size().width()
1045 << "+" << pos().x() << "+" << pos().y());
1047 if (d.splitter_->count() == 0)
1048 // No work area, switch to the background widget.
1052 QMainWindow::showEvent(e);
1056 bool GuiView::closeScheduled()
1063 bool GuiView::prepareAllBuffersForLogout()
1065 Buffer * first = theBufferList().first();
1069 // First, iterate over all buffers and ask the users if unsaved
1070 // changes should be saved.
1071 // We cannot use a for loop as the buffer list cycles.
1074 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1076 b = theBufferList().next(b);
1077 } while (b != first);
1079 // Next, save session state
1080 // When a view/window was closed before without quitting LyX, there
1081 // are already entries in the lastOpened list.
1082 theSession().lastOpened().clear();
1089 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1090 ** is responsibility of the container (e.g., dialog)
1092 void GuiView::closeEvent(QCloseEvent * close_event)
1094 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1096 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1097 Alert::warning(_("Exit LyX"),
1098 _("LyX could not be closed because documents are being processed by LyX."));
1099 close_event->setAccepted(false);
1103 // If the user pressed the x (so we didn't call closeView
1104 // programmatically), we want to clear all existing entries.
1106 theSession().lastOpened().clear();
1111 // it can happen that this event arrives without selecting the view,
1112 // e.g. when clicking the close button on a background window.
1114 if (!closeWorkAreaAll()) {
1116 close_event->ignore();
1120 // Make sure that nothing will use this to be closed View.
1121 guiApp->unregisterView(this);
1123 if (isFullScreen()) {
1124 // Switch off fullscreen before closing.
1129 // Make sure the timer time out will not trigger a statusbar update.
1130 d.statusbar_timer_.stop();
1132 // Saving fullscreen requires additional tweaks in the toolbar code.
1133 // It wouldn't also work under linux natively.
1134 if (lyxrc.allow_geometry_session) {
1139 close_event->accept();
1143 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1145 if (event->mimeData()->hasUrls())
1147 /// \todo Ask lyx-devel is this is enough:
1148 /// if (event->mimeData()->hasFormat("text/plain"))
1149 /// event->acceptProposedAction();
1153 void GuiView::dropEvent(QDropEvent * event)
1155 QList<QUrl> files = event->mimeData()->urls();
1156 if (files.isEmpty())
1159 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1160 for (int i = 0; i != files.size(); ++i) {
1161 string const file = os::internal_path(fromqstr(
1162 files.at(i).toLocalFile()));
1166 string const ext = support::getExtension(file);
1167 vector<const Format *> found_formats;
1169 // Find all formats that have the correct extension.
1170 vector<const Format *> const & import_formats
1171 = theConverters().importableFormats();
1172 vector<const Format *>::const_iterator it = import_formats.begin();
1173 for (; it != import_formats.end(); ++it)
1174 if ((*it)->hasExtension(ext))
1175 found_formats.push_back(*it);
1178 if (!found_formats.empty()) {
1179 if (found_formats.size() > 1) {
1180 //FIXME: show a dialog to choose the correct importable format
1181 LYXERR(Debug::FILES,
1182 "Multiple importable formats found, selecting first");
1184 string const arg = found_formats[0]->name() + " " + file;
1185 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1188 //FIXME: do we have to explicitly check whether it's a lyx file?
1189 LYXERR(Debug::FILES,
1190 "No formats found, trying to open it as a lyx file");
1191 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1193 // add the functions to the queue
1194 guiApp->addToFuncRequestQueue(cmd);
1197 // now process the collected functions. We perform the events
1198 // asynchronously. This prevents potential problems in case the
1199 // BufferView is closed within an event.
1200 guiApp->processFuncRequestQueueAsync();
1204 void GuiView::message(docstring const & str)
1206 if (ForkedProcess::iAmAChild())
1209 // call is moved to GUI-thread by GuiProgress
1210 d.progress_->appendMessage(toqstr(str));
1214 void GuiView::clearMessageText()
1216 message(docstring());
1220 void GuiView::updateStatusBarMessage(QString const & str)
1222 statusBar()->showMessage(str);
1223 d.statusbar_timer_.stop();
1224 d.statusbar_timer_.start(3000);
1228 void GuiView::clearMessage()
1230 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1231 // the hasFocus function mostly returns false, even if the focus is on
1232 // a workarea in this view.
1236 d.statusbar_timer_.stop();
1240 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1242 if (wa != d.current_work_area_
1243 || wa->bufferView().buffer().isInternal())
1245 Buffer const & buf = wa->bufferView().buffer();
1246 // Set the windows title
1247 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1248 if (buf.notifiesExternalModification()) {
1249 title = bformat(_("%1$s (modified externally)"), title);
1250 // If the external modification status has changed, then maybe the status of
1251 // buffer-save has changed too.
1255 title += from_ascii(" - LyX");
1257 setWindowTitle(toqstr(title));
1258 // Sets the path for the window: this is used by OSX to
1259 // allow a context click on the title bar showing a menu
1260 // with the path up to the file
1261 setWindowFilePath(toqstr(buf.absFileName()));
1262 // Tell Qt whether the current document is changed
1263 setWindowModified(!buf.isClean());
1265 if (buf.params().shell_escape)
1266 shell_escape_->show();
1268 shell_escape_->hide();
1270 if (buf.hasReadonlyFlag())
1275 if (buf.lyxvc().inUse()) {
1276 version_control_->show();
1277 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1279 version_control_->hide();
1283 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1285 if (d.current_work_area_)
1286 // disconnect the current work area from all slots
1287 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1289 disconnectBufferView();
1290 connectBufferView(wa->bufferView());
1291 connectBuffer(wa->bufferView().buffer());
1292 d.current_work_area_ = wa;
1293 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1294 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1295 QObject::connect(wa, SIGNAL(busy(bool)),
1296 this, SLOT(setBusy(bool)));
1297 // connection of a signal to a signal
1298 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1299 this, SIGNAL(bufferViewChanged()));
1300 Q_EMIT updateWindowTitle(wa);
1301 Q_EMIT bufferViewChanged();
1305 void GuiView::onBufferViewChanged()
1308 // Buffer-dependent dialogs must be updated. This is done here because
1309 // some dialogs require buffer()->text.
1314 void GuiView::on_lastWorkAreaRemoved()
1317 // We already are in a close event. Nothing more to do.
1320 if (d.splitter_->count() > 1)
1321 // We have a splitter so don't close anything.
1324 // Reset and updates the dialogs.
1325 Q_EMIT bufferViewChanged();
1330 if (lyxrc.open_buffers_in_tabs)
1331 // Nothing more to do, the window should stay open.
1334 if (guiApp->viewIds().size() > 1) {
1340 // On Mac we also close the last window because the application stay
1341 // resident in memory. On other platforms we don't close the last
1342 // window because this would quit the application.
1348 void GuiView::updateStatusBar()
1350 // let the user see the explicit message
1351 if (d.statusbar_timer_.isActive())
1358 void GuiView::showMessage()
1362 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1363 if (msg.isEmpty()) {
1364 BufferView const * bv = currentBufferView();
1366 msg = toqstr(bv->cursor().currentState(devel_mode_));
1368 msg = qt_("Welcome to LyX!");
1370 statusBar()->showMessage(msg);
1374 bool GuiView::event(QEvent * e)
1378 // Useful debug code:
1379 //case QEvent::ActivationChange:
1380 //case QEvent::WindowDeactivate:
1381 //case QEvent::Paint:
1382 //case QEvent::Enter:
1383 //case QEvent::Leave:
1384 //case QEvent::HoverEnter:
1385 //case QEvent::HoverLeave:
1386 //case QEvent::HoverMove:
1387 //case QEvent::StatusTip:
1388 //case QEvent::DragEnter:
1389 //case QEvent::DragLeave:
1390 //case QEvent::Drop:
1393 case QEvent::WindowStateChange: {
1394 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1395 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1396 bool result = QMainWindow::event(e);
1397 bool nfstate = (windowState() & Qt::WindowFullScreen);
1398 if (!ofstate && nfstate) {
1399 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1400 // switch to full-screen state
1401 if (lyxrc.full_screen_statusbar)
1402 statusBar()->hide();
1403 if (lyxrc.full_screen_menubar)
1405 if (lyxrc.full_screen_toolbars) {
1406 ToolbarMap::iterator end = d.toolbars_.end();
1407 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1408 if (it->second->isVisibiltyOn() && it->second->isVisible())
1411 for (int i = 0; i != d.splitter_->count(); ++i)
1412 d.tabWorkArea(i)->setFullScreen(true);
1413 setContentsMargins(-2, -2, -2, -2);
1415 hideDialogs("prefs", nullptr);
1416 } else if (ofstate && !nfstate) {
1417 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1418 // switch back from full-screen state
1419 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1420 statusBar()->show();
1421 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1423 if (lyxrc.full_screen_toolbars) {
1424 ToolbarMap::iterator end = d.toolbars_.end();
1425 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1426 if (it->second->isVisibiltyOn() && !it->second->isVisible())
1430 for (int i = 0; i != d.splitter_->count(); ++i)
1431 d.tabWorkArea(i)->setFullScreen(false);
1432 setContentsMargins(0, 0, 0, 0);
1436 case QEvent::WindowActivate: {
1437 GuiView * old_view = guiApp->currentView();
1438 if (this == old_view) {
1440 return QMainWindow::event(e);
1442 if (old_view && old_view->currentBufferView()) {
1443 // save current selection to the selection buffer to allow
1444 // middle-button paste in this window.
1445 cap::saveSelection(old_view->currentBufferView()->cursor());
1447 guiApp->setCurrentView(this);
1448 if (d.current_work_area_)
1449 on_currentWorkAreaChanged(d.current_work_area_);
1453 return QMainWindow::event(e);
1456 case QEvent::ShortcutOverride: {
1458 if (isFullScreen() && menuBar()->isHidden()) {
1459 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1460 // FIXME: we should also try to detect special LyX shortcut such as
1461 // Alt-P and Alt-M. Right now there is a hack in
1462 // GuiWorkArea::processKeySym() that hides again the menubar for
1464 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1466 return QMainWindow::event(e);
1469 return QMainWindow::event(e);
1473 return QMainWindow::event(e);
1477 void GuiView::resetWindowTitle()
1479 setWindowTitle(qt_("LyX"));
1482 bool GuiView::focusNextPrevChild(bool /*next*/)
1489 bool GuiView::busy() const
1495 void GuiView::setBusy(bool busy)
1497 bool const busy_before = busy_ > 0;
1498 busy ? ++busy_ : --busy_;
1499 if ((busy_ > 0) == busy_before)
1500 // busy state didn't change
1504 QApplication::setOverrideCursor(Qt::WaitCursor);
1507 QApplication::restoreOverrideCursor();
1512 void GuiView::resetCommandExecute()
1514 command_execute_ = false;
1519 double GuiView::pixelRatio() const
1521 #if QT_VERSION >= 0x050000
1522 return qt_scale_factor * devicePixelRatio();
1529 GuiWorkArea * GuiView::workArea(int index)
1531 if (TabWorkArea * twa = d.currentTabWorkArea())
1532 if (index < twa->count())
1533 return twa->workArea(index);
1538 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1540 if (currentWorkArea()
1541 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1542 return currentWorkArea();
1543 if (TabWorkArea * twa = d.currentTabWorkArea())
1544 return twa->workArea(buffer);
1549 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1551 // Automatically create a TabWorkArea if there are none yet.
1552 TabWorkArea * tab_widget = d.splitter_->count()
1553 ? d.currentTabWorkArea() : addTabWorkArea();
1554 return tab_widget->addWorkArea(buffer, *this);
1558 TabWorkArea * GuiView::addTabWorkArea()
1560 TabWorkArea * twa = new TabWorkArea;
1561 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1562 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1563 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1564 this, SLOT(on_lastWorkAreaRemoved()));
1566 d.splitter_->addWidget(twa);
1567 d.stack_widget_->setCurrentWidget(d.splitter_);
1572 GuiWorkArea const * GuiView::currentWorkArea() const
1574 return d.current_work_area_;
1578 GuiWorkArea * GuiView::currentWorkArea()
1580 return d.current_work_area_;
1584 GuiWorkArea const * GuiView::currentMainWorkArea() const
1586 if (!d.currentTabWorkArea())
1588 return d.currentTabWorkArea()->currentWorkArea();
1592 GuiWorkArea * GuiView::currentMainWorkArea()
1594 if (!d.currentTabWorkArea())
1596 return d.currentTabWorkArea()->currentWorkArea();
1600 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1602 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1604 d.current_work_area_ = nullptr;
1606 Q_EMIT bufferViewChanged();
1610 // FIXME: I've no clue why this is here and why it accesses
1611 // theGuiApp()->currentView, which might be 0 (bug 6464).
1612 // See also 27525 (vfr).
1613 if (theGuiApp()->currentView() == this
1614 && theGuiApp()->currentView()->currentWorkArea() == wa)
1617 if (currentBufferView())
1618 cap::saveSelection(currentBufferView()->cursor());
1620 theGuiApp()->setCurrentView(this);
1621 d.current_work_area_ = wa;
1623 // We need to reset this now, because it will need to be
1624 // right if the tabWorkArea gets reset in the for loop. We
1625 // will change it back if we aren't in that case.
1626 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1627 d.current_main_work_area_ = wa;
1629 for (int i = 0; i != d.splitter_->count(); ++i) {
1630 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1631 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1632 << ", Current main wa: " << currentMainWorkArea());
1637 d.current_main_work_area_ = old_cmwa;
1639 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1640 on_currentWorkAreaChanged(wa);
1641 BufferView & bv = wa->bufferView();
1642 bv.cursor().fixIfBroken();
1644 wa->setUpdatesEnabled(true);
1645 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1649 void GuiView::removeWorkArea(GuiWorkArea * wa)
1651 LASSERT(wa, return);
1652 if (wa == d.current_work_area_) {
1654 disconnectBufferView();
1655 d.current_work_area_ = nullptr;
1656 d.current_main_work_area_ = nullptr;
1659 bool found_twa = false;
1660 for (int i = 0; i != d.splitter_->count(); ++i) {
1661 TabWorkArea * twa = d.tabWorkArea(i);
1662 if (twa->removeWorkArea(wa)) {
1663 // Found in this tab group, and deleted the GuiWorkArea.
1665 if (twa->count() != 0) {
1666 if (d.current_work_area_ == nullptr)
1667 // This means that we are closing the current GuiWorkArea, so
1668 // switch to the next GuiWorkArea in the found TabWorkArea.
1669 setCurrentWorkArea(twa->currentWorkArea());
1671 // No more WorkAreas in this tab group, so delete it.
1678 // It is not a tabbed work area (i.e., the search work area), so it
1679 // should be deleted by other means.
1680 LASSERT(found_twa, return);
1682 if (d.current_work_area_ == nullptr) {
1683 if (d.splitter_->count() != 0) {
1684 TabWorkArea * twa = d.currentTabWorkArea();
1685 setCurrentWorkArea(twa->currentWorkArea());
1687 // No more work areas, switch to the background widget.
1688 setCurrentWorkArea(nullptr);
1694 LayoutBox * GuiView::getLayoutDialog() const
1700 void GuiView::updateLayoutList()
1703 d.layout_->updateContents(false);
1707 void GuiView::updateToolbars()
1709 ToolbarMap::iterator end = d.toolbars_.end();
1710 if (d.current_work_area_) {
1712 if (d.current_work_area_->bufferView().cursor().inMathed()
1713 && !d.current_work_area_->bufferView().cursor().inRegexped())
1714 context |= Toolbars::MATH;
1715 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1716 context |= Toolbars::TABLE;
1717 if (currentBufferView()->buffer().areChangesPresent()
1718 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1719 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1720 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1721 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1722 context |= Toolbars::REVIEW;
1723 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1724 context |= Toolbars::MATHMACROTEMPLATE;
1725 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1726 context |= Toolbars::IPA;
1727 if (command_execute_)
1728 context |= Toolbars::MINIBUFFER;
1729 if (minibuffer_focus_) {
1730 context |= Toolbars::MINIBUFFER_FOCUS;
1731 minibuffer_focus_ = false;
1734 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1735 it->second->update(context);
1737 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1738 it->second->update();
1742 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1744 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1745 LASSERT(newBuffer, return);
1747 GuiWorkArea * wa = workArea(*newBuffer);
1748 if (wa == nullptr) {
1750 newBuffer->masterBuffer()->updateBuffer();
1752 wa = addWorkArea(*newBuffer);
1753 // scroll to the position when the BufferView was last closed
1754 if (lyxrc.use_lastfilepos) {
1755 LastFilePosSection::FilePos filepos =
1756 theSession().lastFilePos().load(newBuffer->fileName());
1757 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1760 //Disconnect the old buffer...there's no new one.
1763 connectBuffer(*newBuffer);
1764 connectBufferView(wa->bufferView());
1766 setCurrentWorkArea(wa);
1770 void GuiView::connectBuffer(Buffer & buf)
1772 buf.setGuiDelegate(this);
1776 void GuiView::disconnectBuffer()
1778 if (d.current_work_area_)
1779 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1783 void GuiView::connectBufferView(BufferView & bv)
1785 bv.setGuiDelegate(this);
1789 void GuiView::disconnectBufferView()
1791 if (d.current_work_area_)
1792 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1796 void GuiView::errors(string const & error_type, bool from_master)
1798 BufferView const * const bv = currentBufferView();
1802 ErrorList const & el = from_master ?
1803 bv->buffer().masterBuffer()->errorList(error_type) :
1804 bv->buffer().errorList(error_type);
1809 string err = error_type;
1811 err = "from_master|" + error_type;
1812 showDialog("errorlist", err);
1816 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1818 d.toc_models_.updateItem(toqstr(type), dit);
1822 void GuiView::structureChanged()
1824 // This is called from the Buffer, which has no way to ensure that cursors
1825 // in BufferView remain valid.
1826 if (documentBufferView())
1827 documentBufferView()->cursor().sanitize();
1828 // FIXME: This is slightly expensive, though less than the tocBackend update
1829 // (#9880). This also resets the view in the Toc Widget (#6675).
1830 d.toc_models_.reset(documentBufferView());
1831 // Navigator needs more than a simple update in this case. It needs to be
1833 updateDialog("toc", "");
1837 void GuiView::updateDialog(string const & name, string const & sdata)
1839 if (!isDialogVisible(name))
1842 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1843 if (it == d.dialogs_.end())
1846 Dialog * const dialog = it->second.get();
1847 if (dialog->isVisibleView())
1848 dialog->initialiseParams(sdata);
1852 BufferView * GuiView::documentBufferView()
1854 return currentMainWorkArea()
1855 ? ¤tMainWorkArea()->bufferView()
1860 BufferView const * GuiView::documentBufferView() const
1862 return currentMainWorkArea()
1863 ? ¤tMainWorkArea()->bufferView()
1868 BufferView * GuiView::currentBufferView()
1870 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1874 BufferView const * GuiView::currentBufferView() const
1876 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1880 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1881 Buffer const * orig, Buffer * clone)
1883 bool const success = clone->autoSave();
1885 busyBuffers.remove(orig);
1887 ? _("Automatic save done.")
1888 : _("Automatic save failed!");
1892 void GuiView::autoSave()
1894 LYXERR(Debug::INFO, "Running autoSave()");
1896 Buffer * buffer = documentBufferView()
1897 ? &documentBufferView()->buffer() : nullptr;
1899 resetAutosaveTimers();
1903 GuiViewPrivate::busyBuffers.insert(buffer);
1904 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1905 buffer, buffer->cloneBufferOnly());
1906 d.autosave_watcher_.setFuture(f);
1907 resetAutosaveTimers();
1911 void GuiView::resetAutosaveTimers()
1914 d.autosave_timeout_.restart();
1918 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1921 Buffer * buf = currentBufferView()
1922 ? ¤tBufferView()->buffer() : nullptr;
1923 Buffer * doc_buffer = documentBufferView()
1924 ? &(documentBufferView()->buffer()) : nullptr;
1927 /* In LyX/Mac, when a dialog is open, the menus of the
1928 application can still be accessed without giving focus to
1929 the main window. In this case, we want to disable the menu
1930 entries that are buffer-related.
1931 This code must not be used on Linux and Windows, since it
1932 would disable buffer-related entries when hovering over the
1933 menu (see bug #9574).
1935 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1941 // Check whether we need a buffer
1942 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1943 // no, exit directly
1944 flag.message(from_utf8(N_("Command not allowed with"
1945 "out any document open")));
1946 flag.setEnabled(false);
1950 if (cmd.origin() == FuncRequest::TOC) {
1951 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1952 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1953 flag.setEnabled(false);
1957 switch(cmd.action()) {
1958 case LFUN_BUFFER_IMPORT:
1961 case LFUN_MASTER_BUFFER_EXPORT:
1963 && (doc_buffer->parent() != nullptr
1964 || doc_buffer->hasChildren())
1965 && !d.processing_thread_watcher_.isRunning()
1966 // this launches a dialog, which would be in the wrong Buffer
1967 && !(::lyx::operator==(cmd.argument(), "custom"));
1970 case LFUN_MASTER_BUFFER_UPDATE:
1971 case LFUN_MASTER_BUFFER_VIEW:
1973 && (doc_buffer->parent() != nullptr
1974 || doc_buffer->hasChildren())
1975 && !d.processing_thread_watcher_.isRunning();
1978 case LFUN_BUFFER_UPDATE:
1979 case LFUN_BUFFER_VIEW: {
1980 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1984 string format = to_utf8(cmd.argument());
1985 if (cmd.argument().empty())
1986 format = doc_buffer->params().getDefaultOutputFormat();
1987 enable = doc_buffer->params().isExportable(format, true);
1991 case LFUN_BUFFER_RELOAD:
1992 enable = doc_buffer && !doc_buffer->isUnnamed()
1993 && doc_buffer->fileName().exists()
1994 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1997 case LFUN_BUFFER_RESET_EXPORT:
1998 enable = doc_buffer != nullptr;
2001 case LFUN_BUFFER_CHILD_OPEN:
2002 enable = doc_buffer != nullptr;
2005 case LFUN_MASTER_BUFFER_FORALL: {
2006 if (doc_buffer == nullptr) {
2007 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2011 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2012 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2013 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2018 for (Buffer * buf : doc_buffer->allRelatives()) {
2019 GuiWorkArea * wa = workArea(*buf);
2022 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2023 enable = flag.enabled();
2030 case LFUN_BUFFER_WRITE:
2031 enable = doc_buffer && (doc_buffer->isUnnamed()
2032 || (!doc_buffer->isClean()
2033 || cmd.argument() == "force"));
2036 //FIXME: This LFUN should be moved to GuiApplication.
2037 case LFUN_BUFFER_WRITE_ALL: {
2038 // We enable the command only if there are some modified buffers
2039 Buffer * first = theBufferList().first();
2044 // We cannot use a for loop as the buffer list is a cycle.
2046 if (!b->isClean()) {
2050 b = theBufferList().next(b);
2051 } while (b != first);
2055 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2056 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2059 case LFUN_BUFFER_EXPORT: {
2060 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2064 return doc_buffer->getStatus(cmd, flag);
2067 case LFUN_BUFFER_EXPORT_AS:
2068 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2073 case LFUN_BUFFER_WRITE_AS:
2074 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2075 enable = doc_buffer != nullptr;
2078 case LFUN_EXPORT_CANCEL:
2079 enable = d.processing_thread_watcher_.isRunning();
2082 case LFUN_BUFFER_CLOSE:
2083 case LFUN_VIEW_CLOSE:
2084 enable = doc_buffer != nullptr;
2087 case LFUN_BUFFER_CLOSE_ALL:
2088 enable = theBufferList().last() != theBufferList().first();
2091 case LFUN_BUFFER_CHKTEX: {
2092 // hide if we have no checktex command
2093 if (lyxrc.chktex_command.empty()) {
2094 flag.setUnknown(true);
2098 if (!doc_buffer || !doc_buffer->params().isLatex()
2099 || d.processing_thread_watcher_.isRunning()) {
2100 // grey out, don't hide
2108 case LFUN_VIEW_SPLIT:
2109 if (cmd.getArg(0) == "vertical")
2110 enable = doc_buffer && (d.splitter_->count() == 1 ||
2111 d.splitter_->orientation() == Qt::Vertical);
2113 enable = doc_buffer && (d.splitter_->count() == 1 ||
2114 d.splitter_->orientation() == Qt::Horizontal);
2117 case LFUN_TAB_GROUP_CLOSE:
2118 enable = d.tabWorkAreaCount() > 1;
2121 case LFUN_DEVEL_MODE_TOGGLE:
2122 flag.setOnOff(devel_mode_);
2125 case LFUN_TOOLBAR_TOGGLE: {
2126 string const name = cmd.getArg(0);
2127 if (GuiToolbar * t = toolbar(name))
2128 flag.setOnOff(t->isVisible());
2131 docstring const msg =
2132 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2138 case LFUN_TOOLBAR_MOVABLE: {
2139 string const name = cmd.getArg(0);
2140 // use negation since locked == !movable
2142 // toolbar name * locks all toolbars
2143 flag.setOnOff(!toolbarsMovable_);
2144 else if (GuiToolbar * t = toolbar(name))
2145 flag.setOnOff(!(t->isMovable()));
2148 docstring const msg =
2149 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2155 case LFUN_ICON_SIZE:
2156 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2159 case LFUN_DROP_LAYOUTS_CHOICE:
2160 enable = buf != nullptr;
2163 case LFUN_UI_TOGGLE:
2164 flag.setOnOff(isFullScreen());
2167 case LFUN_DIALOG_DISCONNECT_INSET:
2170 case LFUN_DIALOG_HIDE:
2171 // FIXME: should we check if the dialog is shown?
2174 case LFUN_DIALOG_TOGGLE:
2175 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2178 case LFUN_DIALOG_SHOW: {
2179 string const name = cmd.getArg(0);
2181 enable = name == "aboutlyx"
2182 || name == "file" //FIXME: should be removed.
2183 || name == "lyxfiles"
2185 || name == "texinfo"
2186 || name == "progress"
2187 || name == "compare";
2188 else if (name == "character" || name == "symbols"
2189 || name == "mathdelimiter" || name == "mathmatrix") {
2190 if (!buf || buf->isReadonly())
2193 Cursor const & cur = currentBufferView()->cursor();
2194 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2197 else if (name == "latexlog")
2198 enable = FileName(doc_buffer->logName()).isReadableFile();
2199 else if (name == "spellchecker")
2200 enable = theSpellChecker()
2201 && !doc_buffer->isReadonly()
2202 && !doc_buffer->text().empty();
2203 else if (name == "vclog")
2204 enable = doc_buffer->lyxvc().inUse();
2208 case LFUN_DIALOG_UPDATE: {
2209 string const name = cmd.getArg(0);
2211 enable = name == "prefs";
2215 case LFUN_COMMAND_EXECUTE:
2217 case LFUN_MENU_OPEN:
2218 // Nothing to check.
2221 case LFUN_COMPLETION_INLINE:
2222 if (!d.current_work_area_
2223 || !d.current_work_area_->completer().inlinePossible(
2224 currentBufferView()->cursor()))
2228 case LFUN_COMPLETION_POPUP:
2229 if (!d.current_work_area_
2230 || !d.current_work_area_->completer().popupPossible(
2231 currentBufferView()->cursor()))
2236 if (!d.current_work_area_
2237 || !d.current_work_area_->completer().inlinePossible(
2238 currentBufferView()->cursor()))
2242 case LFUN_COMPLETION_ACCEPT:
2243 if (!d.current_work_area_
2244 || (!d.current_work_area_->completer().popupVisible()
2245 && !d.current_work_area_->completer().inlineVisible()
2246 && !d.current_work_area_->completer().completionAvailable()))
2250 case LFUN_COMPLETION_CANCEL:
2251 if (!d.current_work_area_
2252 || (!d.current_work_area_->completer().popupVisible()
2253 && !d.current_work_area_->completer().inlineVisible()))
2257 case LFUN_BUFFER_ZOOM_OUT:
2258 case LFUN_BUFFER_ZOOM_IN: {
2259 // only diff between these two is that the default for ZOOM_OUT
2261 bool const neg_zoom =
2262 convert<int>(cmd.argument()) < 0 ||
2263 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2264 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2265 docstring const msg =
2266 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2270 enable = doc_buffer;
2274 case LFUN_BUFFER_ZOOM: {
2275 bool const less_than_min_zoom =
2276 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2277 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2278 docstring const msg =
2279 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2284 enable = doc_buffer;
2288 case LFUN_BUFFER_MOVE_NEXT:
2289 case LFUN_BUFFER_MOVE_PREVIOUS:
2290 // we do not cycle when moving
2291 case LFUN_BUFFER_NEXT:
2292 case LFUN_BUFFER_PREVIOUS:
2293 // because we cycle, it doesn't matter whether on first or last
2294 enable = (d.currentTabWorkArea()->count() > 1);
2296 case LFUN_BUFFER_SWITCH:
2297 // toggle on the current buffer, but do not toggle off
2298 // the other ones (is that a good idea?)
2300 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2301 flag.setOnOff(true);
2304 case LFUN_VC_REGISTER:
2305 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2307 case LFUN_VC_RENAME:
2308 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2311 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2313 case LFUN_VC_CHECK_IN:
2314 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2316 case LFUN_VC_CHECK_OUT:
2317 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2319 case LFUN_VC_LOCKING_TOGGLE:
2320 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2321 && doc_buffer->lyxvc().lockingToggleEnabled();
2322 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2324 case LFUN_VC_REVERT:
2325 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2326 && !doc_buffer->hasReadonlyFlag();
2328 case LFUN_VC_UNDO_LAST:
2329 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2331 case LFUN_VC_REPO_UPDATE:
2332 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2334 case LFUN_VC_COMMAND: {
2335 if (cmd.argument().empty())
2337 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2341 case LFUN_VC_COMPARE:
2342 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2345 case LFUN_SERVER_GOTO_FILE_ROW:
2346 case LFUN_LYX_ACTIVATE:
2347 case LFUN_WINDOW_RAISE:
2349 case LFUN_FORWARD_SEARCH:
2350 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2353 case LFUN_FILE_INSERT_PLAINTEXT:
2354 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2355 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2358 case LFUN_SPELLING_CONTINUOUSLY:
2359 flag.setOnOff(lyxrc.spellcheck_continuously);
2362 case LFUN_CITATION_OPEN:
2371 flag.setEnabled(false);
2377 static FileName selectTemplateFile()
2379 FileDialog dlg(qt_("Select template file"));
2380 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2381 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2383 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2384 QStringList(qt_("LyX Documents (*.lyx)")));
2386 if (result.first == FileDialog::Later)
2388 if (result.second.isEmpty())
2390 return FileName(fromqstr(result.second));
2394 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2398 Buffer * newBuffer = nullptr;
2400 newBuffer = checkAndLoadLyXFile(filename);
2401 } catch (ExceptionMessage const &) {
2408 message(_("Document not loaded."));
2412 setBuffer(newBuffer);
2413 newBuffer->errors("Parse");
2416 theSession().lastFiles().add(filename);
2417 theSession().writeFile();
2424 void GuiView::openDocument(string const & fname)
2426 string initpath = lyxrc.document_path;
2428 if (documentBufferView()) {
2429 string const trypath = documentBufferView()->buffer().filePath();
2430 // If directory is writeable, use this as default.
2431 if (FileName(trypath).isDirWritable())
2437 if (fname.empty()) {
2438 FileDialog dlg(qt_("Select document to open"));
2439 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2440 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2442 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2443 FileDialog::Result result =
2444 dlg.open(toqstr(initpath), filter);
2446 if (result.first == FileDialog::Later)
2449 filename = fromqstr(result.second);
2451 // check selected filename
2452 if (filename.empty()) {
2453 message(_("Canceled."));
2459 // get absolute path of file and add ".lyx" to the filename if
2461 FileName const fullname =
2462 fileSearch(string(), filename, "lyx", support::may_not_exist);
2463 if (!fullname.empty())
2464 filename = fullname.absFileName();
2466 if (!fullname.onlyPath().isDirectory()) {
2467 Alert::warning(_("Invalid filename"),
2468 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2469 from_utf8(fullname.absFileName())));
2473 // if the file doesn't exist and isn't already open (bug 6645),
2474 // let the user create one
2475 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2476 !LyXVC::file_not_found_hook(fullname)) {
2477 // the user specifically chose this name. Believe him.
2478 Buffer * const b = newFile(filename, string(), true);
2484 docstring const disp_fn = makeDisplayPath(filename);
2485 message(bformat(_("Opening document %1$s..."), disp_fn));
2488 Buffer * buf = loadDocument(fullname);
2490 str2 = bformat(_("Document %1$s opened."), disp_fn);
2491 if (buf->lyxvc().inUse())
2492 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2493 " " + _("Version control detected.");
2495 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2500 // FIXME: clean that
2501 static bool import(GuiView * lv, FileName const & filename,
2502 string const & format, ErrorList & errorList)
2504 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2506 string loader_format;
2507 vector<string> loaders = theConverters().loaders();
2508 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2509 vector<string>::const_iterator it = loaders.begin();
2510 vector<string>::const_iterator en = loaders.end();
2511 for (; it != en; ++it) {
2512 if (!theConverters().isReachable(format, *it))
2515 string const tofile =
2516 support::changeExtension(filename.absFileName(),
2517 theFormats().extension(*it));
2518 if (theConverters().convert(nullptr, filename, FileName(tofile),
2519 filename, format, *it, errorList) != Converters::SUCCESS)
2521 loader_format = *it;
2524 if (loader_format.empty()) {
2525 frontend::Alert::error(_("Couldn't import file"),
2526 bformat(_("No information for importing the format %1$s."),
2527 translateIfPossible(theFormats().prettyName(format))));
2531 loader_format = format;
2533 if (loader_format == "lyx") {
2534 Buffer * buf = lv->loadDocument(lyxfile);
2538 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2542 bool as_paragraphs = loader_format == "textparagraph";
2543 string filename2 = (loader_format == format) ? filename.absFileName()
2544 : support::changeExtension(filename.absFileName(),
2545 theFormats().extension(loader_format));
2546 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2548 guiApp->setCurrentView(lv);
2549 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2556 void GuiView::importDocument(string const & argument)
2559 string filename = split(argument, format, ' ');
2561 LYXERR(Debug::INFO, format << " file: " << filename);
2563 // need user interaction
2564 if (filename.empty()) {
2565 string initpath = lyxrc.document_path;
2566 if (documentBufferView()) {
2567 string const trypath = documentBufferView()->buffer().filePath();
2568 // If directory is writeable, use this as default.
2569 if (FileName(trypath).isDirWritable())
2573 docstring const text = bformat(_("Select %1$s file to import"),
2574 translateIfPossible(theFormats().prettyName(format)));
2576 FileDialog dlg(toqstr(text));
2577 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2578 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2580 docstring filter = translateIfPossible(theFormats().prettyName(format));
2583 filter += from_utf8(theFormats().extensions(format));
2586 FileDialog::Result result =
2587 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2589 if (result.first == FileDialog::Later)
2592 filename = fromqstr(result.second);
2594 // check selected filename
2595 if (filename.empty())
2596 message(_("Canceled."));
2599 if (filename.empty())
2602 // get absolute path of file
2603 FileName const fullname(support::makeAbsPath(filename));
2605 // Can happen if the user entered a path into the dialog
2607 if (fullname.onlyFileName().empty()) {
2608 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2609 "Aborting import."),
2610 from_utf8(fullname.absFileName()));
2611 frontend::Alert::error(_("File name error"), msg);
2612 message(_("Canceled."));
2617 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2619 // Check if the document already is open
2620 Buffer * buf = theBufferList().getBuffer(lyxfile);
2623 if (!closeBuffer()) {
2624 message(_("Canceled."));
2629 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2631 // if the file exists already, and we didn't do
2632 // -i lyx thefile.lyx, warn
2633 if (lyxfile.exists() && fullname != lyxfile) {
2635 docstring text = bformat(_("The document %1$s already exists.\n\n"
2636 "Do you want to overwrite that document?"), displaypath);
2637 int const ret = Alert::prompt(_("Overwrite document?"),
2638 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2641 message(_("Canceled."));
2646 message(bformat(_("Importing %1$s..."), displaypath));
2647 ErrorList errorList;
2648 if (import(this, fullname, format, errorList))
2649 message(_("imported."));
2651 message(_("file not imported!"));
2653 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2657 void GuiView::newDocument(string const & filename, string templatefile,
2660 FileName initpath(lyxrc.document_path);
2661 if (documentBufferView()) {
2662 FileName const trypath(documentBufferView()->buffer().filePath());
2663 // If directory is writeable, use this as default.
2664 if (trypath.isDirWritable())
2668 if (from_template) {
2669 if (templatefile.empty())
2670 templatefile = selectTemplateFile().absFileName();
2671 if (templatefile.empty())
2676 if (filename.empty())
2677 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2679 b = newFile(filename, templatefile, true);
2684 // If no new document could be created, it is unsure
2685 // whether there is a valid BufferView.
2686 if (currentBufferView())
2687 // Ensure the cursor is correctly positioned on screen.
2688 currentBufferView()->showCursor();
2692 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2694 BufferView * bv = documentBufferView();
2699 FileName filename(to_utf8(fname));
2700 if (filename.empty()) {
2701 // Launch a file browser
2703 string initpath = lyxrc.document_path;
2704 string const trypath = bv->buffer().filePath();
2705 // If directory is writeable, use this as default.
2706 if (FileName(trypath).isDirWritable())
2710 FileDialog dlg(qt_("Select LyX document to insert"));
2711 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2712 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2714 FileDialog::Result result = dlg.open(toqstr(initpath),
2715 QStringList(qt_("LyX Documents (*.lyx)")));
2717 if (result.first == FileDialog::Later)
2721 filename.set(fromqstr(result.second));
2723 // check selected filename
2724 if (filename.empty()) {
2725 // emit message signal.
2726 message(_("Canceled."));
2731 bv->insertLyXFile(filename, ignorelang);
2732 bv->buffer().errors("Parse");
2736 string const GuiView::getTemplatesPath(Buffer & b)
2738 // We start off with the user's templates path
2739 string result = addPath(package().user_support().absFileName(), "templates");
2740 // Check for the document language
2741 string const langcode = b.params().language->code();
2742 string const shortcode = langcode.substr(0, 2);
2743 if (!langcode.empty() && shortcode != "en") {
2744 string subpath = addPath(result, shortcode);
2745 string subpath_long = addPath(result, langcode);
2746 // If we have a subdirectory for the language already,
2748 FileName sp = FileName(subpath);
2749 if (sp.isDirectory())
2751 else if (FileName(subpath_long).isDirectory())
2752 result = subpath_long;
2754 // Ask whether we should create such a subdirectory
2755 docstring const text =
2756 bformat(_("It is suggested to save the template in a subdirectory\n"
2757 "appropriate to the document language (%1$s).\n"
2758 "This subdirectory does not exists yet.\n"
2759 "Do you want to create it?"),
2760 _(b.params().language->display()));
2761 if (Alert::prompt(_("Create Language Directory?"),
2762 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2763 // If the user agreed, we try to create it and report if this failed.
2764 if (!sp.createDirectory(0777))
2765 Alert::error(_("Subdirectory creation failed!"),
2766 _("Could not create subdirectory.\n"
2767 "The template will be saved in the parent directory."));
2773 // Do we have a layout category?
2774 string const cat = b.params().baseClass() ?
2775 b.params().baseClass()->category()
2778 string subpath = addPath(result, cat);
2779 // If we have a subdirectory for the category already,
2781 FileName sp = FileName(subpath);
2782 if (sp.isDirectory())
2785 // Ask whether we should create such a subdirectory
2786 docstring const text =
2787 bformat(_("It is suggested to save the template in a subdirectory\n"
2788 "appropriate to the layout category (%1$s).\n"
2789 "This subdirectory does not exists yet.\n"
2790 "Do you want to create it?"),
2792 if (Alert::prompt(_("Create Category Directory?"),
2793 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2794 // If the user agreed, we try to create it and report if this failed.
2795 if (!sp.createDirectory(0777))
2796 Alert::error(_("Subdirectory creation failed!"),
2797 _("Could not create subdirectory.\n"
2798 "The template will be saved in the parent directory."));
2808 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2810 FileName fname = b.fileName();
2811 FileName const oldname = fname;
2812 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2814 if (!newname.empty()) {
2817 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2819 fname = support::makeAbsPath(to_utf8(newname),
2820 oldname.onlyPath().absFileName());
2822 // Switch to this Buffer.
2825 // No argument? Ask user through dialog.
2827 QString const title = as_template ? qt_("Choose a filename to save template as")
2828 : qt_("Choose a filename to save document as");
2829 FileDialog dlg(title);
2830 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2831 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2833 if (!isLyXFileName(fname.absFileName()))
2834 fname.changeExtension(".lyx");
2836 string const path = as_template ?
2838 : fname.onlyPath().absFileName();
2839 FileDialog::Result result =
2840 dlg.save(toqstr(path),
2841 QStringList(qt_("LyX Documents (*.lyx)")),
2842 toqstr(fname.onlyFileName()));
2844 if (result.first == FileDialog::Later)
2847 fname.set(fromqstr(result.second));
2852 if (!isLyXFileName(fname.absFileName()))
2853 fname.changeExtension(".lyx");
2856 // fname is now the new Buffer location.
2858 // if there is already a Buffer open with this name, we do not want
2859 // to have another one. (the second test makes sure we're not just
2860 // trying to overwrite ourselves, which is fine.)
2861 if (theBufferList().exists(fname) && fname != oldname
2862 && theBufferList().getBuffer(fname) != &b) {
2863 docstring const text =
2864 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2865 "Please close it before attempting to overwrite it.\n"
2866 "Do you want to choose a new filename?"),
2867 from_utf8(fname.absFileName()));
2868 int const ret = Alert::prompt(_("Chosen File Already Open"),
2869 text, 0, 1, _("&Rename"), _("&Cancel"));
2871 case 0: return renameBuffer(b, docstring(), kind);
2872 case 1: return false;
2877 bool const existsLocal = fname.exists();
2878 bool const existsInVC = LyXVC::fileInVC(fname);
2879 if (existsLocal || existsInVC) {
2880 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2881 if (kind != LV_WRITE_AS && existsInVC) {
2882 // renaming to a name that is already in VC
2884 docstring text = bformat(_("The document %1$s "
2885 "is already registered.\n\n"
2886 "Do you want to choose a new name?"),
2888 docstring const title = (kind == LV_VC_RENAME) ?
2889 _("Rename document?") : _("Copy document?");
2890 docstring const button = (kind == LV_VC_RENAME) ?
2891 _("&Rename") : _("&Copy");
2892 int const ret = Alert::prompt(title, text, 0, 1,
2893 button, _("&Cancel"));
2895 case 0: return renameBuffer(b, docstring(), kind);
2896 case 1: return false;
2901 docstring text = bformat(_("The document %1$s "
2902 "already exists.\n\n"
2903 "Do you want to overwrite that document?"),
2905 int const ret = Alert::prompt(_("Overwrite document?"),
2906 text, 0, 2, _("&Overwrite"),
2907 _("&Rename"), _("&Cancel"));
2910 case 1: return renameBuffer(b, docstring(), kind);
2911 case 2: return false;
2917 case LV_VC_RENAME: {
2918 string msg = b.lyxvc().rename(fname);
2921 message(from_utf8(msg));
2925 string msg = b.lyxvc().copy(fname);
2928 message(from_utf8(msg));
2932 case LV_WRITE_AS_TEMPLATE:
2935 // LyXVC created the file already in case of LV_VC_RENAME or
2936 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2937 // relative paths of included stuff right if we moved e.g. from
2938 // /a/b.lyx to /a/c/b.lyx.
2940 bool const saved = saveBuffer(b, fname);
2947 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2949 FileName fname = b.fileName();
2951 FileDialog dlg(qt_("Choose a filename to export the document as"));
2952 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2955 QString const anyformat = qt_("Guess from extension (*.*)");
2958 vector<Format const *> export_formats;
2959 for (Format const & f : theFormats())
2960 if (f.documentFormat())
2961 export_formats.push_back(&f);
2962 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2963 map<QString, string> fmap;
2966 for (Format const * f : export_formats) {
2967 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2968 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2970 from_ascii(f->extension())));
2971 types << loc_filter;
2972 fmap[loc_filter] = f->name();
2973 if (from_ascii(f->name()) == iformat) {
2974 filter = loc_filter;
2975 ext = f->extension();
2978 string ofname = fname.onlyFileName();
2980 ofname = support::changeExtension(ofname, ext);
2981 FileDialog::Result result =
2982 dlg.save(toqstr(fname.onlyPath().absFileName()),
2986 if (result.first != FileDialog::Chosen)
2990 fname.set(fromqstr(result.second));
2991 if (filter == anyformat)
2992 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2994 fmt_name = fmap[filter];
2995 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2996 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2998 if (fmt_name.empty() || fname.empty())
3001 // fname is now the new Buffer location.
3002 if (FileName(fname).exists()) {
3003 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3004 docstring text = bformat(_("The document %1$s already "
3005 "exists.\n\nDo you want to "
3006 "overwrite that document?"),
3008 int const ret = Alert::prompt(_("Overwrite document?"),
3009 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3012 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3013 case 2: return false;
3017 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3020 return dr.dispatched();
3024 bool GuiView::saveBuffer(Buffer & b)
3026 return saveBuffer(b, FileName());
3030 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3032 if (workArea(b) && workArea(b)->inDialogMode())
3035 if (fn.empty() && b.isUnnamed())
3036 return renameBuffer(b, docstring());
3038 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3040 theSession().lastFiles().add(b.fileName());
3041 theSession().writeFile();
3045 // Switch to this Buffer.
3048 // FIXME: we don't tell the user *WHY* the save failed !!
3049 docstring const file = makeDisplayPath(b.absFileName(), 30);
3050 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3051 "Do you want to rename the document and "
3052 "try again?"), file);
3053 int const ret = Alert::prompt(_("Rename and save?"),
3054 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3057 if (!renameBuffer(b, docstring()))
3066 return saveBuffer(b, fn);
3070 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3072 return closeWorkArea(wa, false);
3076 // We only want to close the buffer if it is not visible in other workareas
3077 // of the same view, nor in other views, and if this is not a child
3078 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3080 Buffer & buf = wa->bufferView().buffer();
3082 bool last_wa = d.countWorkAreasOf(buf) == 1
3083 && !inOtherView(buf) && !buf.parent();
3085 bool close_buffer = last_wa;
3088 if (lyxrc.close_buffer_with_last_view == "yes")
3090 else if (lyxrc.close_buffer_with_last_view == "no")
3091 close_buffer = false;
3094 if (buf.isUnnamed())
3095 file = from_utf8(buf.fileName().onlyFileName());
3097 file = buf.fileName().displayName(30);
3098 docstring const text = bformat(
3099 _("Last view on document %1$s is being closed.\n"
3100 "Would you like to close or hide the document?\n"
3102 "Hidden documents can be displayed back through\n"
3103 "the menu: View->Hidden->...\n"
3105 "To remove this question, set your preference in:\n"
3106 " Tools->Preferences->Look&Feel->UserInterface\n"
3108 int ret = Alert::prompt(_("Close or hide document?"),
3109 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3112 close_buffer = (ret == 0);
3116 return closeWorkArea(wa, close_buffer);
3120 bool GuiView::closeBuffer()
3122 GuiWorkArea * wa = currentMainWorkArea();
3123 // coverity complained about this
3124 // it seems unnecessary, but perhaps is worth the check
3125 LASSERT(wa, return false);
3127 setCurrentWorkArea(wa);
3128 Buffer & buf = wa->bufferView().buffer();
3129 return closeWorkArea(wa, !buf.parent());
3133 void GuiView::writeSession() const {
3134 GuiWorkArea const * active_wa = currentMainWorkArea();
3135 for (int i = 0; i < d.splitter_->count(); ++i) {
3136 TabWorkArea * twa = d.tabWorkArea(i);
3137 for (int j = 0; j < twa->count(); ++j) {
3138 GuiWorkArea * wa = twa->workArea(j);
3139 Buffer & buf = wa->bufferView().buffer();
3140 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3146 bool GuiView::closeBufferAll()
3149 for (auto & buf : theBufferList()) {
3150 if (!saveBufferIfNeeded(*buf, false)) {
3151 // Closing has been cancelled, so abort.
3156 // Close the workareas in all other views
3157 QList<int> const ids = guiApp->viewIds();
3158 for (int i = 0; i != ids.size(); ++i) {
3159 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3163 // Close our own workareas
3164 if (!closeWorkAreaAll())
3171 bool GuiView::closeWorkAreaAll()
3173 setCurrentWorkArea(currentMainWorkArea());
3175 // We might be in a situation that there is still a tabWorkArea, but
3176 // there are no tabs anymore. This can happen when we get here after a
3177 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3178 // many TabWorkArea's have no documents anymore.
3181 // We have to call count() each time, because it can happen that
3182 // more than one splitter will disappear in one iteration (bug 5998).
3183 while (d.splitter_->count() > empty_twa) {
3184 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3186 if (twa->count() == 0)
3189 setCurrentWorkArea(twa->currentWorkArea());
3190 if (!closeTabWorkArea(twa))
3198 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3203 Buffer & buf = wa->bufferView().buffer();
3205 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3206 Alert::warning(_("Close document"),
3207 _("Document could not be closed because it is being processed by LyX."));
3212 return closeBuffer(buf);
3214 if (!inMultiTabs(wa))
3215 if (!saveBufferIfNeeded(buf, true))
3223 bool GuiView::closeBuffer(Buffer & buf)
3225 bool success = true;
3226 ListOfBuffers clist = buf.getChildren();
3227 ListOfBuffers::const_iterator it = clist.begin();
3228 ListOfBuffers::const_iterator const bend = clist.end();
3229 for (; it != bend; ++it) {
3230 Buffer * child_buf = *it;
3231 if (theBufferList().isOthersChild(&buf, child_buf)) {
3232 child_buf->setParent(nullptr);
3236 // FIXME: should we look in other tabworkareas?
3237 // ANSWER: I don't think so. I've tested, and if the child is
3238 // open in some other window, it closes without a problem.
3239 GuiWorkArea * child_wa = workArea(*child_buf);
3242 // If we are in a close_event all children will be closed in some time,
3243 // so no need to do it here. This will ensure that the children end up
3244 // in the session file in the correct order. If we close the master
3245 // buffer, we can close or release the child buffers here too.
3247 success = closeWorkArea(child_wa, true);
3251 // In this case the child buffer is open but hidden.
3252 // Even in this case, children can be dirty (e.g.,
3253 // after a label change in the master, see #11405).
3254 // Therefore, check this
3255 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3256 // If we are in a close_event all children will be closed in some time,
3257 // so no need to do it here. This will ensure that the children end up
3258 // in the session file in the correct order. If we close the master
3259 // buffer, we can close or release the child buffers here too.
3262 // Save dirty buffers also if closing_!
3263 if (saveBufferIfNeeded(*child_buf, false)) {
3264 child_buf->removeAutosaveFile();
3265 theBufferList().release(child_buf);
3267 // Saving of dirty children has been cancelled.
3268 // Cancel the whole process.
3275 // goto bookmark to update bookmark pit.
3276 // FIXME: we should update only the bookmarks related to this buffer!
3277 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3278 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3279 guiApp->gotoBookmark(i + 1, false, false);
3281 if (saveBufferIfNeeded(buf, false)) {
3282 buf.removeAutosaveFile();
3283 theBufferList().release(&buf);
3287 // open all children again to avoid a crash because of dangling
3288 // pointers (bug 6603)
3294 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3296 while (twa == d.currentTabWorkArea()) {
3297 twa->setCurrentIndex(twa->count() - 1);
3299 GuiWorkArea * wa = twa->currentWorkArea();
3300 Buffer & b = wa->bufferView().buffer();
3302 // We only want to close the buffer if the same buffer is not visible
3303 // in another view, and if this is not a child and if we are closing
3304 // a view (not a tabgroup).
3305 bool const close_buffer =
3306 !inOtherView(b) && !b.parent() && closing_;
3308 if (!closeWorkArea(wa, close_buffer))
3315 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3317 if (buf.isClean() || buf.paragraphs().empty())
3320 // Switch to this Buffer.
3326 if (buf.isUnnamed()) {
3327 file = from_utf8(buf.fileName().onlyFileName());
3330 FileName filename = buf.fileName();
3332 file = filename.displayName(30);
3333 exists = filename.exists();
3336 // Bring this window to top before asking questions.
3341 if (hiding && buf.isUnnamed()) {
3342 docstring const text = bformat(_("The document %1$s has not been "
3343 "saved yet.\n\nDo you want to save "
3344 "the document?"), file);
3345 ret = Alert::prompt(_("Save new document?"),
3346 text, 0, 1, _("&Save"), _("&Cancel"));
3350 docstring const text = exists ?
3351 bformat(_("The document %1$s has unsaved changes."
3352 "\n\nDo you want to save the document or "
3353 "discard the changes?"), file) :
3354 bformat(_("The document %1$s has not been saved yet."
3355 "\n\nDo you want to save the document or "
3356 "discard it entirely?"), file);
3357 docstring const title = exists ?
3358 _("Save changed document?") : _("Save document?");
3359 ret = Alert::prompt(title, text, 0, 2,
3360 _("&Save"), _("&Discard"), _("&Cancel"));
3365 if (!saveBuffer(buf))
3369 // If we crash after this we could have no autosave file
3370 // but I guess this is really improbable (Jug).
3371 // Sometimes improbable things happen:
3372 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3373 // buf.removeAutosaveFile();
3375 // revert all changes
3386 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3388 Buffer & buf = wa->bufferView().buffer();
3390 for (int i = 0; i != d.splitter_->count(); ++i) {
3391 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3392 if (wa_ && wa_ != wa)
3395 return inOtherView(buf);
3399 bool GuiView::inOtherView(Buffer & buf)
3401 QList<int> const ids = guiApp->viewIds();
3403 for (int i = 0; i != ids.size(); ++i) {
3407 if (guiApp->view(ids[i]).workArea(buf))
3414 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3416 if (!documentBufferView())
3419 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3420 Buffer * const curbuf = &documentBufferView()->buffer();
3421 int nwa = twa->count();
3422 for (int i = 0; i < nwa; ++i) {
3423 if (&workArea(i)->bufferView().buffer() == curbuf) {
3425 if (np == NEXTBUFFER)
3426 next_index = (i == nwa - 1 ? 0 : i + 1);
3428 next_index = (i == 0 ? nwa - 1 : i - 1);
3430 twa->moveTab(i, next_index);
3432 setBuffer(&workArea(next_index)->bufferView().buffer());
3440 /// make sure the document is saved
3441 static bool ensureBufferClean(Buffer * buffer)
3443 LASSERT(buffer, return false);
3444 if (buffer->isClean() && !buffer->isUnnamed())
3447 docstring const file = buffer->fileName().displayName(30);
3450 if (!buffer->isUnnamed()) {
3451 text = bformat(_("The document %1$s has unsaved "
3452 "changes.\n\nDo you want to save "
3453 "the document?"), file);
3454 title = _("Save changed document?");
3457 text = bformat(_("The document %1$s has not been "
3458 "saved yet.\n\nDo you want to save "
3459 "the document?"), file);
3460 title = _("Save new document?");
3462 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3465 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3467 return buffer->isClean() && !buffer->isUnnamed();
3471 bool GuiView::reloadBuffer(Buffer & buf)
3473 currentBufferView()->cursor().reset();
3474 Buffer::ReadStatus status = buf.reload();
3475 return status == Buffer::ReadSuccess;
3479 void GuiView::checkExternallyModifiedBuffers()
3481 BufferList::iterator bit = theBufferList().begin();
3482 BufferList::iterator const bend = theBufferList().end();
3483 for (; bit != bend; ++bit) {
3484 Buffer * buf = *bit;
3485 if (buf->fileName().exists() && buf->isChecksumModified()) {
3486 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3487 " Reload now? Any local changes will be lost."),
3488 from_utf8(buf->absFileName()));
3489 int const ret = Alert::prompt(_("Reload externally changed document?"),
3490 text, 0, 1, _("&Reload"), _("&Cancel"));
3498 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3500 Buffer * buffer = documentBufferView()
3501 ? &(documentBufferView()->buffer()) : nullptr;
3503 switch (cmd.action()) {
3504 case LFUN_VC_REGISTER:
3505 if (!buffer || !ensureBufferClean(buffer))
3507 if (!buffer->lyxvc().inUse()) {
3508 if (buffer->lyxvc().registrer()) {
3509 reloadBuffer(*buffer);
3510 dr.clearMessageUpdate();
3515 case LFUN_VC_RENAME:
3516 case LFUN_VC_COPY: {
3517 if (!buffer || !ensureBufferClean(buffer))
3519 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3520 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3521 // Some changes are not yet committed.
3522 // We test here and not in getStatus(), since
3523 // this test is expensive.
3525 LyXVC::CommandResult ret =
3526 buffer->lyxvc().checkIn(log);
3528 if (ret == LyXVC::ErrorCommand ||
3529 ret == LyXVC::VCSuccess)
3530 reloadBuffer(*buffer);
3531 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3532 frontend::Alert::error(
3533 _("Revision control error."),
3534 _("Document could not be checked in."));
3538 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3539 LV_VC_RENAME : LV_VC_COPY;
3540 renameBuffer(*buffer, cmd.argument(), kind);
3545 case LFUN_VC_CHECK_IN:
3546 if (!buffer || !ensureBufferClean(buffer))
3548 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3550 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3552 // Only skip reloading if the checkin was cancelled or
3553 // an error occurred before the real checkin VCS command
3554 // was executed, since the VCS might have changed the
3555 // file even if it could not checkin successfully.
3556 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3557 reloadBuffer(*buffer);
3561 case LFUN_VC_CHECK_OUT:
3562 if (!buffer || !ensureBufferClean(buffer))
3564 if (buffer->lyxvc().inUse()) {
3565 dr.setMessage(buffer->lyxvc().checkOut());
3566 reloadBuffer(*buffer);
3570 case LFUN_VC_LOCKING_TOGGLE:
3571 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3573 if (buffer->lyxvc().inUse()) {
3574 string res = buffer->lyxvc().lockingToggle();
3576 frontend::Alert::error(_("Revision control error."),
3577 _("Error when setting the locking property."));
3580 reloadBuffer(*buffer);
3585 case LFUN_VC_REVERT:
3588 if (buffer->lyxvc().revert()) {
3589 reloadBuffer(*buffer);
3590 dr.clearMessageUpdate();
3594 case LFUN_VC_UNDO_LAST:
3597 buffer->lyxvc().undoLast();
3598 reloadBuffer(*buffer);
3599 dr.clearMessageUpdate();
3602 case LFUN_VC_REPO_UPDATE:
3605 if (ensureBufferClean(buffer)) {
3606 dr.setMessage(buffer->lyxvc().repoUpdate());
3607 checkExternallyModifiedBuffers();
3611 case LFUN_VC_COMMAND: {
3612 string flag = cmd.getArg(0);
3613 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3616 if (contains(flag, 'M')) {
3617 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3620 string path = cmd.getArg(1);
3621 if (contains(path, "$$p") && buffer)
3622 path = subst(path, "$$p", buffer->filePath());
3623 LYXERR(Debug::LYXVC, "Directory: " << path);
3625 if (!pp.isReadableDirectory()) {
3626 lyxerr << _("Directory is not accessible.") << endl;
3629 support::PathChanger p(pp);
3631 string command = cmd.getArg(2);
3632 if (command.empty())
3635 command = subst(command, "$$i", buffer->absFileName());
3636 command = subst(command, "$$p", buffer->filePath());
3638 command = subst(command, "$$m", to_utf8(message));
3639 LYXERR(Debug::LYXVC, "Command: " << command);
3641 one.startscript(Systemcall::Wait, command);
3645 if (contains(flag, 'I'))
3646 buffer->markDirty();
3647 if (contains(flag, 'R'))
3648 reloadBuffer(*buffer);
3653 case LFUN_VC_COMPARE: {
3654 if (cmd.argument().empty()) {
3655 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3661 string rev1 = cmd.getArg(0);
3665 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3668 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3669 f2 = buffer->absFileName();
3671 string rev2 = cmd.getArg(1);
3675 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3679 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3680 f1 << "\n" << f2 << "\n" );
3681 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3682 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3692 void GuiView::openChildDocument(string const & fname)
3694 LASSERT(documentBufferView(), return);
3695 Buffer & buffer = documentBufferView()->buffer();
3696 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3697 documentBufferView()->saveBookmark(false);
3698 Buffer * child = nullptr;
3699 if (theBufferList().exists(filename)) {
3700 child = theBufferList().getBuffer(filename);
3703 message(bformat(_("Opening child document %1$s..."),
3704 makeDisplayPath(filename.absFileName())));
3705 child = loadDocument(filename, false);
3707 // Set the parent name of the child document.
3708 // This makes insertion of citations and references in the child work,
3709 // when the target is in the parent or another child document.
3711 child->setParent(&buffer);
3715 bool GuiView::goToFileRow(string const & argument)
3719 size_t i = argument.find_last_of(' ');
3720 if (i != string::npos) {
3721 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3722 istringstream is(argument.substr(i + 1));
3727 if (i == string::npos) {
3728 LYXERR0("Wrong argument: " << argument);
3731 Buffer * buf = nullptr;
3732 string const realtmp = package().temp_dir().realPath();
3733 // We have to use os::path_prefix_is() here, instead of
3734 // simply prefixIs(), because the file name comes from
3735 // an external application and may need case adjustment.
3736 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3737 buf = theBufferList().getBufferFromTmp(file_name, true);
3738 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3739 << (buf ? " success" : " failed"));
3741 // Must replace extension of the file to be .lyx
3742 // and get full path
3743 FileName const s = fileSearch(string(),
3744 support::changeExtension(file_name, ".lyx"), "lyx");
3745 // Either change buffer or load the file
3746 if (theBufferList().exists(s))
3747 buf = theBufferList().getBuffer(s);
3748 else if (s.exists()) {
3749 buf = loadDocument(s);
3754 _("File does not exist: %1$s"),
3755 makeDisplayPath(file_name)));
3761 _("No buffer for file: %1$s."),
3762 makeDisplayPath(file_name))
3767 bool success = documentBufferView()->setCursorFromRow(row);
3769 LYXERR(Debug::LATEX,
3770 "setCursorFromRow: invalid position for row " << row);
3771 frontend::Alert::error(_("Inverse Search Failed"),
3772 _("Invalid position requested by inverse search.\n"
3773 "You may need to update the viewed document."));
3779 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3781 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3782 menu->exec(QCursor::pos());
3787 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3788 Buffer const * orig, Buffer * clone, string const & format)
3790 Buffer::ExportStatus const status = func(format);
3792 // the cloning operation will have produced a clone of the entire set of
3793 // documents, starting from the master. so we must delete those.
3794 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3796 busyBuffers.remove(orig);
3801 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3802 Buffer const * orig, Buffer * clone, string const & format)
3804 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3806 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3810 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3811 Buffer const * orig, Buffer * clone, string const & format)
3813 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3815 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3819 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3820 Buffer const * orig, Buffer * clone, string const & format)
3822 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3824 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3828 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3829 string const & argument,
3830 Buffer const * used_buffer,
3831 docstring const & msg,
3832 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3833 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3834 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3840 string format = argument;
3842 format = used_buffer->params().getDefaultOutputFormat();
3843 processing_format = format;
3845 progress_->clearMessages();
3848 #if EXPORT_in_THREAD
3850 GuiViewPrivate::busyBuffers.insert(used_buffer);
3851 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3852 if (!cloned_buffer) {
3853 Alert::error(_("Export Error"),
3854 _("Error cloning the Buffer."));
3857 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3862 setPreviewFuture(f);
3863 last_export_format = used_buffer->params().bufferFormat();
3866 // We are asynchronous, so we don't know here anything about the success
3869 Buffer::ExportStatus status;
3871 status = (used_buffer->*syncFunc)(format, false);
3872 } else if (previewFunc) {
3873 status = (used_buffer->*previewFunc)(format);
3876 handleExportStatus(gv_, status, format);
3878 return (status == Buffer::ExportSuccess
3879 || status == Buffer::PreviewSuccess);
3883 Buffer::ExportStatus status;
3885 status = (used_buffer->*syncFunc)(format, true);
3886 } else if (previewFunc) {
3887 status = (used_buffer->*previewFunc)(format);
3890 handleExportStatus(gv_, status, format);
3892 return (status == Buffer::ExportSuccess
3893 || status == Buffer::PreviewSuccess);
3897 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3899 BufferView * bv = currentBufferView();
3900 LASSERT(bv, return);
3902 // Let the current BufferView dispatch its own actions.
3903 bv->dispatch(cmd, dr);
3904 if (dr.dispatched()) {
3905 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3906 updateDialog("document", "");
3910 // Try with the document BufferView dispatch if any.
3911 BufferView * doc_bv = documentBufferView();
3912 if (doc_bv && doc_bv != bv) {
3913 doc_bv->dispatch(cmd, dr);
3914 if (dr.dispatched()) {
3915 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3916 updateDialog("document", "");
3921 // Then let the current Cursor dispatch its own actions.
3922 bv->cursor().dispatch(cmd);
3924 // update completion. We do it here and not in
3925 // processKeySym to avoid another redraw just for a
3926 // changed inline completion
3927 if (cmd.origin() == FuncRequest::KEYBOARD) {
3928 if (cmd.action() == LFUN_SELF_INSERT
3929 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3930 updateCompletion(bv->cursor(), true, true);
3931 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3932 updateCompletion(bv->cursor(), false, true);
3934 updateCompletion(bv->cursor(), false, false);
3937 dr = bv->cursor().result();
3941 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3943 BufferView * bv = currentBufferView();
3944 // By default we won't need any update.
3945 dr.screenUpdate(Update::None);
3946 // assume cmd will be dispatched
3947 dr.dispatched(true);
3949 Buffer * doc_buffer = documentBufferView()
3950 ? &(documentBufferView()->buffer()) : nullptr;
3952 if (cmd.origin() == FuncRequest::TOC) {
3953 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3954 toc->doDispatch(bv->cursor(), cmd, dr);
3958 string const argument = to_utf8(cmd.argument());
3960 switch(cmd.action()) {
3961 case LFUN_BUFFER_CHILD_OPEN:
3962 openChildDocument(to_utf8(cmd.argument()));
3965 case LFUN_BUFFER_IMPORT:
3966 importDocument(to_utf8(cmd.argument()));
3969 case LFUN_MASTER_BUFFER_EXPORT:
3971 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3973 case LFUN_BUFFER_EXPORT: {
3976 // GCC only sees strfwd.h when building merged
3977 if (::lyx::operator==(cmd.argument(), "custom")) {
3978 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3979 // so the following test should not be needed.
3980 // In principle, we could try to switch to such a view...
3981 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3982 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3986 string const dest = cmd.getArg(1);
3987 FileName target_dir;
3988 if (!dest.empty() && FileName::isAbsolute(dest))
3989 target_dir = FileName(support::onlyPath(dest));
3991 target_dir = doc_buffer->fileName().onlyPath();
3993 string const format = (argument.empty() || argument == "default") ?
3994 doc_buffer->params().getDefaultOutputFormat() : argument;
3996 if ((dest.empty() && doc_buffer->isUnnamed())
3997 || !target_dir.isDirWritable()) {
3998 exportBufferAs(*doc_buffer, from_utf8(format));
4001 /* TODO/Review: Is it a problem to also export the children?
4002 See the update_unincluded flag */
4003 d.asyncBufferProcessing(format,
4006 &GuiViewPrivate::exportAndDestroy,
4008 nullptr, cmd.allowAsync());
4009 // TODO Inform user about success
4013 case LFUN_BUFFER_EXPORT_AS: {
4014 LASSERT(doc_buffer, break);
4015 docstring f = cmd.argument();
4017 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4018 exportBufferAs(*doc_buffer, f);
4022 case LFUN_BUFFER_UPDATE: {
4023 d.asyncBufferProcessing(argument,
4026 &GuiViewPrivate::compileAndDestroy,
4028 nullptr, cmd.allowAsync());
4031 case LFUN_BUFFER_VIEW: {
4032 d.asyncBufferProcessing(argument,
4034 _("Previewing ..."),
4035 &GuiViewPrivate::previewAndDestroy,
4037 &Buffer::preview, cmd.allowAsync());
4040 case LFUN_MASTER_BUFFER_UPDATE: {
4041 d.asyncBufferProcessing(argument,
4042 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4044 &GuiViewPrivate::compileAndDestroy,
4046 nullptr, cmd.allowAsync());
4049 case LFUN_MASTER_BUFFER_VIEW: {
4050 d.asyncBufferProcessing(argument,
4051 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4053 &GuiViewPrivate::previewAndDestroy,
4054 nullptr, &Buffer::preview, cmd.allowAsync());
4057 case LFUN_EXPORT_CANCEL: {
4058 Systemcall::killscript();
4061 case LFUN_BUFFER_SWITCH: {
4062 string const file_name = to_utf8(cmd.argument());
4063 if (!FileName::isAbsolute(file_name)) {
4065 dr.setMessage(_("Absolute filename expected."));
4069 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4072 dr.setMessage(_("Document not loaded"));
4076 // Do we open or switch to the buffer in this view ?
4077 if (workArea(*buffer)
4078 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4083 // Look for the buffer in other views
4084 QList<int> const ids = guiApp->viewIds();
4086 for (; i != ids.size(); ++i) {
4087 GuiView & gv = guiApp->view(ids[i]);
4088 if (gv.workArea(*buffer)) {
4090 gv.activateWindow();
4092 gv.setBuffer(buffer);
4097 // If necessary, open a new window as a last resort
4098 if (i == ids.size()) {
4099 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4105 case LFUN_BUFFER_NEXT:
4106 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4109 case LFUN_BUFFER_MOVE_NEXT:
4110 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4113 case LFUN_BUFFER_PREVIOUS:
4114 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4117 case LFUN_BUFFER_MOVE_PREVIOUS:
4118 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4121 case LFUN_BUFFER_CHKTEX:
4122 LASSERT(doc_buffer, break);
4123 doc_buffer->runChktex();
4126 case LFUN_COMMAND_EXECUTE: {
4127 command_execute_ = true;
4128 minibuffer_focus_ = true;
4131 case LFUN_DROP_LAYOUTS_CHOICE:
4132 d.layout_->showPopup();
4135 case LFUN_MENU_OPEN:
4136 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4137 menu->exec(QCursor::pos());
4140 case LFUN_FILE_INSERT: {
4141 if (cmd.getArg(1) == "ignorelang")
4142 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4144 insertLyXFile(cmd.argument());
4148 case LFUN_FILE_INSERT_PLAINTEXT:
4149 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4150 string const fname = to_utf8(cmd.argument());
4151 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4152 dr.setMessage(_("Absolute filename expected."));
4156 FileName filename(fname);
4157 if (fname.empty()) {
4158 FileDialog dlg(qt_("Select file to insert"));
4160 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4161 QStringList(qt_("All Files (*)")));
4163 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4164 dr.setMessage(_("Canceled."));
4168 filename.set(fromqstr(result.second));
4172 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4173 bv->dispatch(new_cmd, dr);
4178 case LFUN_BUFFER_RELOAD: {
4179 LASSERT(doc_buffer, break);
4182 bool drop = (cmd.argument() == "dump");
4185 if (!drop && !doc_buffer->isClean()) {
4186 docstring const file =
4187 makeDisplayPath(doc_buffer->absFileName(), 20);
4188 if (doc_buffer->notifiesExternalModification()) {
4189 docstring text = _("The current version will be lost. "
4190 "Are you sure you want to load the version on disk "
4191 "of the document %1$s?");
4192 ret = Alert::prompt(_("Reload saved document?"),
4193 bformat(text, file), 1, 1,
4194 _("&Reload"), _("&Cancel"));
4196 docstring text = _("Any changes will be lost. "
4197 "Are you sure you want to revert to the saved version "
4198 "of the document %1$s?");
4199 ret = Alert::prompt(_("Revert to saved document?"),
4200 bformat(text, file), 1, 1,
4201 _("&Revert"), _("&Cancel"));
4206 doc_buffer->markClean();
4207 reloadBuffer(*doc_buffer);
4208 dr.forceBufferUpdate();
4213 case LFUN_BUFFER_RESET_EXPORT:
4214 LASSERT(doc_buffer, break);
4215 doc_buffer->requireFreshStart(true);
4216 dr.setMessage(_("Buffer export reset."));
4219 case LFUN_BUFFER_WRITE:
4220 LASSERT(doc_buffer, break);
4221 saveBuffer(*doc_buffer);
4224 case LFUN_BUFFER_WRITE_AS:
4225 LASSERT(doc_buffer, break);
4226 renameBuffer(*doc_buffer, cmd.argument());
4229 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4230 LASSERT(doc_buffer, break);
4231 renameBuffer(*doc_buffer, cmd.argument(),
4232 LV_WRITE_AS_TEMPLATE);
4235 case LFUN_BUFFER_WRITE_ALL: {
4236 Buffer * first = theBufferList().first();
4239 message(_("Saving all documents..."));
4240 // We cannot use a for loop as the buffer list cycles.
4243 if (!b->isClean()) {
4245 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4247 b = theBufferList().next(b);
4248 } while (b != first);
4249 dr.setMessage(_("All documents saved."));
4253 case LFUN_MASTER_BUFFER_FORALL: {
4257 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4258 funcToRun.allowAsync(false);
4260 for (Buffer const * buf : doc_buffer->allRelatives()) {
4261 // Switch to other buffer view and resend cmd
4262 lyx::dispatch(FuncRequest(
4263 LFUN_BUFFER_SWITCH, buf->absFileName()));
4264 lyx::dispatch(funcToRun);
4267 lyx::dispatch(FuncRequest(
4268 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4272 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4273 LASSERT(doc_buffer, break);
4274 doc_buffer->clearExternalModification();
4277 case LFUN_BUFFER_CLOSE:
4281 case LFUN_BUFFER_CLOSE_ALL:
4285 case LFUN_DEVEL_MODE_TOGGLE:
4286 devel_mode_ = !devel_mode_;
4288 dr.setMessage(_("Developer mode is now enabled."));
4290 dr.setMessage(_("Developer mode is now disabled."));
4293 case LFUN_TOOLBAR_TOGGLE: {
4294 string const name = cmd.getArg(0);
4295 if (GuiToolbar * t = toolbar(name))
4300 case LFUN_TOOLBAR_MOVABLE: {
4301 string const name = cmd.getArg(0);
4303 // toggle (all) toolbars movablility
4304 toolbarsMovable_ = !toolbarsMovable_;
4305 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4306 GuiToolbar * tb = toolbar(ti.name);
4307 if (tb && tb->isMovable() != toolbarsMovable_)
4308 // toggle toolbar movablity if it does not fit lock
4309 // (all) toolbars positions state silent = true, since
4310 // status bar notifications are slow
4313 if (toolbarsMovable_)
4314 dr.setMessage(_("Toolbars unlocked."));
4316 dr.setMessage(_("Toolbars locked."));
4317 } else if (GuiToolbar * t = toolbar(name)) {
4318 // toggle current toolbar movablity
4320 // update lock (all) toolbars positions
4321 updateLockToolbars();
4326 case LFUN_ICON_SIZE: {
4327 QSize size = d.iconSize(cmd.argument());
4329 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4330 size.width(), size.height()));
4334 case LFUN_DIALOG_UPDATE: {
4335 string const name = to_utf8(cmd.argument());
4336 if (name == "prefs" || name == "document")
4337 updateDialog(name, string());
4338 else if (name == "paragraph")
4339 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4340 else if (currentBufferView()) {
4341 Inset * inset = currentBufferView()->editedInset(name);
4342 // Can only update a dialog connected to an existing inset
4344 // FIXME: get rid of this indirection; GuiView ask the inset
4345 // if he is kind enough to update itself...
4346 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4347 //FIXME: pass DispatchResult here?
4348 inset->dispatch(currentBufferView()->cursor(), fr);
4354 case LFUN_DIALOG_TOGGLE: {
4355 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4356 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4357 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4361 case LFUN_DIALOG_DISCONNECT_INSET:
4362 disconnectDialog(to_utf8(cmd.argument()));
4365 case LFUN_DIALOG_HIDE: {
4366 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4370 case LFUN_DIALOG_SHOW: {
4371 string const name = cmd.getArg(0);
4372 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4374 if (name == "latexlog") {
4375 // getStatus checks that
4376 LASSERT(doc_buffer, break);
4377 Buffer::LogType type;
4378 string const logfile = doc_buffer->logName(&type);
4380 case Buffer::latexlog:
4383 case Buffer::buildlog:
4384 sdata = "literate ";
4387 sdata += Lexer::quoteString(logfile);
4388 showDialog("log", sdata);
4389 } else if (name == "vclog") {
4390 // getStatus checks that
4391 LASSERT(doc_buffer, break);
4392 string const sdata2 = "vc " +
4393 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4394 showDialog("log", sdata2);
4395 } else if (name == "symbols") {
4396 sdata = bv->cursor().getEncoding()->name();
4398 showDialog("symbols", sdata);
4400 } else if (name == "prefs" && isFullScreen()) {
4401 lfunUiToggle("fullscreen");
4402 showDialog("prefs", sdata);
4404 showDialog(name, sdata);
4409 dr.setMessage(cmd.argument());
4412 case LFUN_UI_TOGGLE: {
4413 string arg = cmd.getArg(0);
4414 if (!lfunUiToggle(arg)) {
4415 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4416 dr.setMessage(bformat(msg, from_utf8(arg)));
4418 // Make sure the keyboard focus stays in the work area.
4423 case LFUN_VIEW_SPLIT: {
4424 LASSERT(doc_buffer, break);
4425 string const orientation = cmd.getArg(0);
4426 d.splitter_->setOrientation(orientation == "vertical"
4427 ? Qt::Vertical : Qt::Horizontal);
4428 TabWorkArea * twa = addTabWorkArea();
4429 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4430 setCurrentWorkArea(wa);
4433 case LFUN_TAB_GROUP_CLOSE:
4434 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4435 closeTabWorkArea(twa);
4436 d.current_work_area_ = nullptr;
4437 twa = d.currentTabWorkArea();
4438 // Switch to the next GuiWorkArea in the found TabWorkArea.
4440 // Make sure the work area is up to date.
4441 setCurrentWorkArea(twa->currentWorkArea());
4443 setCurrentWorkArea(nullptr);
4448 case LFUN_VIEW_CLOSE:
4449 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4450 closeWorkArea(twa->currentWorkArea());
4451 d.current_work_area_ = nullptr;
4452 twa = d.currentTabWorkArea();
4453 // Switch to the next GuiWorkArea in the found TabWorkArea.
4455 // Make sure the work area is up to date.
4456 setCurrentWorkArea(twa->currentWorkArea());
4458 setCurrentWorkArea(nullptr);
4463 case LFUN_COMPLETION_INLINE:
4464 if (d.current_work_area_)
4465 d.current_work_area_->completer().showInline();
4468 case LFUN_COMPLETION_POPUP:
4469 if (d.current_work_area_)
4470 d.current_work_area_->completer().showPopup();
4475 if (d.current_work_area_)
4476 d.current_work_area_->completer().tab();
4479 case LFUN_COMPLETION_CANCEL:
4480 if (d.current_work_area_) {
4481 if (d.current_work_area_->completer().popupVisible())
4482 d.current_work_area_->completer().hidePopup();
4484 d.current_work_area_->completer().hideInline();
4488 case LFUN_COMPLETION_ACCEPT:
4489 if (d.current_work_area_)
4490 d.current_work_area_->completer().activate();
4493 case LFUN_BUFFER_ZOOM_IN:
4494 case LFUN_BUFFER_ZOOM_OUT:
4495 case LFUN_BUFFER_ZOOM: {
4496 if (cmd.argument().empty()) {
4497 if (cmd.action() == LFUN_BUFFER_ZOOM)
4499 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4504 if (cmd.action() == LFUN_BUFFER_ZOOM)
4505 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4506 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4507 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4509 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4512 // Actual zoom value: default zoom + fractional extra value
4513 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4514 if (zoom < static_cast<int>(zoom_min_))
4517 lyxrc.currentZoom = zoom;
4519 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4520 lyxrc.currentZoom, lyxrc.defaultZoom));
4522 guiApp->fontLoader().update();
4523 dr.screenUpdate(Update::Force | Update::FitCursor);
4527 case LFUN_VC_REGISTER:
4528 case LFUN_VC_RENAME:
4530 case LFUN_VC_CHECK_IN:
4531 case LFUN_VC_CHECK_OUT:
4532 case LFUN_VC_REPO_UPDATE:
4533 case LFUN_VC_LOCKING_TOGGLE:
4534 case LFUN_VC_REVERT:
4535 case LFUN_VC_UNDO_LAST:
4536 case LFUN_VC_COMMAND:
4537 case LFUN_VC_COMPARE:
4538 dispatchVC(cmd, dr);
4541 case LFUN_SERVER_GOTO_FILE_ROW:
4542 if(goToFileRow(to_utf8(cmd.argument())))
4543 dr.screenUpdate(Update::Force | Update::FitCursor);
4546 case LFUN_LYX_ACTIVATE:
4550 case LFUN_WINDOW_RAISE:
4556 case LFUN_FORWARD_SEARCH: {
4557 // it seems safe to assume we have a document buffer, since
4558 // getStatus wants one.
4559 LASSERT(doc_buffer, break);
4560 Buffer const * doc_master = doc_buffer->masterBuffer();
4561 FileName const path(doc_master->temppath());
4562 string const texname = doc_master->isChild(doc_buffer)
4563 ? DocFileName(changeExtension(
4564 doc_buffer->absFileName(),
4565 "tex")).mangledFileName()
4566 : doc_buffer->latexName();
4567 string const fulltexname =
4568 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4569 string const mastername =
4570 removeExtension(doc_master->latexName());
4571 FileName const dviname(addName(path.absFileName(),
4572 addExtension(mastername, "dvi")));
4573 FileName const pdfname(addName(path.absFileName(),
4574 addExtension(mastername, "pdf")));
4575 bool const have_dvi = dviname.exists();
4576 bool const have_pdf = pdfname.exists();
4577 if (!have_dvi && !have_pdf) {
4578 dr.setMessage(_("Please, preview the document first."));
4581 string outname = dviname.onlyFileName();
4582 string command = lyxrc.forward_search_dvi;
4583 if (!have_dvi || (have_pdf &&
4584 pdfname.lastModified() > dviname.lastModified())) {
4585 outname = pdfname.onlyFileName();
4586 command = lyxrc.forward_search_pdf;
4589 DocIterator cur = bv->cursor();
4590 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4591 LYXERR(Debug::ACTION, "Forward search: row:" << row
4593 if (row == -1 || command.empty()) {
4594 dr.setMessage(_("Couldn't proceed."));
4597 string texrow = convert<string>(row);
4599 command = subst(command, "$$n", texrow);
4600 command = subst(command, "$$f", fulltexname);
4601 command = subst(command, "$$t", texname);
4602 command = subst(command, "$$o", outname);
4604 volatile PathChanger p(path);
4606 one.startscript(Systemcall::DontWait, command);
4610 case LFUN_SPELLING_CONTINUOUSLY:
4611 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4612 dr.screenUpdate(Update::Force);
4615 case LFUN_CITATION_OPEN: {
4617 if (theFormats().getFormat("pdf"))
4618 pdfv = theFormats().getFormat("pdf")->viewer();
4619 if (theFormats().getFormat("ps"))
4620 psv = theFormats().getFormat("ps")->viewer();
4621 frontend::showTarget(argument, pdfv, psv);
4626 // The LFUN must be for one of BufferView, Buffer or Cursor;
4628 dispatchToBufferView(cmd, dr);
4632 // Part of automatic menu appearance feature.
4633 if (isFullScreen()) {
4634 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4638 // Need to update bv because many LFUNs here might have destroyed it
4639 bv = currentBufferView();
4641 // Clear non-empty selections
4642 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4644 Cursor & cur = bv->cursor();
4645 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4646 cur.clearSelection();
4652 bool GuiView::lfunUiToggle(string const & ui_component)
4654 if (ui_component == "scrollbar") {
4655 // hide() is of no help
4656 if (d.current_work_area_->verticalScrollBarPolicy() ==
4657 Qt::ScrollBarAlwaysOff)
4659 d.current_work_area_->setVerticalScrollBarPolicy(
4660 Qt::ScrollBarAsNeeded);
4662 d.current_work_area_->setVerticalScrollBarPolicy(
4663 Qt::ScrollBarAlwaysOff);
4664 } else if (ui_component == "statusbar") {
4665 statusBar()->setVisible(!statusBar()->isVisible());
4666 } else if (ui_component == "menubar") {
4667 menuBar()->setVisible(!menuBar()->isVisible());
4669 if (ui_component == "frame") {
4670 int const l = contentsMargins().left();
4672 //are the frames in default state?
4673 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4675 setContentsMargins(-2, -2, -2, -2);
4677 setContentsMargins(0, 0, 0, 0);
4680 if (ui_component == "fullscreen") {
4688 void GuiView::toggleFullScreen()
4690 setWindowState(windowState() ^ Qt::WindowFullScreen);
4694 Buffer const * GuiView::updateInset(Inset const * inset)
4699 Buffer const * inset_buffer = &(inset->buffer());
4701 for (int i = 0; i != d.splitter_->count(); ++i) {
4702 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4705 Buffer const * buffer = &(wa->bufferView().buffer());
4706 if (inset_buffer == buffer)
4707 wa->scheduleRedraw(true);
4709 return inset_buffer;
4713 void GuiView::restartCaret()
4715 /* When we move around, or type, it's nice to be able to see
4716 * the caret immediately after the keypress.
4718 if (d.current_work_area_)
4719 d.current_work_area_->startBlinkingCaret();
4721 // Take this occasion to update the other GUI elements.
4727 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4729 if (d.current_work_area_)
4730 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4735 // This list should be kept in sync with the list of insets in
4736 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4737 // dialog should have the same name as the inset.
4738 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4739 // docs in LyXAction.cpp.
4741 char const * const dialognames[] = {
4743 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4744 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4745 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4746 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4747 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4748 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4749 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4750 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4752 char const * const * const end_dialognames =
4753 dialognames + (sizeof(dialognames) / sizeof(char *));
4757 cmpCStr(char const * name) : name_(name) {}
4758 bool operator()(char const * other) {
4759 return strcmp(other, name_) == 0;
4766 bool isValidName(string const & name)
4768 return find_if(dialognames, end_dialognames,
4769 cmpCStr(name.c_str())) != end_dialognames;
4775 void GuiView::resetDialogs()
4777 // Make sure that no LFUN uses any GuiView.
4778 guiApp->setCurrentView(nullptr);
4782 constructToolbars();
4783 guiApp->menus().fillMenuBar(menuBar(), this, false);
4784 d.layout_->updateContents(true);
4785 // Now update controls with current buffer.
4786 guiApp->setCurrentView(this);
4792 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4794 for (QObject * child: widget->children()) {
4795 if (child->inherits("QGroupBox")) {
4796 QGroupBox * box = (QGroupBox*) child;
4799 flatGroupBoxes(child, flag);
4805 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4807 if (!isValidName(name))
4810 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4812 if (it != d.dialogs_.end()) {
4814 it->second->hideView();
4815 return it->second.get();
4818 Dialog * dialog = build(name);
4819 d.dialogs_[name].reset(dialog);
4820 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4821 // Force a uniform style for group boxes
4822 // On Mac non-flat works better, on Linux flat is standard
4823 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4825 if (lyxrc.allow_geometry_session)
4826 dialog->restoreSession();
4833 void GuiView::showDialog(string const & name, string const & sdata,
4836 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4840 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4846 const string name = fromqstr(qname);
4847 const string sdata = fromqstr(qdata);
4851 Dialog * dialog = findOrBuild(name, false);
4853 bool const visible = dialog->isVisibleView();
4854 dialog->showData(sdata);
4855 if (currentBufferView())
4856 currentBufferView()->editInset(name, inset);
4857 // We only set the focus to the new dialog if it was not yet
4858 // visible in order not to change the existing previous behaviour
4860 // activateWindow is needed for floating dockviews
4861 dialog->asQWidget()->raise();
4862 dialog->asQWidget()->activateWindow();
4863 dialog->asQWidget()->setFocus();
4867 catch (ExceptionMessage const &) {
4875 bool GuiView::isDialogVisible(string const & name) const
4877 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4878 if (it == d.dialogs_.end())
4880 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4884 void GuiView::hideDialog(string const & name, Inset * inset)
4886 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4887 if (it == d.dialogs_.end())
4891 if (!currentBufferView())
4893 if (inset != currentBufferView()->editedInset(name))
4897 Dialog * const dialog = it->second.get();
4898 if (dialog->isVisibleView())
4900 if (currentBufferView())
4901 currentBufferView()->editInset(name, nullptr);
4905 void GuiView::disconnectDialog(string const & name)
4907 if (!isValidName(name))
4909 if (currentBufferView())
4910 currentBufferView()->editInset(name, nullptr);
4914 void GuiView::hideAll() const
4916 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4917 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4919 for(; it != end; ++it)
4920 it->second->hideView();
4924 void GuiView::updateDialogs()
4926 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4927 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4929 for(; it != end; ++it) {
4930 Dialog * dialog = it->second.get();
4932 if (dialog->needBufferOpen() && !documentBufferView())
4933 hideDialog(fromqstr(dialog->name()), nullptr);
4934 else if (dialog->isVisibleView())
4935 dialog->checkStatus();
4942 Dialog * createDialog(GuiView & lv, string const & name);
4944 // will be replaced by a proper factory...
4945 Dialog * createGuiAbout(GuiView & lv);
4946 Dialog * createGuiBibtex(GuiView & lv);
4947 Dialog * createGuiChanges(GuiView & lv);
4948 Dialog * createGuiCharacter(GuiView & lv);
4949 Dialog * createGuiCitation(GuiView & lv);
4950 Dialog * createGuiCompare(GuiView & lv);
4951 Dialog * createGuiCompareHistory(GuiView & lv);
4952 Dialog * createGuiDelimiter(GuiView & lv);
4953 Dialog * createGuiDocument(GuiView & lv);
4954 Dialog * createGuiErrorList(GuiView & lv);
4955 Dialog * createGuiExternal(GuiView & lv);
4956 Dialog * createGuiGraphics(GuiView & lv);
4957 Dialog * createGuiInclude(GuiView & lv);
4958 Dialog * createGuiIndex(GuiView & lv);
4959 Dialog * createGuiListings(GuiView & lv);
4960 Dialog * createGuiLog(GuiView & lv);
4961 Dialog * createGuiLyXFiles(GuiView & lv);
4962 Dialog * createGuiMathMatrix(GuiView & lv);
4963 Dialog * createGuiNote(GuiView & lv);
4964 Dialog * createGuiParagraph(GuiView & lv);
4965 Dialog * createGuiPhantom(GuiView & lv);
4966 Dialog * createGuiPreferences(GuiView & lv);
4967 Dialog * createGuiPrint(GuiView & lv);
4968 Dialog * createGuiPrintindex(GuiView & lv);
4969 Dialog * createGuiRef(GuiView & lv);
4970 Dialog * createGuiSearch(GuiView & lv);
4971 Dialog * createGuiSearchAdv(GuiView & lv);
4972 Dialog * createGuiSendTo(GuiView & lv);
4973 Dialog * createGuiShowFile(GuiView & lv);
4974 Dialog * createGuiSpellchecker(GuiView & lv);
4975 Dialog * createGuiSymbols(GuiView & lv);
4976 Dialog * createGuiTabularCreate(GuiView & lv);
4977 Dialog * createGuiTexInfo(GuiView & lv);
4978 Dialog * createGuiToc(GuiView & lv);
4979 Dialog * createGuiThesaurus(GuiView & lv);
4980 Dialog * createGuiViewSource(GuiView & lv);
4981 Dialog * createGuiWrap(GuiView & lv);
4982 Dialog * createGuiProgressView(GuiView & lv);
4986 Dialog * GuiView::build(string const & name)
4988 LASSERT(isValidName(name), return nullptr);
4990 Dialog * dialog = createDialog(*this, name);
4994 if (name == "aboutlyx")
4995 return createGuiAbout(*this);
4996 if (name == "bibtex")
4997 return createGuiBibtex(*this);
4998 if (name == "changes")
4999 return createGuiChanges(*this);
5000 if (name == "character")
5001 return createGuiCharacter(*this);
5002 if (name == "citation")
5003 return createGuiCitation(*this);
5004 if (name == "compare")
5005 return createGuiCompare(*this);
5006 if (name == "comparehistory")
5007 return createGuiCompareHistory(*this);
5008 if (name == "document")
5009 return createGuiDocument(*this);
5010 if (name == "errorlist")
5011 return createGuiErrorList(*this);
5012 if (name == "external")
5013 return createGuiExternal(*this);
5015 return createGuiShowFile(*this);
5016 if (name == "findreplace")
5017 return createGuiSearch(*this);
5018 if (name == "findreplaceadv")
5019 return createGuiSearchAdv(*this);
5020 if (name == "graphics")
5021 return createGuiGraphics(*this);
5022 if (name == "include")
5023 return createGuiInclude(*this);
5024 if (name == "index")
5025 return createGuiIndex(*this);
5026 if (name == "index_print")
5027 return createGuiPrintindex(*this);
5028 if (name == "listings")
5029 return createGuiListings(*this);
5031 return createGuiLog(*this);
5032 if (name == "lyxfiles")
5033 return createGuiLyXFiles(*this);
5034 if (name == "mathdelimiter")
5035 return createGuiDelimiter(*this);
5036 if (name == "mathmatrix")
5037 return createGuiMathMatrix(*this);
5039 return createGuiNote(*this);
5040 if (name == "paragraph")
5041 return createGuiParagraph(*this);
5042 if (name == "phantom")
5043 return createGuiPhantom(*this);
5044 if (name == "prefs")
5045 return createGuiPreferences(*this);
5047 return createGuiRef(*this);
5048 if (name == "sendto")
5049 return createGuiSendTo(*this);
5050 if (name == "spellchecker")
5051 return createGuiSpellchecker(*this);
5052 if (name == "symbols")
5053 return createGuiSymbols(*this);
5054 if (name == "tabularcreate")
5055 return createGuiTabularCreate(*this);
5056 if (name == "texinfo")
5057 return createGuiTexInfo(*this);
5058 if (name == "thesaurus")
5059 return createGuiThesaurus(*this);
5061 return createGuiToc(*this);
5062 if (name == "view-source")
5063 return createGuiViewSource(*this);
5065 return createGuiWrap(*this);
5066 if (name == "progress")
5067 return createGuiProgressView(*this);
5073 SEMenu::SEMenu(QWidget * parent)
5075 QAction * action = addAction(qt_("Disable Shell Escape"));
5076 connect(action, SIGNAL(triggered()),
5077 parent, SLOT(disableShellEscape()));
5080 } // namespace frontend
5083 #include "moc_GuiView.cpp"