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 "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
116 // sync with GuiAlert.cpp
117 #define EXPORT_in_THREAD 1
120 #include "support/bind.h"
124 #ifdef HAVE_SYS_TIME_H
125 # include <sys/time.h>
133 using namespace lyx::support;
137 using support::addExtension;
138 using support::changeExtension;
139 using support::removeExtension;
145 class BackgroundWidget : public QWidget
148 BackgroundWidget(int width, int height)
149 : width_(width), height_(height)
151 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
152 if (!lyxrc.show_banner)
154 /// The text to be written on top of the pixmap
155 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
156 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
157 /// The text to be written on top of the pixmap
158 QString const text = lyx_version ?
159 qt_("version ") + lyx_version : qt_("unknown version");
160 #if QT_VERSION >= 0x050000
161 QString imagedir = "images/";
162 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
163 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
164 if (svgRenderer.isValid()) {
165 splash_ = QPixmap(splashSize());
166 QPainter painter(&splash_);
167 svgRenderer.render(&painter);
168 splash_.setDevicePixelRatio(pixelRatio());
170 splash_ = getPixmap("images/", "banner", "png");
173 splash_ = getPixmap("images/", "banner", "svgz,png");
176 QPainter pain(&splash_);
177 pain.setPen(QColor(0, 0, 0));
178 qreal const fsize = fontSize();
181 qreal locscale = htextsize.toFloat(&ok);
184 QPointF const position = textPosition(false);
185 QPointF const hposition = textPosition(true);
186 QRectF const hrect(hposition, splashSize());
188 "widget pixel ratio: " << pixelRatio() <<
189 " splash pixel ratio: " << splashPixelRatio() <<
190 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
192 // The font used to display the version info
193 font.setStyleHint(QFont::SansSerif);
194 font.setWeight(QFont::Bold);
195 font.setPointSizeF(fsize);
197 pain.drawText(position, text);
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Normal);
201 font.setPointSizeF(hfsize);
202 // Check how long the logo gets with the current font
203 // and adapt if the font is running wider than what
205 GuiFontMetrics fm(font);
206 // Split the title into lines to measure the longest line
207 // in the current l7n.
208 QStringList titlesegs = htext.split('\n');
210 int hline = fm.maxHeight();
211 QStringList::const_iterator sit;
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
616 statusBar()->addPermanentWidget(busylabel);
617 search_mode mode = theGuiApp()->imageSearchMode();
618 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
619 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
620 busylabel->setMovie(busyanim);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busylabel, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busylabel, SLOT(hide()));
628 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
633 // Small size slider for macOS to prevent the status bar from enlarging
634 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
635 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
636 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
638 zoom_slider_->setFixedWidth(fm.width('x') * 15);
640 // Make the defaultZoom center
641 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
642 // Initialize proper zoom value
644 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
645 // Actual zoom value: default zoom + fractional offset
646 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
647 if (zoom < static_cast<int>(zoom_min_))
649 zoom_slider_->setValue(zoom);
650 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
651 zoom_slider_->setTickPosition(QSlider::TicksBelow);
652 zoom_slider_->setTickInterval(lyxrc.defaultZoom - 10);
654 // Buttons to change zoom stepwise
655 zoom_in_ = new QPushButton(statusBar());
656 zoom_in_->setText("+");
657 zoom_in_->setFlat(true);
658 zoom_in_->setFixedSize(QSize(fm.height(), fm.height()));
659 zoom_out_ = new QPushButton(statusBar());
660 zoom_out_->setText(QString(0x2212));
661 zoom_out_->setFixedSize(QSize(fm.height(), fm.height()));
662 zoom_out_->setFlat(true);
664 statusBar()->addPermanentWidget(zoom_out_);
665 zoom_out_->setEnabled(currentBufferView());
666 statusBar()->addPermanentWidget(zoom_slider_);
667 zoom_slider_->setEnabled(currentBufferView());
668 zoom_in_->setEnabled(currentBufferView());
669 statusBar()->addPermanentWidget(zoom_in_);
671 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
672 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
673 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
674 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
675 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
677 zoom_value_ = new QLabel(statusBar());
678 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
679 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
680 zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
682 zoom_value_->setMinimumWidth(fm.width("000%"));
684 statusBar()->addPermanentWidget(zoom_value_);
685 zoom_value_->setEnabled(currentBufferView());
687 int const iconheight = max(int(d.normalIconSize), fm.height());
688 QSize const iconsize(iconheight, iconheight);
690 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
691 shell_escape_ = new QLabel(statusBar());
692 shell_escape_->setPixmap(shellescape);
693 shell_escape_->setScaledContents(true);
694 shell_escape_->setAlignment(Qt::AlignCenter);
695 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
696 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
697 "external commands for this document. "
698 "Right click to change."));
699 SEMenu * menu = new SEMenu(this);
700 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
701 menu, SLOT(showMenu(QPoint)));
702 shell_escape_->hide();
703 statusBar()->addPermanentWidget(shell_escape_);
705 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
706 read_only_ = new QLabel(statusBar());
707 read_only_->setPixmap(readonly);
708 read_only_->setScaledContents(true);
709 read_only_->setAlignment(Qt::AlignCenter);
711 statusBar()->addPermanentWidget(read_only_);
713 version_control_ = new QLabel(statusBar());
714 version_control_->setAlignment(Qt::AlignCenter);
715 version_control_->setFrameStyle(QFrame::StyledPanel);
716 version_control_->hide();
717 statusBar()->addPermanentWidget(version_control_);
719 statusBar()->setSizeGripEnabled(true);
722 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
723 SLOT(autoSaveThreadFinished()));
725 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
726 SLOT(processingThreadStarted()));
727 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
728 SLOT(processingThreadFinished()));
730 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
731 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
733 // set custom application bars context menu, e.g. tool bar and menu bar
734 setContextMenuPolicy(Qt::CustomContextMenu);
735 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
736 SLOT(toolBarPopup(const QPoint &)));
738 // Forbid too small unresizable window because it can happen
739 // with some window manager under X11.
740 setMinimumSize(300, 200);
742 if (lyxrc.allow_geometry_session) {
743 // Now take care of session management.
748 // no session handling, default to a sane size.
749 setGeometry(50, 50, 690, 510);
752 // clear session data if any.
753 settings.remove("views");
763 void GuiView::disableShellEscape()
765 BufferView * bv = documentBufferView();
768 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
769 bv->buffer().params().shell_escape = false;
770 bv->processUpdateFlags(Update::Force);
774 void GuiView::checkCancelBackground()
776 docstring const ttl = _("Cancel Export?");
777 docstring const msg = _("Do you want to cancel the background export process?");
779 Alert::prompt(ttl, msg, 1, 1,
780 _("&Cancel export"), _("Co&ntinue"));
782 Systemcall::killscript();
786 void GuiView::zoomSliderMoved(int value)
789 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
790 currentWorkArea()->scheduleRedraw(true);
791 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
795 void GuiView::zoomValueChanged(int value)
797 if (value != lyxrc.currentZoom)
798 zoomSliderMoved(value);
802 void GuiView::zoomInPressed()
805 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
806 currentWorkArea()->scheduleRedraw(true);
810 void GuiView::zoomOutPressed()
813 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
814 currentWorkArea()->scheduleRedraw(true);
818 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
820 QVector<GuiWorkArea*> areas;
821 for (int i = 0; i < tabWorkAreaCount(); i++) {
822 TabWorkArea* ta = tabWorkArea(i);
823 for (int u = 0; u < ta->count(); u++) {
824 areas << ta->workArea(u);
830 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
831 string const & format)
833 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
836 case Buffer::ExportSuccess:
837 msg = bformat(_("Successful export to format: %1$s"), fmt);
839 case Buffer::ExportCancel:
840 msg = _("Document export cancelled.");
842 case Buffer::ExportError:
843 case Buffer::ExportNoPathToFormat:
844 case Buffer::ExportTexPathHasSpaces:
845 case Buffer::ExportConverterError:
846 msg = bformat(_("Error while exporting format: %1$s"), fmt);
848 case Buffer::PreviewSuccess:
849 msg = bformat(_("Successful preview of format: %1$s"), fmt);
851 case Buffer::PreviewError:
852 msg = bformat(_("Error while previewing format: %1$s"), fmt);
854 case Buffer::ExportKilled:
855 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
862 void GuiView::processingThreadStarted()
867 void GuiView::processingThreadFinished()
869 QFutureWatcher<Buffer::ExportStatus> const * watcher =
870 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
872 Buffer::ExportStatus const status = watcher->result();
873 handleExportStatus(this, status, d.processing_format);
876 BufferView const * const bv = currentBufferView();
877 if (bv && !bv->buffer().errorList("Export").empty()) {
882 bool const error = (status != Buffer::ExportSuccess &&
883 status != Buffer::PreviewSuccess &&
884 status != Buffer::ExportCancel);
886 ErrorList & el = bv->buffer().errorList(d.last_export_format);
887 // at this point, we do not know if buffer-view or
888 // master-buffer-view was called. If there was an export error,
889 // and the current buffer's error log is empty, we guess that
890 // it must be master-buffer-view that was called so we set
892 errors(d.last_export_format, el.empty());
897 void GuiView::autoSaveThreadFinished()
899 QFutureWatcher<docstring> const * watcher =
900 static_cast<QFutureWatcher<docstring> const *>(sender());
901 message(watcher->result());
906 void GuiView::saveLayout() const
909 settings.setValue("zoom_ratio", zoom_ratio_);
910 settings.setValue("devel_mode", devel_mode_);
911 settings.beginGroup("views");
912 settings.beginGroup(QString::number(id_));
913 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
914 settings.setValue("pos", pos());
915 settings.setValue("size", size());
917 settings.setValue("geometry", saveGeometry());
918 settings.setValue("layout", saveState(0));
919 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
923 void GuiView::saveUISettings() const
927 // Save the toolbar private states
928 for (auto const & tb_p : d.toolbars_)
929 tb_p.second->saveSession(settings);
930 // Now take care of all other dialogs
931 for (auto const & dlg_p : d.dialogs_)
932 dlg_p.second->saveSession(settings);
936 void GuiView::setCurrentZoom(const int v)
938 lyxrc.currentZoom = v;
939 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
940 Q_EMIT currentZoomChanged(v);
944 bool GuiView::restoreLayout()
947 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
948 // Actual zoom value: default zoom + fractional offset
949 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
950 if (zoom < static_cast<int>(zoom_min_))
952 setCurrentZoom(zoom);
953 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
954 settings.beginGroup("views");
955 settings.beginGroup(QString::number(id_));
956 QString const icon_key = "icon_size";
957 if (!settings.contains(icon_key))
960 //code below is skipped when when ~/.config/LyX is (re)created
961 setIconSize(d.iconSize(settings.value(icon_key).toString()));
963 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
964 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
965 QSize size = settings.value("size", QSize(690, 510)).toSize();
969 // Work-around for bug #6034: the window ends up in an undetermined
970 // state when trying to restore a maximized window when it is
971 // already maximized.
972 if (!(windowState() & Qt::WindowMaximized))
973 if (!restoreGeometry(settings.value("geometry").toByteArray()))
974 setGeometry(50, 50, 690, 510);
977 // Make sure layout is correctly oriented.
978 setLayoutDirection(qApp->layoutDirection());
980 // Allow the toc and view-source dock widget to be restored if needed.
982 if ((dialog = findOrBuild("toc", true)))
983 // see bug 5082. At least setup title and enabled state.
984 // Visibility will be adjusted by restoreState below.
985 dialog->prepareView();
986 if ((dialog = findOrBuild("view-source", true)))
987 dialog->prepareView();
988 if ((dialog = findOrBuild("progress", true)))
989 dialog->prepareView();
991 if (!restoreState(settings.value("layout").toByteArray(), 0))
994 // init the toolbars that have not been restored
995 for (auto const & tb_p : guiApp->toolbars()) {
996 GuiToolbar * tb = toolbar(tb_p.name);
997 if (tb && !tb->isRestored())
998 initToolbar(tb_p.name);
1001 // update lock (all) toolbars positions
1002 updateLockToolbars();
1009 GuiToolbar * GuiView::toolbar(string const & name)
1011 ToolbarMap::iterator it = d.toolbars_.find(name);
1012 if (it != d.toolbars_.end())
1015 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1020 void GuiView::updateLockToolbars()
1022 toolbarsMovable_ = false;
1023 for (ToolbarInfo const & info : guiApp->toolbars()) {
1024 GuiToolbar * tb = toolbar(info.name);
1025 if (tb && tb->isMovable())
1026 toolbarsMovable_ = true;
1031 void GuiView::constructToolbars()
1033 for (auto const & tb_p : d.toolbars_)
1035 d.toolbars_.clear();
1037 // I don't like doing this here, but the standard toolbar
1038 // destroys this object when it's destroyed itself (vfr)
1039 d.layout_ = new LayoutBox(*this);
1040 d.stack_widget_->addWidget(d.layout_);
1041 d.layout_->move(0,0);
1043 // extracts the toolbars from the backend
1044 for (ToolbarInfo const & inf : guiApp->toolbars())
1045 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1049 void GuiView::initToolbars()
1051 // extracts the toolbars from the backend
1052 for (ToolbarInfo const & inf : guiApp->toolbars())
1053 initToolbar(inf.name);
1057 void GuiView::initToolbar(string const & name)
1059 GuiToolbar * tb = toolbar(name);
1062 int const visibility = guiApp->toolbars().defaultVisibility(name);
1063 bool newline = !(visibility & Toolbars::SAMEROW);
1064 tb->setVisible(false);
1065 tb->setVisibility(visibility);
1067 if (visibility & Toolbars::TOP) {
1069 addToolBarBreak(Qt::TopToolBarArea);
1070 addToolBar(Qt::TopToolBarArea, tb);
1073 if (visibility & Toolbars::BOTTOM) {
1075 addToolBarBreak(Qt::BottomToolBarArea);
1076 addToolBar(Qt::BottomToolBarArea, tb);
1079 if (visibility & Toolbars::LEFT) {
1081 addToolBarBreak(Qt::LeftToolBarArea);
1082 addToolBar(Qt::LeftToolBarArea, tb);
1085 if (visibility & Toolbars::RIGHT) {
1087 addToolBarBreak(Qt::RightToolBarArea);
1088 addToolBar(Qt::RightToolBarArea, tb);
1091 if (visibility & Toolbars::ON)
1092 tb->setVisible(true);
1094 tb->setMovable(true);
1098 TocModels & GuiView::tocModels()
1100 return d.toc_models_;
1104 void GuiView::setFocus()
1106 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1107 QMainWindow::setFocus();
1111 bool GuiView::hasFocus() const
1113 if (currentWorkArea())
1114 return currentWorkArea()->hasFocus();
1115 if (currentMainWorkArea())
1116 return currentMainWorkArea()->hasFocus();
1117 return d.bg_widget_->hasFocus();
1121 void GuiView::focusInEvent(QFocusEvent * e)
1123 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1124 QMainWindow::focusInEvent(e);
1125 // Make sure guiApp points to the correct view.
1126 guiApp->setCurrentView(this);
1127 if (currentWorkArea())
1128 currentWorkArea()->setFocus();
1129 else if (currentMainWorkArea())
1130 currentMainWorkArea()->setFocus();
1132 d.bg_widget_->setFocus();
1136 void GuiView::showEvent(QShowEvent * e)
1138 LYXERR(Debug::GUI, "Passed Geometry "
1139 << size().height() << "x" << size().width()
1140 << "+" << pos().x() << "+" << pos().y());
1142 if (d.splitter_->count() == 0)
1143 // No work area, switch to the background widget.
1147 QMainWindow::showEvent(e);
1151 bool GuiView::closeScheduled()
1158 bool GuiView::prepareAllBuffersForLogout()
1160 Buffer * first = theBufferList().first();
1164 // First, iterate over all buffers and ask the users if unsaved
1165 // changes should be saved.
1166 // We cannot use a for loop as the buffer list cycles.
1169 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1171 b = theBufferList().next(b);
1172 } while (b != first);
1174 // Next, save session state
1175 // When a view/window was closed before without quitting LyX, there
1176 // are already entries in the lastOpened list.
1177 theSession().lastOpened().clear();
1184 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1185 ** is responsibility of the container (e.g., dialog)
1187 void GuiView::closeEvent(QCloseEvent * close_event)
1189 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1191 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1192 Alert::warning(_("Exit LyX"),
1193 _("LyX could not be closed because documents are being processed by LyX."));
1194 close_event->setAccepted(false);
1198 // If the user pressed the x (so we didn't call closeView
1199 // programmatically), we want to clear all existing entries.
1201 theSession().lastOpened().clear();
1206 // it can happen that this event arrives without selecting the view,
1207 // e.g. when clicking the close button on a background window.
1209 if (!closeWorkAreaAll()) {
1211 close_event->ignore();
1215 // Make sure that nothing will use this to be closed View.
1216 guiApp->unregisterView(this);
1218 if (isFullScreen()) {
1219 // Switch off fullscreen before closing.
1224 // Make sure the timer time out will not trigger a statusbar update.
1225 d.statusbar_timer_.stop();
1227 // Saving fullscreen requires additional tweaks in the toolbar code.
1228 // It wouldn't also work under linux natively.
1229 if (lyxrc.allow_geometry_session) {
1234 close_event->accept();
1238 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1240 if (event->mimeData()->hasUrls())
1242 /// \todo Ask lyx-devel is this is enough:
1243 /// if (event->mimeData()->hasFormat("text/plain"))
1244 /// event->acceptProposedAction();
1248 void GuiView::dropEvent(QDropEvent * event)
1250 QList<QUrl> files = event->mimeData()->urls();
1251 if (files.isEmpty())
1254 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1255 for (int i = 0; i != files.size(); ++i) {
1256 string const file = os::internal_path(fromqstr(
1257 files.at(i).toLocalFile()));
1261 string const ext = support::getExtension(file);
1262 vector<const Format *> found_formats;
1264 // Find all formats that have the correct extension.
1265 for (const Format * fmt : theConverters().importableFormats())
1266 if (fmt->hasExtension(ext))
1267 found_formats.push_back(fmt);
1270 if (!found_formats.empty()) {
1271 if (found_formats.size() > 1) {
1272 //FIXME: show a dialog to choose the correct importable format
1273 LYXERR(Debug::FILES,
1274 "Multiple importable formats found, selecting first");
1276 string const arg = found_formats[0]->name() + " " + file;
1277 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1280 //FIXME: do we have to explicitly check whether it's a lyx file?
1281 LYXERR(Debug::FILES,
1282 "No formats found, trying to open it as a lyx file");
1283 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1285 // add the functions to the queue
1286 guiApp->addToFuncRequestQueue(cmd);
1289 // now process the collected functions. We perform the events
1290 // asynchronously. This prevents potential problems in case the
1291 // BufferView is closed within an event.
1292 guiApp->processFuncRequestQueueAsync();
1296 void GuiView::message(docstring const & str)
1298 if (ForkedProcess::iAmAChild())
1301 // call is moved to GUI-thread by GuiProgress
1302 d.progress_->appendMessage(toqstr(str));
1306 void GuiView::clearMessageText()
1308 message(docstring());
1312 void GuiView::updateStatusBarMessage(QString const & str)
1314 statusBar()->showMessage(str);
1315 d.statusbar_timer_.stop();
1316 d.statusbar_timer_.start(3000);
1320 void GuiView::clearMessage()
1322 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1323 // the hasFocus function mostly returns false, even if the focus is on
1324 // a workarea in this view.
1328 d.statusbar_timer_.stop();
1332 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1334 if (wa != d.current_work_area_
1335 || wa->bufferView().buffer().isInternal())
1337 Buffer const & buf = wa->bufferView().buffer();
1338 // Set the windows title
1339 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1340 if (buf.notifiesExternalModification()) {
1341 title = bformat(_("%1$s (modified externally)"), title);
1342 // If the external modification status has changed, then maybe the status of
1343 // buffer-save has changed too.
1347 title += from_ascii(" - LyX");
1349 setWindowTitle(toqstr(title));
1350 // Sets the path for the window: this is used by OSX to
1351 // allow a context click on the title bar showing a menu
1352 // with the path up to the file
1353 setWindowFilePath(toqstr(buf.absFileName()));
1354 // Tell Qt whether the current document is changed
1355 setWindowModified(!buf.isClean());
1357 if (buf.params().shell_escape)
1358 shell_escape_->show();
1360 shell_escape_->hide();
1362 if (buf.hasReadonlyFlag())
1367 if (buf.lyxvc().inUse()) {
1368 version_control_->show();
1369 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1371 version_control_->hide();
1375 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1377 if (d.current_work_area_)
1378 // disconnect the current work area from all slots
1379 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1381 disconnectBufferView();
1382 connectBufferView(wa->bufferView());
1383 connectBuffer(wa->bufferView().buffer());
1384 d.current_work_area_ = wa;
1385 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1386 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1387 QObject::connect(wa, SIGNAL(busy(bool)),
1388 this, SLOT(setBusy(bool)));
1389 // connection of a signal to a signal
1390 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1391 this, SIGNAL(bufferViewChanged()));
1392 Q_EMIT updateWindowTitle(wa);
1393 Q_EMIT bufferViewChanged();
1397 void GuiView::onBufferViewChanged()
1400 // Buffer-dependent dialogs must be updated. This is done here because
1401 // some dialogs require buffer()->text.
1403 zoom_slider_->setEnabled(currentBufferView());
1404 zoom_value_->setEnabled(currentBufferView());
1405 zoom_in_->setEnabled(currentBufferView());
1406 zoom_out_->setEnabled(currentBufferView());
1410 void GuiView::on_lastWorkAreaRemoved()
1413 // We already are in a close event. Nothing more to do.
1416 if (d.splitter_->count() > 1)
1417 // We have a splitter so don't close anything.
1420 // Reset and updates the dialogs.
1421 Q_EMIT bufferViewChanged();
1426 if (lyxrc.open_buffers_in_tabs)
1427 // Nothing more to do, the window should stay open.
1430 if (guiApp->viewIds().size() > 1) {
1436 // On Mac we also close the last window because the application stay
1437 // resident in memory. On other platforms we don't close the last
1438 // window because this would quit the application.
1444 void GuiView::updateStatusBar()
1446 // let the user see the explicit message
1447 if (d.statusbar_timer_.isActive())
1454 void GuiView::showMessage()
1458 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1459 if (msg.isEmpty()) {
1460 BufferView const * bv = currentBufferView();
1462 msg = toqstr(bv->cursor().currentState(devel_mode_));
1464 msg = qt_("Welcome to LyX!");
1466 statusBar()->showMessage(msg);
1470 bool GuiView::event(QEvent * e)
1474 // Useful debug code:
1475 //case QEvent::ActivationChange:
1476 //case QEvent::WindowDeactivate:
1477 //case QEvent::Paint:
1478 //case QEvent::Enter:
1479 //case QEvent::Leave:
1480 //case QEvent::HoverEnter:
1481 //case QEvent::HoverLeave:
1482 //case QEvent::HoverMove:
1483 //case QEvent::StatusTip:
1484 //case QEvent::DragEnter:
1485 //case QEvent::DragLeave:
1486 //case QEvent::Drop:
1489 case QEvent::WindowStateChange: {
1490 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1491 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1492 bool result = QMainWindow::event(e);
1493 bool nfstate = (windowState() & Qt::WindowFullScreen);
1494 if (!ofstate && nfstate) {
1495 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1496 // switch to full-screen state
1497 if (lyxrc.full_screen_statusbar)
1498 statusBar()->hide();
1499 if (lyxrc.full_screen_menubar)
1501 if (lyxrc.full_screen_toolbars) {
1502 for (auto const & tb_p : d.toolbars_)
1503 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1504 tb_p.second->hide();
1506 for (int i = 0; i != d.splitter_->count(); ++i)
1507 d.tabWorkArea(i)->setFullScreen(true);
1508 #if QT_VERSION > 0x050903
1509 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1510 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1512 setContentsMargins(-2, -2, -2, -2);
1514 hideDialogs("prefs", nullptr);
1515 } else if (ofstate && !nfstate) {
1516 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1517 // switch back from full-screen state
1518 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1519 statusBar()->show();
1520 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1522 if (lyxrc.full_screen_toolbars) {
1523 for (auto const & tb_p : d.toolbars_)
1524 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1525 tb_p.second->show();
1528 for (int i = 0; i != d.splitter_->count(); ++i)
1529 d.tabWorkArea(i)->setFullScreen(false);
1530 #if QT_VERSION > 0x050903
1531 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1533 setContentsMargins(0, 0, 0, 0);
1537 case QEvent::WindowActivate: {
1538 GuiView * old_view = guiApp->currentView();
1539 if (this == old_view) {
1541 return QMainWindow::event(e);
1543 if (old_view && old_view->currentBufferView()) {
1544 // save current selection to the selection buffer to allow
1545 // middle-button paste in this window.
1546 cap::saveSelection(old_view->currentBufferView()->cursor());
1548 guiApp->setCurrentView(this);
1549 if (d.current_work_area_)
1550 on_currentWorkAreaChanged(d.current_work_area_);
1554 return QMainWindow::event(e);
1557 case QEvent::ShortcutOverride: {
1559 if (isFullScreen() && menuBar()->isHidden()) {
1560 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1561 // FIXME: we should also try to detect special LyX shortcut such as
1562 // Alt-P and Alt-M. Right now there is a hack in
1563 // GuiWorkArea::processKeySym() that hides again the menubar for
1565 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1567 return QMainWindow::event(e);
1570 return QMainWindow::event(e);
1573 case QEvent::ApplicationPaletteChange: {
1574 // runtime switch from/to dark mode
1576 return QMainWindow::event(e);
1580 return QMainWindow::event(e);
1584 void GuiView::resetWindowTitle()
1586 setWindowTitle(qt_("LyX"));
1589 bool GuiView::focusNextPrevChild(bool /*next*/)
1596 bool GuiView::busy() const
1602 void GuiView::setBusy(bool busy)
1604 bool const busy_before = busy_ > 0;
1605 busy ? ++busy_ : --busy_;
1606 if ((busy_ > 0) == busy_before)
1607 // busy state didn't change
1611 QApplication::setOverrideCursor(Qt::WaitCursor);
1614 QApplication::restoreOverrideCursor();
1619 void GuiView::resetCommandExecute()
1621 command_execute_ = false;
1626 double GuiView::pixelRatio() const
1628 #if QT_VERSION >= 0x050000
1629 return qt_scale_factor * devicePixelRatio();
1636 GuiWorkArea * GuiView::workArea(int index)
1638 if (TabWorkArea * twa = d.currentTabWorkArea())
1639 if (index < twa->count())
1640 return twa->workArea(index);
1645 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1647 if (currentWorkArea()
1648 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1649 return currentWorkArea();
1650 if (TabWorkArea * twa = d.currentTabWorkArea())
1651 return twa->workArea(buffer);
1656 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1658 // Automatically create a TabWorkArea if there are none yet.
1659 TabWorkArea * tab_widget = d.splitter_->count()
1660 ? d.currentTabWorkArea() : addTabWorkArea();
1661 return tab_widget->addWorkArea(buffer, *this);
1665 TabWorkArea * GuiView::addTabWorkArea()
1667 TabWorkArea * twa = new TabWorkArea;
1668 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1669 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1670 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1671 this, SLOT(on_lastWorkAreaRemoved()));
1673 d.splitter_->addWidget(twa);
1674 d.stack_widget_->setCurrentWidget(d.splitter_);
1679 GuiWorkArea const * GuiView::currentWorkArea() const
1681 return d.current_work_area_;
1685 GuiWorkArea * GuiView::currentWorkArea()
1687 return d.current_work_area_;
1691 GuiWorkArea const * GuiView::currentMainWorkArea() const
1693 if (!d.currentTabWorkArea())
1695 return d.currentTabWorkArea()->currentWorkArea();
1699 GuiWorkArea * GuiView::currentMainWorkArea()
1701 if (!d.currentTabWorkArea())
1703 return d.currentTabWorkArea()->currentWorkArea();
1707 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1709 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1711 d.current_work_area_ = nullptr;
1713 Q_EMIT bufferViewChanged();
1717 // FIXME: I've no clue why this is here and why it accesses
1718 // theGuiApp()->currentView, which might be 0 (bug 6464).
1719 // See also 27525 (vfr).
1720 if (theGuiApp()->currentView() == this
1721 && theGuiApp()->currentView()->currentWorkArea() == wa)
1724 if (currentBufferView())
1725 cap::saveSelection(currentBufferView()->cursor());
1727 theGuiApp()->setCurrentView(this);
1728 d.current_work_area_ = wa;
1730 // We need to reset this now, because it will need to be
1731 // right if the tabWorkArea gets reset in the for loop. We
1732 // will change it back if we aren't in that case.
1733 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1734 d.current_main_work_area_ = wa;
1736 for (int i = 0; i != d.splitter_->count(); ++i) {
1737 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1738 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1739 << ", Current main wa: " << currentMainWorkArea());
1744 d.current_main_work_area_ = old_cmwa;
1746 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1747 on_currentWorkAreaChanged(wa);
1748 BufferView & bv = wa->bufferView();
1749 bv.cursor().fixIfBroken();
1751 wa->setUpdatesEnabled(true);
1752 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1756 void GuiView::removeWorkArea(GuiWorkArea * wa)
1758 LASSERT(wa, return);
1759 if (wa == d.current_work_area_) {
1761 disconnectBufferView();
1762 d.current_work_area_ = nullptr;
1763 d.current_main_work_area_ = nullptr;
1766 bool found_twa = false;
1767 for (int i = 0; i != d.splitter_->count(); ++i) {
1768 TabWorkArea * twa = d.tabWorkArea(i);
1769 if (twa->removeWorkArea(wa)) {
1770 // Found in this tab group, and deleted the GuiWorkArea.
1772 if (twa->count() != 0) {
1773 if (d.current_work_area_ == nullptr)
1774 // This means that we are closing the current GuiWorkArea, so
1775 // switch to the next GuiWorkArea in the found TabWorkArea.
1776 setCurrentWorkArea(twa->currentWorkArea());
1778 // No more WorkAreas in this tab group, so delete it.
1785 // It is not a tabbed work area (i.e., the search work area), so it
1786 // should be deleted by other means.
1787 LASSERT(found_twa, return);
1789 if (d.current_work_area_ == nullptr) {
1790 if (d.splitter_->count() != 0) {
1791 TabWorkArea * twa = d.currentTabWorkArea();
1792 setCurrentWorkArea(twa->currentWorkArea());
1794 // No more work areas, switch to the background widget.
1795 setCurrentWorkArea(nullptr);
1801 LayoutBox * GuiView::getLayoutDialog() const
1807 void GuiView::updateLayoutList()
1810 d.layout_->updateContents(false);
1814 void GuiView::updateToolbars()
1816 if (d.current_work_area_) {
1818 if (d.current_work_area_->bufferView().cursor().inMathed()
1819 && !d.current_work_area_->bufferView().cursor().inRegexped())
1820 context |= Toolbars::MATH;
1821 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1822 context |= Toolbars::TABLE;
1823 if (currentBufferView()->buffer().areChangesPresent()
1824 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1825 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1826 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1827 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1828 context |= Toolbars::REVIEW;
1829 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1830 context |= Toolbars::MATHMACROTEMPLATE;
1831 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1832 context |= Toolbars::IPA;
1833 if (command_execute_)
1834 context |= Toolbars::MINIBUFFER;
1835 if (minibuffer_focus_) {
1836 context |= Toolbars::MINIBUFFER_FOCUS;
1837 minibuffer_focus_ = false;
1840 for (auto const & tb_p : d.toolbars_)
1841 tb_p.second->update(context);
1843 for (auto const & tb_p : d.toolbars_)
1844 tb_p.second->update();
1848 void GuiView::refillToolbars()
1850 for (auto const & tb_p : d.toolbars_)
1851 tb_p.second->refill();
1855 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1857 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1858 LASSERT(newBuffer, return);
1860 GuiWorkArea * wa = workArea(*newBuffer);
1861 if (wa == nullptr) {
1863 newBuffer->masterBuffer()->updateBuffer();
1865 wa = addWorkArea(*newBuffer);
1866 // scroll to the position when the BufferView was last closed
1867 if (lyxrc.use_lastfilepos) {
1868 LastFilePosSection::FilePos filepos =
1869 theSession().lastFilePos().load(newBuffer->fileName());
1870 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1873 //Disconnect the old buffer...there's no new one.
1876 connectBuffer(*newBuffer);
1877 connectBufferView(wa->bufferView());
1879 setCurrentWorkArea(wa);
1883 void GuiView::connectBuffer(Buffer & buf)
1885 buf.setGuiDelegate(this);
1889 void GuiView::disconnectBuffer()
1891 if (d.current_work_area_)
1892 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1896 void GuiView::connectBufferView(BufferView & bv)
1898 bv.setGuiDelegate(this);
1902 void GuiView::disconnectBufferView()
1904 if (d.current_work_area_)
1905 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1909 void GuiView::errors(string const & error_type, bool from_master)
1911 BufferView const * const bv = currentBufferView();
1915 ErrorList const & el = from_master ?
1916 bv->buffer().masterBuffer()->errorList(error_type) :
1917 bv->buffer().errorList(error_type);
1922 string err = error_type;
1924 err = "from_master|" + error_type;
1925 showDialog("errorlist", err);
1929 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1931 d.toc_models_.updateItem(toqstr(type), dit);
1935 void GuiView::structureChanged()
1937 // This is called from the Buffer, which has no way to ensure that cursors
1938 // in BufferView remain valid.
1939 if (documentBufferView())
1940 documentBufferView()->cursor().sanitize();
1941 // FIXME: This is slightly expensive, though less than the tocBackend update
1942 // (#9880). This also resets the view in the Toc Widget (#6675).
1943 d.toc_models_.reset(documentBufferView());
1944 // Navigator needs more than a simple update in this case. It needs to be
1946 updateDialog("toc", "");
1950 void GuiView::updateDialog(string const & name, string const & sdata)
1952 if (!isDialogVisible(name))
1955 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1956 if (it == d.dialogs_.end())
1959 Dialog * const dialog = it->second.get();
1960 if (dialog->isVisibleView())
1961 dialog->initialiseParams(sdata);
1965 BufferView * GuiView::documentBufferView()
1967 return currentMainWorkArea()
1968 ? ¤tMainWorkArea()->bufferView()
1973 BufferView const * GuiView::documentBufferView() const
1975 return currentMainWorkArea()
1976 ? ¤tMainWorkArea()->bufferView()
1981 BufferView * GuiView::currentBufferView()
1983 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1987 BufferView const * GuiView::currentBufferView() const
1989 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1993 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1994 Buffer const * orig, Buffer * clone)
1996 bool const success = clone->autoSave();
1998 busyBuffers.remove(orig);
2000 ? _("Automatic save done.")
2001 : _("Automatic save failed!");
2005 void GuiView::autoSave()
2007 LYXERR(Debug::INFO, "Running autoSave()");
2009 Buffer * buffer = documentBufferView()
2010 ? &documentBufferView()->buffer() : nullptr;
2012 resetAutosaveTimers();
2016 GuiViewPrivate::busyBuffers.insert(buffer);
2017 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2018 buffer, buffer->cloneBufferOnly());
2019 d.autosave_watcher_.setFuture(f);
2020 resetAutosaveTimers();
2024 void GuiView::resetAutosaveTimers()
2027 d.autosave_timeout_.restart();
2031 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2034 Buffer * buf = currentBufferView()
2035 ? ¤tBufferView()->buffer() : nullptr;
2036 Buffer * doc_buffer = documentBufferView()
2037 ? &(documentBufferView()->buffer()) : nullptr;
2040 /* In LyX/Mac, when a dialog is open, the menus of the
2041 application can still be accessed without giving focus to
2042 the main window. In this case, we want to disable the menu
2043 entries that are buffer-related.
2044 This code must not be used on Linux and Windows, since it
2045 would disable buffer-related entries when hovering over the
2046 menu (see bug #9574).
2048 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2054 // Check whether we need a buffer
2055 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2056 // no, exit directly
2057 flag.message(from_utf8(N_("Command not allowed with"
2058 "out any document open")));
2059 flag.setEnabled(false);
2063 if (cmd.origin() == FuncRequest::TOC) {
2064 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2065 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2066 flag.setEnabled(false);
2070 switch(cmd.action()) {
2071 case LFUN_BUFFER_IMPORT:
2074 case LFUN_MASTER_BUFFER_EXPORT:
2076 && (doc_buffer->parent() != nullptr
2077 || doc_buffer->hasChildren())
2078 && !d.processing_thread_watcher_.isRunning()
2079 // this launches a dialog, which would be in the wrong Buffer
2080 && !(::lyx::operator==(cmd.argument(), "custom"));
2083 case LFUN_MASTER_BUFFER_UPDATE:
2084 case LFUN_MASTER_BUFFER_VIEW:
2086 && (doc_buffer->parent() != nullptr
2087 || doc_buffer->hasChildren())
2088 && !d.processing_thread_watcher_.isRunning();
2091 case LFUN_BUFFER_UPDATE:
2092 case LFUN_BUFFER_VIEW: {
2093 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2097 string format = to_utf8(cmd.argument());
2098 if (cmd.argument().empty())
2099 format = doc_buffer->params().getDefaultOutputFormat();
2100 enable = doc_buffer->params().isExportable(format, true);
2104 case LFUN_BUFFER_RELOAD:
2105 enable = doc_buffer && !doc_buffer->isUnnamed()
2106 && doc_buffer->fileName().exists()
2107 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2110 case LFUN_BUFFER_RESET_EXPORT:
2111 enable = doc_buffer != nullptr;
2114 case LFUN_BUFFER_CHILD_OPEN:
2115 enable = doc_buffer != nullptr;
2118 case LFUN_MASTER_BUFFER_FORALL: {
2119 if (doc_buffer == nullptr) {
2120 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2124 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2125 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2126 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2131 for (Buffer * buf : doc_buffer->allRelatives()) {
2132 GuiWorkArea * wa = workArea(*buf);
2135 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2136 enable = flag.enabled();
2143 case LFUN_BUFFER_WRITE:
2144 enable = doc_buffer && (doc_buffer->isUnnamed()
2145 || (!doc_buffer->isClean()
2146 || cmd.argument() == "force"));
2149 //FIXME: This LFUN should be moved to GuiApplication.
2150 case LFUN_BUFFER_WRITE_ALL: {
2151 // We enable the command only if there are some modified buffers
2152 Buffer * first = theBufferList().first();
2157 // We cannot use a for loop as the buffer list is a cycle.
2159 if (!b->isClean()) {
2163 b = theBufferList().next(b);
2164 } while (b != first);
2168 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2169 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2172 case LFUN_BUFFER_EXPORT: {
2173 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2177 return doc_buffer->getStatus(cmd, flag);
2180 case LFUN_BUFFER_EXPORT_AS:
2181 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2186 case LFUN_BUFFER_WRITE_AS:
2187 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2188 enable = doc_buffer != nullptr;
2191 case LFUN_EXPORT_CANCEL:
2192 enable = d.processing_thread_watcher_.isRunning();
2195 case LFUN_BUFFER_CLOSE:
2196 case LFUN_VIEW_CLOSE:
2197 enable = doc_buffer != nullptr;
2200 case LFUN_BUFFER_CLOSE_ALL:
2201 enable = theBufferList().last() != theBufferList().first();
2204 case LFUN_BUFFER_CHKTEX: {
2205 // hide if we have no checktex command
2206 if (lyxrc.chktex_command.empty()) {
2207 flag.setUnknown(true);
2211 if (!doc_buffer || !doc_buffer->params().isLatex()
2212 || d.processing_thread_watcher_.isRunning()) {
2213 // grey out, don't hide
2221 case LFUN_VIEW_SPLIT:
2222 if (cmd.getArg(0) == "vertical")
2223 enable = doc_buffer && (d.splitter_->count() == 1 ||
2224 d.splitter_->orientation() == Qt::Vertical);
2226 enable = doc_buffer && (d.splitter_->count() == 1 ||
2227 d.splitter_->orientation() == Qt::Horizontal);
2230 case LFUN_TAB_GROUP_CLOSE:
2231 enable = d.tabWorkAreaCount() > 1;
2234 case LFUN_DEVEL_MODE_TOGGLE:
2235 flag.setOnOff(devel_mode_);
2238 case LFUN_TOOLBAR_SET: {
2239 string const name = cmd.getArg(0);
2240 string const state = cmd.getArg(1);
2241 if (name.empty() || state.empty()) {
2243 docstring const msg =
2244 _("Function toolbar-set requires two arguments!");
2248 if (state != "on" && state != "off" && state != "auto") {
2250 docstring const msg =
2251 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2256 if (GuiToolbar * t = toolbar(name)) {
2257 bool const autovis = t->visibility() & Toolbars::AUTO;
2259 flag.setOnOff(t->isVisible() && !autovis);
2260 else if (state == "off")
2261 flag.setOnOff(!t->isVisible() && !autovis);
2262 else if (state == "auto")
2263 flag.setOnOff(autovis);
2266 docstring const msg =
2267 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2273 case LFUN_TOOLBAR_TOGGLE: {
2274 string const name = cmd.getArg(0);
2275 if (GuiToolbar * t = toolbar(name))
2276 flag.setOnOff(t->isVisible());
2279 docstring const msg =
2280 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2286 case LFUN_TOOLBAR_MOVABLE: {
2287 string const name = cmd.getArg(0);
2288 // use negation since locked == !movable
2290 // toolbar name * locks all toolbars
2291 flag.setOnOff(!toolbarsMovable_);
2292 else if (GuiToolbar * t = toolbar(name))
2293 flag.setOnOff(!(t->isMovable()));
2296 docstring const msg =
2297 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2303 case LFUN_ICON_SIZE:
2304 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2307 case LFUN_DROP_LAYOUTS_CHOICE:
2308 enable = buf != nullptr;
2311 case LFUN_UI_TOGGLE:
2312 flag.setOnOff(isFullScreen());
2315 case LFUN_DIALOG_DISCONNECT_INSET:
2318 case LFUN_DIALOG_HIDE:
2319 // FIXME: should we check if the dialog is shown?
2322 case LFUN_DIALOG_TOGGLE:
2323 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2326 case LFUN_DIALOG_SHOW: {
2327 string const name = cmd.getArg(0);
2329 enable = name == "aboutlyx"
2330 || name == "file" //FIXME: should be removed.
2331 || name == "lyxfiles"
2333 || name == "texinfo"
2334 || name == "progress"
2335 || name == "compare";
2336 else if (name == "character" || name == "symbols"
2337 || name == "mathdelimiter" || name == "mathmatrix") {
2338 if (!buf || buf->isReadonly())
2341 Cursor const & cur = currentBufferView()->cursor();
2342 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2345 else if (name == "latexlog")
2346 enable = FileName(doc_buffer->logName()).isReadableFile();
2347 else if (name == "spellchecker")
2348 enable = theSpellChecker()
2349 && !doc_buffer->text().empty();
2350 else if (name == "vclog")
2351 enable = doc_buffer->lyxvc().inUse();
2355 case LFUN_DIALOG_UPDATE: {
2356 string const name = cmd.getArg(0);
2358 enable = name == "prefs";
2362 case LFUN_COMMAND_EXECUTE:
2364 case LFUN_MENU_OPEN:
2365 // Nothing to check.
2368 case LFUN_COMPLETION_INLINE:
2369 if (!d.current_work_area_
2370 || !d.current_work_area_->completer().inlinePossible(
2371 currentBufferView()->cursor()))
2375 case LFUN_COMPLETION_POPUP:
2376 if (!d.current_work_area_
2377 || !d.current_work_area_->completer().popupPossible(
2378 currentBufferView()->cursor()))
2383 if (!d.current_work_area_
2384 || !d.current_work_area_->completer().inlinePossible(
2385 currentBufferView()->cursor()))
2389 case LFUN_COMPLETION_ACCEPT:
2390 if (!d.current_work_area_
2391 || (!d.current_work_area_->completer().popupVisible()
2392 && !d.current_work_area_->completer().inlineVisible()
2393 && !d.current_work_area_->completer().completionAvailable()))
2397 case LFUN_COMPLETION_CANCEL:
2398 if (!d.current_work_area_
2399 || (!d.current_work_area_->completer().popupVisible()
2400 && !d.current_work_area_->completer().inlineVisible()))
2404 case LFUN_BUFFER_ZOOM_OUT:
2405 case LFUN_BUFFER_ZOOM_IN: {
2406 // only diff between these two is that the default for ZOOM_OUT
2408 bool const neg_zoom =
2409 convert<int>(cmd.argument()) < 0 ||
2410 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2411 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2412 docstring const msg =
2413 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2417 enable = doc_buffer;
2421 case LFUN_BUFFER_ZOOM: {
2422 bool const less_than_min_zoom =
2423 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2424 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2425 docstring const msg =
2426 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2431 enable = doc_buffer;
2435 case LFUN_BUFFER_MOVE_NEXT:
2436 case LFUN_BUFFER_MOVE_PREVIOUS:
2437 // we do not cycle when moving
2438 case LFUN_BUFFER_NEXT:
2439 case LFUN_BUFFER_PREVIOUS:
2440 // because we cycle, it doesn't matter whether on first or last
2441 enable = (d.currentTabWorkArea()->count() > 1);
2443 case LFUN_BUFFER_SWITCH:
2444 // toggle on the current buffer, but do not toggle off
2445 // the other ones (is that a good idea?)
2447 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2448 flag.setOnOff(true);
2451 case LFUN_VC_REGISTER:
2452 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2454 case LFUN_VC_RENAME:
2455 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2458 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2460 case LFUN_VC_CHECK_IN:
2461 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2463 case LFUN_VC_CHECK_OUT:
2464 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2466 case LFUN_VC_LOCKING_TOGGLE:
2467 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2468 && doc_buffer->lyxvc().lockingToggleEnabled();
2469 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2471 case LFUN_VC_REVERT:
2472 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2473 && !doc_buffer->hasReadonlyFlag();
2475 case LFUN_VC_UNDO_LAST:
2476 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2478 case LFUN_VC_REPO_UPDATE:
2479 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2481 case LFUN_VC_COMMAND: {
2482 if (cmd.argument().empty())
2484 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2488 case LFUN_VC_COMPARE:
2489 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2492 case LFUN_SERVER_GOTO_FILE_ROW:
2493 case LFUN_LYX_ACTIVATE:
2494 case LFUN_WINDOW_RAISE:
2496 case LFUN_FORWARD_SEARCH:
2497 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2500 case LFUN_FILE_INSERT_PLAINTEXT:
2501 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2502 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2505 case LFUN_SPELLING_CONTINUOUSLY:
2506 flag.setOnOff(lyxrc.spellcheck_continuously);
2509 case LFUN_CITATION_OPEN:
2518 flag.setEnabled(false);
2524 static FileName selectTemplateFile()
2526 FileDialog dlg(qt_("Select template file"));
2527 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2528 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2530 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2531 QStringList(qt_("LyX Documents (*.lyx)")));
2533 if (result.first == FileDialog::Later)
2535 if (result.second.isEmpty())
2537 return FileName(fromqstr(result.second));
2541 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2545 Buffer * newBuffer = nullptr;
2547 newBuffer = checkAndLoadLyXFile(filename);
2548 } catch (ExceptionMessage const &) {
2555 message(_("Document not loaded."));
2559 setBuffer(newBuffer);
2560 newBuffer->errors("Parse");
2563 theSession().lastFiles().add(filename);
2564 theSession().writeFile();
2571 void GuiView::openDocument(string const & fname)
2573 string initpath = lyxrc.document_path;
2575 if (documentBufferView()) {
2576 string const trypath = documentBufferView()->buffer().filePath();
2577 // If directory is writeable, use this as default.
2578 if (FileName(trypath).isDirWritable())
2584 if (fname.empty()) {
2585 FileDialog dlg(qt_("Select document to open"));
2586 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2587 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2589 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2590 FileDialog::Result result =
2591 dlg.open(toqstr(initpath), filter);
2593 if (result.first == FileDialog::Later)
2596 filename = fromqstr(result.second);
2598 // check selected filename
2599 if (filename.empty()) {
2600 message(_("Canceled."));
2606 // get absolute path of file and add ".lyx" to the filename if
2608 FileName const fullname =
2609 fileSearch(string(), filename, "lyx", support::may_not_exist);
2610 if (!fullname.empty())
2611 filename = fullname.absFileName();
2613 if (!fullname.onlyPath().isDirectory()) {
2614 Alert::warning(_("Invalid filename"),
2615 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2616 from_utf8(fullname.absFileName())));
2620 // if the file doesn't exist and isn't already open (bug 6645),
2621 // let the user create one
2622 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2623 !LyXVC::file_not_found_hook(fullname)) {
2624 // the user specifically chose this name. Believe him.
2625 Buffer * const b = newFile(filename, string(), true);
2631 docstring const disp_fn = makeDisplayPath(filename);
2632 message(bformat(_("Opening document %1$s..."), disp_fn));
2635 Buffer * buf = loadDocument(fullname);
2637 str2 = bformat(_("Document %1$s opened."), disp_fn);
2638 if (buf->lyxvc().inUse())
2639 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2640 " " + _("Version control detected.");
2642 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2647 // FIXME: clean that
2648 static bool import(GuiView * lv, FileName const & filename,
2649 string const & format, ErrorList & errorList)
2651 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2653 string loader_format;
2654 vector<string> loaders = theConverters().loaders();
2655 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2656 for (string const & loader : loaders) {
2657 if (!theConverters().isReachable(format, loader))
2660 string const tofile =
2661 support::changeExtension(filename.absFileName(),
2662 theFormats().extension(loader));
2663 if (theConverters().convert(nullptr, filename, FileName(tofile),
2664 filename, format, loader, errorList) != Converters::SUCCESS)
2666 loader_format = loader;
2669 if (loader_format.empty()) {
2670 frontend::Alert::error(_("Couldn't import file"),
2671 bformat(_("No information for importing the format %1$s."),
2672 translateIfPossible(theFormats().prettyName(format))));
2676 loader_format = format;
2678 if (loader_format == "lyx") {
2679 Buffer * buf = lv->loadDocument(lyxfile);
2683 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2687 bool as_paragraphs = loader_format == "textparagraph";
2688 string filename2 = (loader_format == format) ? filename.absFileName()
2689 : support::changeExtension(filename.absFileName(),
2690 theFormats().extension(loader_format));
2691 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2693 guiApp->setCurrentView(lv);
2694 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2701 void GuiView::importDocument(string const & argument)
2704 string filename = split(argument, format, ' ');
2706 LYXERR(Debug::INFO, format << " file: " << filename);
2708 // need user interaction
2709 if (filename.empty()) {
2710 string initpath = lyxrc.document_path;
2711 if (documentBufferView()) {
2712 string const trypath = documentBufferView()->buffer().filePath();
2713 // If directory is writeable, use this as default.
2714 if (FileName(trypath).isDirWritable())
2718 docstring const text = bformat(_("Select %1$s file to import"),
2719 translateIfPossible(theFormats().prettyName(format)));
2721 FileDialog dlg(toqstr(text));
2722 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2723 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2725 docstring filter = translateIfPossible(theFormats().prettyName(format));
2728 filter += from_utf8(theFormats().extensions(format));
2731 FileDialog::Result result =
2732 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2734 if (result.first == FileDialog::Later)
2737 filename = fromqstr(result.second);
2739 // check selected filename
2740 if (filename.empty())
2741 message(_("Canceled."));
2744 if (filename.empty())
2747 // get absolute path of file
2748 FileName const fullname(support::makeAbsPath(filename));
2750 // Can happen if the user entered a path into the dialog
2752 if (fullname.onlyFileName().empty()) {
2753 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2754 "Aborting import."),
2755 from_utf8(fullname.absFileName()));
2756 frontend::Alert::error(_("File name error"), msg);
2757 message(_("Canceled."));
2762 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2764 // Check if the document already is open
2765 Buffer * buf = theBufferList().getBuffer(lyxfile);
2768 if (!closeBuffer()) {
2769 message(_("Canceled."));
2774 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2776 // if the file exists already, and we didn't do
2777 // -i lyx thefile.lyx, warn
2778 if (lyxfile.exists() && fullname != lyxfile) {
2780 docstring text = bformat(_("The document %1$s already exists.\n\n"
2781 "Do you want to overwrite that document?"), displaypath);
2782 int const ret = Alert::prompt(_("Overwrite document?"),
2783 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2786 message(_("Canceled."));
2791 message(bformat(_("Importing %1$s..."), displaypath));
2792 ErrorList errorList;
2793 if (import(this, fullname, format, errorList))
2794 message(_("imported."));
2796 message(_("file not imported!"));
2798 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2802 void GuiView::newDocument(string const & filename, string templatefile,
2805 FileName initpath(lyxrc.document_path);
2806 if (documentBufferView()) {
2807 FileName const trypath(documentBufferView()->buffer().filePath());
2808 // If directory is writeable, use this as default.
2809 if (trypath.isDirWritable())
2813 if (from_template) {
2814 if (templatefile.empty())
2815 templatefile = selectTemplateFile().absFileName();
2816 if (templatefile.empty())
2821 if (filename.empty())
2822 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2824 b = newFile(filename, templatefile, true);
2829 // If no new document could be created, it is unsure
2830 // whether there is a valid BufferView.
2831 if (currentBufferView())
2832 // Ensure the cursor is correctly positioned on screen.
2833 currentBufferView()->showCursor();
2837 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2839 BufferView * bv = documentBufferView();
2844 FileName filename(to_utf8(fname));
2845 if (filename.empty()) {
2846 // Launch a file browser
2848 string initpath = lyxrc.document_path;
2849 string const trypath = bv->buffer().filePath();
2850 // If directory is writeable, use this as default.
2851 if (FileName(trypath).isDirWritable())
2855 FileDialog dlg(qt_("Select LyX document to insert"));
2856 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2857 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2859 FileDialog::Result result = dlg.open(toqstr(initpath),
2860 QStringList(qt_("LyX Documents (*.lyx)")));
2862 if (result.first == FileDialog::Later)
2866 filename.set(fromqstr(result.second));
2868 // check selected filename
2869 if (filename.empty()) {
2870 // emit message signal.
2871 message(_("Canceled."));
2876 bv->insertLyXFile(filename, ignorelang);
2877 bv->buffer().errors("Parse");
2882 string const GuiView::getTemplatesPath(Buffer & b)
2884 // We start off with the user's templates path
2885 string result = addPath(package().user_support().absFileName(), "templates");
2886 // Check for the document language
2887 string const langcode = b.params().language->code();
2888 string const shortcode = langcode.substr(0, 2);
2889 if (!langcode.empty() && shortcode != "en") {
2890 string subpath = addPath(result, shortcode);
2891 string subpath_long = addPath(result, langcode);
2892 // If we have a subdirectory for the language already,
2894 FileName sp = FileName(subpath);
2895 if (sp.isDirectory())
2897 else if (FileName(subpath_long).isDirectory())
2898 result = subpath_long;
2900 // Ask whether we should create such a subdirectory
2901 docstring const text =
2902 bformat(_("It is suggested to save the template in a subdirectory\n"
2903 "appropriate to the document language (%1$s).\n"
2904 "This subdirectory does not exists yet.\n"
2905 "Do you want to create it?"),
2906 _(b.params().language->display()));
2907 if (Alert::prompt(_("Create Language Directory?"),
2908 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2909 // If the user agreed, we try to create it and report if this failed.
2910 if (!sp.createDirectory(0777))
2911 Alert::error(_("Subdirectory creation failed!"),
2912 _("Could not create subdirectory.\n"
2913 "The template will be saved in the parent directory."));
2919 // Do we have a layout category?
2920 string const cat = b.params().baseClass() ?
2921 b.params().baseClass()->category()
2924 string subpath = addPath(result, cat);
2925 // If we have a subdirectory for the category already,
2927 FileName sp = FileName(subpath);
2928 if (sp.isDirectory())
2931 // Ask whether we should create such a subdirectory
2932 docstring const text =
2933 bformat(_("It is suggested to save the template in a subdirectory\n"
2934 "appropriate to the layout category (%1$s).\n"
2935 "This subdirectory does not exists yet.\n"
2936 "Do you want to create it?"),
2938 if (Alert::prompt(_("Create Category Directory?"),
2939 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2940 // If the user agreed, we try to create it and report if this failed.
2941 if (!sp.createDirectory(0777))
2942 Alert::error(_("Subdirectory creation failed!"),
2943 _("Could not create subdirectory.\n"
2944 "The template will be saved in the parent directory."));
2954 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2956 FileName fname = b.fileName();
2957 FileName const oldname = fname;
2958 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2960 if (!newname.empty()) {
2963 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2965 fname = support::makeAbsPath(to_utf8(newname),
2966 oldname.onlyPath().absFileName());
2968 // Switch to this Buffer.
2971 // No argument? Ask user through dialog.
2973 QString const title = as_template ? qt_("Choose a filename to save template as")
2974 : qt_("Choose a filename to save document as");
2975 FileDialog dlg(title);
2976 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2977 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2979 if (!isLyXFileName(fname.absFileName()))
2980 fname.changeExtension(".lyx");
2982 string const path = as_template ?
2984 : fname.onlyPath().absFileName();
2985 FileDialog::Result result =
2986 dlg.save(toqstr(path),
2987 QStringList(qt_("LyX Documents (*.lyx)")),
2988 toqstr(fname.onlyFileName()));
2990 if (result.first == FileDialog::Later)
2993 fname.set(fromqstr(result.second));
2998 if (!isLyXFileName(fname.absFileName()))
2999 fname.changeExtension(".lyx");
3002 // fname is now the new Buffer location.
3004 // if there is already a Buffer open with this name, we do not want
3005 // to have another one. (the second test makes sure we're not just
3006 // trying to overwrite ourselves, which is fine.)
3007 if (theBufferList().exists(fname) && fname != oldname
3008 && theBufferList().getBuffer(fname) != &b) {
3009 docstring const text =
3010 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3011 "Please close it before attempting to overwrite it.\n"
3012 "Do you want to choose a new filename?"),
3013 from_utf8(fname.absFileName()));
3014 int const ret = Alert::prompt(_("Chosen File Already Open"),
3015 text, 0, 1, _("&Rename"), _("&Cancel"));
3017 case 0: return renameBuffer(b, docstring(), kind);
3018 case 1: return false;
3023 bool const existsLocal = fname.exists();
3024 bool const existsInVC = LyXVC::fileInVC(fname);
3025 if (existsLocal || existsInVC) {
3026 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3027 if (kind != LV_WRITE_AS && existsInVC) {
3028 // renaming to a name that is already in VC
3030 docstring text = bformat(_("The document %1$s "
3031 "is already registered.\n\n"
3032 "Do you want to choose a new name?"),
3034 docstring const title = (kind == LV_VC_RENAME) ?
3035 _("Rename document?") : _("Copy document?");
3036 docstring const button = (kind == LV_VC_RENAME) ?
3037 _("&Rename") : _("&Copy");
3038 int const ret = Alert::prompt(title, text, 0, 1,
3039 button, _("&Cancel"));
3041 case 0: return renameBuffer(b, docstring(), kind);
3042 case 1: return false;
3047 docstring text = bformat(_("The document %1$s "
3048 "already exists.\n\n"
3049 "Do you want to overwrite that document?"),
3051 int const ret = Alert::prompt(_("Overwrite document?"),
3052 text, 0, 2, _("&Overwrite"),
3053 _("&Rename"), _("&Cancel"));
3056 case 1: return renameBuffer(b, docstring(), kind);
3057 case 2: return false;
3063 case LV_VC_RENAME: {
3064 string msg = b.lyxvc().rename(fname);
3067 message(from_utf8(msg));
3071 string msg = b.lyxvc().copy(fname);
3074 message(from_utf8(msg));
3078 case LV_WRITE_AS_TEMPLATE:
3081 // LyXVC created the file already in case of LV_VC_RENAME or
3082 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3083 // relative paths of included stuff right if we moved e.g. from
3084 // /a/b.lyx to /a/c/b.lyx.
3086 bool const saved = saveBuffer(b, fname);
3093 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3095 FileName fname = b.fileName();
3097 FileDialog dlg(qt_("Choose a filename to export the document as"));
3098 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3101 QString const anyformat = qt_("Guess from extension (*.*)");
3104 vector<Format const *> export_formats;
3105 for (Format const & f : theFormats())
3106 if (f.documentFormat())
3107 export_formats.push_back(&f);
3108 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3109 map<QString, string> fmap;
3112 for (Format const * f : export_formats) {
3113 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3114 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3116 from_ascii(f->extension())));
3117 types << loc_filter;
3118 fmap[loc_filter] = f->name();
3119 if (from_ascii(f->name()) == iformat) {
3120 filter = loc_filter;
3121 ext = f->extension();
3124 string ofname = fname.onlyFileName();
3126 ofname = support::changeExtension(ofname, ext);
3127 FileDialog::Result result =
3128 dlg.save(toqstr(fname.onlyPath().absFileName()),
3132 if (result.first != FileDialog::Chosen)
3136 fname.set(fromqstr(result.second));
3137 if (filter == anyformat)
3138 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3140 fmt_name = fmap[filter];
3141 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3142 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3144 if (fmt_name.empty() || fname.empty())
3147 // fname is now the new Buffer location.
3148 if (FileName(fname).exists()) {
3149 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3150 docstring text = bformat(_("The document %1$s already "
3151 "exists.\n\nDo you want to "
3152 "overwrite that document?"),
3154 int const ret = Alert::prompt(_("Overwrite document?"),
3155 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3158 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3159 case 2: return false;
3163 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3166 return dr.dispatched();
3170 bool GuiView::saveBuffer(Buffer & b)
3172 return saveBuffer(b, FileName());
3176 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3178 if (workArea(b) && workArea(b)->inDialogMode())
3181 if (fn.empty() && b.isUnnamed())
3182 return renameBuffer(b, docstring());
3184 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3186 theSession().lastFiles().add(b.fileName());
3187 theSession().writeFile();
3191 // Switch to this Buffer.
3194 // FIXME: we don't tell the user *WHY* the save failed !!
3195 docstring const file = makeDisplayPath(b.absFileName(), 30);
3196 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3197 "Do you want to rename the document and "
3198 "try again?"), file);
3199 int const ret = Alert::prompt(_("Rename and save?"),
3200 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3203 if (!renameBuffer(b, docstring()))
3212 return saveBuffer(b, fn);
3216 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3218 return closeWorkArea(wa, false);
3222 // We only want to close the buffer if it is not visible in other workareas
3223 // of the same view, nor in other views, and if this is not a child
3224 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3226 Buffer & buf = wa->bufferView().buffer();
3228 bool last_wa = d.countWorkAreasOf(buf) == 1
3229 && !inOtherView(buf) && !buf.parent();
3231 bool close_buffer = last_wa;
3234 if (lyxrc.close_buffer_with_last_view == "yes")
3236 else if (lyxrc.close_buffer_with_last_view == "no")
3237 close_buffer = false;
3240 if (buf.isUnnamed())
3241 file = from_utf8(buf.fileName().onlyFileName());
3243 file = buf.fileName().displayName(30);
3244 docstring const text = bformat(
3245 _("Last view on document %1$s is being closed.\n"
3246 "Would you like to close or hide the document?\n"
3248 "Hidden documents can be displayed back through\n"
3249 "the menu: View->Hidden->...\n"
3251 "To remove this question, set your preference in:\n"
3252 " Tools->Preferences->Look&Feel->UserInterface\n"
3254 int ret = Alert::prompt(_("Close or hide document?"),
3255 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3258 close_buffer = (ret == 0);
3262 return closeWorkArea(wa, close_buffer);
3266 bool GuiView::closeBuffer()
3268 GuiWorkArea * wa = currentMainWorkArea();
3269 // coverity complained about this
3270 // it seems unnecessary, but perhaps is worth the check
3271 LASSERT(wa, return false);
3273 setCurrentWorkArea(wa);
3274 Buffer & buf = wa->bufferView().buffer();
3275 return closeWorkArea(wa, !buf.parent());
3279 void GuiView::writeSession() const {
3280 GuiWorkArea const * active_wa = currentMainWorkArea();
3281 for (int i = 0; i < d.splitter_->count(); ++i) {
3282 TabWorkArea * twa = d.tabWorkArea(i);
3283 for (int j = 0; j < twa->count(); ++j) {
3284 GuiWorkArea * wa = twa->workArea(j);
3285 Buffer & buf = wa->bufferView().buffer();
3286 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3292 bool GuiView::closeBufferAll()
3295 for (auto & buf : theBufferList()) {
3296 if (!saveBufferIfNeeded(*buf, false)) {
3297 // Closing has been cancelled, so abort.
3302 // Close the workareas in all other views
3303 QList<int> const ids = guiApp->viewIds();
3304 for (int i = 0; i != ids.size(); ++i) {
3305 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3309 // Close our own workareas
3310 if (!closeWorkAreaAll())
3317 bool GuiView::closeWorkAreaAll()
3319 setCurrentWorkArea(currentMainWorkArea());
3321 // We might be in a situation that there is still a tabWorkArea, but
3322 // there are no tabs anymore. This can happen when we get here after a
3323 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3324 // many TabWorkArea's have no documents anymore.
3327 // We have to call count() each time, because it can happen that
3328 // more than one splitter will disappear in one iteration (bug 5998).
3329 while (d.splitter_->count() > empty_twa) {
3330 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3332 if (twa->count() == 0)
3335 setCurrentWorkArea(twa->currentWorkArea());
3336 if (!closeTabWorkArea(twa))
3344 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3349 Buffer & buf = wa->bufferView().buffer();
3351 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3352 Alert::warning(_("Close document"),
3353 _("Document could not be closed because it is being processed by LyX."));
3358 return closeBuffer(buf);
3360 if (!inMultiTabs(wa))
3361 if (!saveBufferIfNeeded(buf, true))
3369 bool GuiView::closeBuffer(Buffer & buf)
3371 bool success = true;
3372 for (Buffer * child_buf : buf.getChildren()) {
3373 if (theBufferList().isOthersChild(&buf, child_buf)) {
3374 child_buf->setParent(nullptr);
3378 // FIXME: should we look in other tabworkareas?
3379 // ANSWER: I don't think so. I've tested, and if the child is
3380 // open in some other window, it closes without a problem.
3381 GuiWorkArea * child_wa = workArea(*child_buf);
3384 // If we are in a close_event all children will be closed in some time,
3385 // so no need to do it here. This will ensure that the children end up
3386 // in the session file in the correct order. If we close the master
3387 // buffer, we can close or release the child buffers here too.
3389 success = closeWorkArea(child_wa, true);
3393 // In this case the child buffer is open but hidden.
3394 // Even in this case, children can be dirty (e.g.,
3395 // after a label change in the master, see #11405).
3396 // Therefore, check this
3397 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3398 // If we are in a close_event all children will be closed in some time,
3399 // so no need to do it here. This will ensure that the children end up
3400 // in the session file in the correct order. If we close the master
3401 // buffer, we can close or release the child buffers here too.
3404 // Save dirty buffers also if closing_!
3405 if (saveBufferIfNeeded(*child_buf, false)) {
3406 child_buf->removeAutosaveFile();
3407 theBufferList().release(child_buf);
3409 // Saving of dirty children has been cancelled.
3410 // Cancel the whole process.
3417 // goto bookmark to update bookmark pit.
3418 // FIXME: we should update only the bookmarks related to this buffer!
3419 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3420 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3421 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3422 guiApp->gotoBookmark(i, false, false);
3424 if (saveBufferIfNeeded(buf, false)) {
3425 buf.removeAutosaveFile();
3426 theBufferList().release(&buf);
3430 // open all children again to avoid a crash because of dangling
3431 // pointers (bug 6603)
3437 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3439 while (twa == d.currentTabWorkArea()) {
3440 twa->setCurrentIndex(twa->count() - 1);
3442 GuiWorkArea * wa = twa->currentWorkArea();
3443 Buffer & b = wa->bufferView().buffer();
3445 // We only want to close the buffer if the same buffer is not visible
3446 // in another view, and if this is not a child and if we are closing
3447 // a view (not a tabgroup).
3448 bool const close_buffer =
3449 !inOtherView(b) && !b.parent() && closing_;
3451 if (!closeWorkArea(wa, close_buffer))
3458 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3460 if (buf.isClean() || buf.paragraphs().empty())
3463 // Switch to this Buffer.
3469 if (buf.isUnnamed()) {
3470 file = from_utf8(buf.fileName().onlyFileName());
3473 FileName filename = buf.fileName();
3475 file = filename.displayName(30);
3476 exists = filename.exists();
3479 // Bring this window to top before asking questions.
3484 if (hiding && buf.isUnnamed()) {
3485 docstring const text = bformat(_("The document %1$s has not been "
3486 "saved yet.\n\nDo you want to save "
3487 "the document?"), file);
3488 ret = Alert::prompt(_("Save new document?"),
3489 text, 0, 1, _("&Save"), _("&Cancel"));
3493 docstring const text = exists ?
3494 bformat(_("The document %1$s has unsaved changes."
3495 "\n\nDo you want to save the document or "
3496 "discard the changes?"), file) :
3497 bformat(_("The document %1$s has not been saved yet."
3498 "\n\nDo you want to save the document or "
3499 "discard it entirely?"), file);
3500 docstring const title = exists ?
3501 _("Save changed document?") : _("Save document?");
3502 ret = Alert::prompt(title, text, 0, 2,
3503 _("&Save"), _("&Discard"), _("&Cancel"));
3508 if (!saveBuffer(buf))
3512 // If we crash after this we could have no autosave file
3513 // but I guess this is really improbable (Jug).
3514 // Sometimes improbable things happen:
3515 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3516 // buf.removeAutosaveFile();
3518 // revert all changes
3529 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3531 Buffer & buf = wa->bufferView().buffer();
3533 for (int i = 0; i != d.splitter_->count(); ++i) {
3534 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3535 if (wa_ && wa_ != wa)
3538 return inOtherView(buf);
3542 bool GuiView::inOtherView(Buffer & buf)
3544 QList<int> const ids = guiApp->viewIds();
3546 for (int i = 0; i != ids.size(); ++i) {
3550 if (guiApp->view(ids[i]).workArea(buf))
3557 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3559 if (!documentBufferView())
3562 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3563 Buffer * const curbuf = &documentBufferView()->buffer();
3564 int nwa = twa->count();
3565 for (int i = 0; i < nwa; ++i) {
3566 if (&workArea(i)->bufferView().buffer() == curbuf) {
3568 if (np == NEXTBUFFER)
3569 next_index = (i == nwa - 1 ? 0 : i + 1);
3571 next_index = (i == 0 ? nwa - 1 : i - 1);
3573 twa->moveTab(i, next_index);
3575 setBuffer(&workArea(next_index)->bufferView().buffer());
3583 /// make sure the document is saved
3584 static bool ensureBufferClean(Buffer * buffer)
3586 LASSERT(buffer, return false);
3587 if (buffer->isClean() && !buffer->isUnnamed())
3590 docstring const file = buffer->fileName().displayName(30);
3593 if (!buffer->isUnnamed()) {
3594 text = bformat(_("The document %1$s has unsaved "
3595 "changes.\n\nDo you want to save "
3596 "the document?"), file);
3597 title = _("Save changed document?");
3600 text = bformat(_("The document %1$s has not been "
3601 "saved yet.\n\nDo you want to save "
3602 "the document?"), file);
3603 title = _("Save new document?");
3605 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3608 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3610 return buffer->isClean() && !buffer->isUnnamed();
3614 bool GuiView::reloadBuffer(Buffer & buf)
3616 currentBufferView()->cursor().reset();
3617 Buffer::ReadStatus status = buf.reload();
3618 return status == Buffer::ReadSuccess;
3622 void GuiView::checkExternallyModifiedBuffers()
3624 for (Buffer * buf : theBufferList()) {
3625 if (buf->fileName().exists() && buf->isChecksumModified()) {
3626 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3627 " Reload now? Any local changes will be lost."),
3628 from_utf8(buf->absFileName()));
3629 int const ret = Alert::prompt(_("Reload externally changed document?"),
3630 text, 0, 1, _("&Reload"), _("&Cancel"));
3638 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3640 Buffer * buffer = documentBufferView()
3641 ? &(documentBufferView()->buffer()) : nullptr;
3643 switch (cmd.action()) {
3644 case LFUN_VC_REGISTER:
3645 if (!buffer || !ensureBufferClean(buffer))
3647 if (!buffer->lyxvc().inUse()) {
3648 if (buffer->lyxvc().registrer()) {
3649 reloadBuffer(*buffer);
3650 dr.clearMessageUpdate();
3655 case LFUN_VC_RENAME:
3656 case LFUN_VC_COPY: {
3657 if (!buffer || !ensureBufferClean(buffer))
3659 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3660 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3661 // Some changes are not yet committed.
3662 // We test here and not in getStatus(), since
3663 // this test is expensive.
3665 LyXVC::CommandResult ret =
3666 buffer->lyxvc().checkIn(log);
3668 if (ret == LyXVC::ErrorCommand ||
3669 ret == LyXVC::VCSuccess)
3670 reloadBuffer(*buffer);
3671 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3672 frontend::Alert::error(
3673 _("Revision control error."),
3674 _("Document could not be checked in."));
3678 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3679 LV_VC_RENAME : LV_VC_COPY;
3680 renameBuffer(*buffer, cmd.argument(), kind);
3685 case LFUN_VC_CHECK_IN:
3686 if (!buffer || !ensureBufferClean(buffer))
3688 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3690 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3692 // Only skip reloading if the checkin was cancelled or
3693 // an error occurred before the real checkin VCS command
3694 // was executed, since the VCS might have changed the
3695 // file even if it could not checkin successfully.
3696 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3697 reloadBuffer(*buffer);
3701 case LFUN_VC_CHECK_OUT:
3702 if (!buffer || !ensureBufferClean(buffer))
3704 if (buffer->lyxvc().inUse()) {
3705 dr.setMessage(buffer->lyxvc().checkOut());
3706 reloadBuffer(*buffer);
3710 case LFUN_VC_LOCKING_TOGGLE:
3711 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3713 if (buffer->lyxvc().inUse()) {
3714 string res = buffer->lyxvc().lockingToggle();
3716 frontend::Alert::error(_("Revision control error."),
3717 _("Error when setting the locking property."));
3720 reloadBuffer(*buffer);
3725 case LFUN_VC_REVERT:
3728 if (buffer->lyxvc().revert()) {
3729 reloadBuffer(*buffer);
3730 dr.clearMessageUpdate();
3734 case LFUN_VC_UNDO_LAST:
3737 buffer->lyxvc().undoLast();
3738 reloadBuffer(*buffer);
3739 dr.clearMessageUpdate();
3742 case LFUN_VC_REPO_UPDATE:
3745 if (ensureBufferClean(buffer)) {
3746 dr.setMessage(buffer->lyxvc().repoUpdate());
3747 checkExternallyModifiedBuffers();
3751 case LFUN_VC_COMMAND: {
3752 string flag = cmd.getArg(0);
3753 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3756 if (contains(flag, 'M')) {
3757 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3760 string path = cmd.getArg(1);
3761 if (contains(path, "$$p") && buffer)
3762 path = subst(path, "$$p", buffer->filePath());
3763 LYXERR(Debug::LYXVC, "Directory: " << path);
3765 if (!pp.isReadableDirectory()) {
3766 lyxerr << _("Directory is not accessible.") << endl;
3769 support::PathChanger p(pp);
3771 string command = cmd.getArg(2);
3772 if (command.empty())
3775 command = subst(command, "$$i", buffer->absFileName());
3776 command = subst(command, "$$p", buffer->filePath());
3778 command = subst(command, "$$m", to_utf8(message));
3779 LYXERR(Debug::LYXVC, "Command: " << command);
3781 one.startscript(Systemcall::Wait, command);
3785 if (contains(flag, 'I'))
3786 buffer->markDirty();
3787 if (contains(flag, 'R'))
3788 reloadBuffer(*buffer);
3793 case LFUN_VC_COMPARE: {
3794 if (cmd.argument().empty()) {
3795 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3801 string rev1 = cmd.getArg(0);
3805 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3808 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3809 f2 = buffer->absFileName();
3811 string rev2 = cmd.getArg(1);
3815 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3819 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3820 f1 << "\n" << f2 << "\n" );
3821 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3822 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3832 void GuiView::openChildDocument(string const & fname)
3834 LASSERT(documentBufferView(), return);
3835 Buffer & buffer = documentBufferView()->buffer();
3836 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3837 documentBufferView()->saveBookmark(false);
3838 Buffer * child = nullptr;
3839 if (theBufferList().exists(filename)) {
3840 child = theBufferList().getBuffer(filename);
3843 message(bformat(_("Opening child document %1$s..."),
3844 makeDisplayPath(filename.absFileName())));
3845 child = loadDocument(filename, false);
3847 // Set the parent name of the child document.
3848 // This makes insertion of citations and references in the child work,
3849 // when the target is in the parent or another child document.
3851 child->setParent(&buffer);
3855 bool GuiView::goToFileRow(string const & argument)
3859 size_t i = argument.find_last_of(' ');
3860 if (i != string::npos) {
3861 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3862 istringstream is(argument.substr(i + 1));
3867 if (i == string::npos) {
3868 LYXERR0("Wrong argument: " << argument);
3871 Buffer * buf = nullptr;
3872 string const realtmp = package().temp_dir().realPath();
3873 // We have to use os::path_prefix_is() here, instead of
3874 // simply prefixIs(), because the file name comes from
3875 // an external application and may need case adjustment.
3876 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3877 buf = theBufferList().getBufferFromTmp(file_name, true);
3878 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3879 << (buf ? " success" : " failed"));
3881 // Must replace extension of the file to be .lyx
3882 // and get full path
3883 FileName const s = fileSearch(string(),
3884 support::changeExtension(file_name, ".lyx"), "lyx");
3885 // Either change buffer or load the file
3886 if (theBufferList().exists(s))
3887 buf = theBufferList().getBuffer(s);
3888 else if (s.exists()) {
3889 buf = loadDocument(s);
3894 _("File does not exist: %1$s"),
3895 makeDisplayPath(file_name)));
3901 _("No buffer for file: %1$s."),
3902 makeDisplayPath(file_name))
3907 bool success = documentBufferView()->setCursorFromRow(row);
3909 LYXERR(Debug::LATEX,
3910 "setCursorFromRow: invalid position for row " << row);
3911 frontend::Alert::error(_("Inverse Search Failed"),
3912 _("Invalid position requested by inverse search.\n"
3913 "You may need to update the viewed document."));
3919 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3921 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3922 menu->exec(QCursor::pos());
3927 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3928 Buffer const * orig, Buffer * clone, string const & format)
3930 Buffer::ExportStatus const status = func(format);
3932 // the cloning operation will have produced a clone of the entire set of
3933 // documents, starting from the master. so we must delete those.
3934 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3936 busyBuffers.remove(orig);
3941 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3942 Buffer const * orig, Buffer * clone, string const & format)
3944 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3946 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3950 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3951 Buffer const * orig, Buffer * clone, string const & format)
3953 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3955 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3959 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3960 Buffer const * orig, Buffer * clone, string const & format)
3962 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3964 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3968 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3969 Buffer const * used_buffer,
3970 docstring const & msg,
3971 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3972 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3973 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3974 bool allow_async, bool use_tmpdir)
3979 string format = argument;
3981 format = used_buffer->params().getDefaultOutputFormat();
3982 processing_format = format;
3984 progress_->clearMessages();
3987 #if EXPORT_in_THREAD
3989 GuiViewPrivate::busyBuffers.insert(used_buffer);
3990 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3991 if (!cloned_buffer) {
3992 Alert::error(_("Export Error"),
3993 _("Error cloning the Buffer."));
3996 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4001 setPreviewFuture(f);
4002 last_export_format = used_buffer->params().bufferFormat();
4005 // We are asynchronous, so we don't know here anything about the success
4008 Buffer::ExportStatus status;
4010 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4011 } else if (previewFunc) {
4012 status = (used_buffer->*previewFunc)(format);
4015 handleExportStatus(gv_, status, format);
4017 return (status == Buffer::ExportSuccess
4018 || status == Buffer::PreviewSuccess);
4022 Buffer::ExportStatus status;
4024 status = (used_buffer->*syncFunc)(format, true);
4025 } else if (previewFunc) {
4026 status = (used_buffer->*previewFunc)(format);
4029 handleExportStatus(gv_, status, format);
4031 return (status == Buffer::ExportSuccess
4032 || status == Buffer::PreviewSuccess);
4036 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4038 BufferView * bv = currentBufferView();
4039 LASSERT(bv, return);
4041 // Let the current BufferView dispatch its own actions.
4042 bv->dispatch(cmd, dr);
4043 if (dr.dispatched()) {
4044 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4045 updateDialog("document", "");
4049 // Try with the document BufferView dispatch if any.
4050 BufferView * doc_bv = documentBufferView();
4051 if (doc_bv && doc_bv != bv) {
4052 doc_bv->dispatch(cmd, dr);
4053 if (dr.dispatched()) {
4054 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4055 updateDialog("document", "");
4060 // Then let the current Cursor dispatch its own actions.
4061 bv->cursor().dispatch(cmd);
4063 // update completion. We do it here and not in
4064 // processKeySym to avoid another redraw just for a
4065 // changed inline completion
4066 if (cmd.origin() == FuncRequest::KEYBOARD) {
4067 if (cmd.action() == LFUN_SELF_INSERT
4068 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4069 updateCompletion(bv->cursor(), true, true);
4070 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4071 updateCompletion(bv->cursor(), false, true);
4073 updateCompletion(bv->cursor(), false, false);
4076 dr = bv->cursor().result();
4080 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4082 BufferView * bv = currentBufferView();
4083 // By default we won't need any update.
4084 dr.screenUpdate(Update::None);
4085 // assume cmd will be dispatched
4086 dr.dispatched(true);
4088 Buffer * doc_buffer = documentBufferView()
4089 ? &(documentBufferView()->buffer()) : nullptr;
4091 if (cmd.origin() == FuncRequest::TOC) {
4092 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4093 toc->doDispatch(bv->cursor(), cmd, dr);
4097 string const argument = to_utf8(cmd.argument());
4099 switch(cmd.action()) {
4100 case LFUN_BUFFER_CHILD_OPEN:
4101 openChildDocument(to_utf8(cmd.argument()));
4104 case LFUN_BUFFER_IMPORT:
4105 importDocument(to_utf8(cmd.argument()));
4108 case LFUN_MASTER_BUFFER_EXPORT:
4110 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4112 case LFUN_BUFFER_EXPORT: {
4115 // GCC only sees strfwd.h when building merged
4116 if (::lyx::operator==(cmd.argument(), "custom")) {
4117 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4118 // so the following test should not be needed.
4119 // In principle, we could try to switch to such a view...
4120 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4121 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4125 string const dest = cmd.getArg(1);
4126 FileName target_dir;
4127 if (!dest.empty() && FileName::isAbsolute(dest))
4128 target_dir = FileName(support::onlyPath(dest));
4130 target_dir = doc_buffer->fileName().onlyPath();
4132 string const format = (argument.empty() || argument == "default") ?
4133 doc_buffer->params().getDefaultOutputFormat() : argument;
4135 if ((dest.empty() && doc_buffer->isUnnamed())
4136 || !target_dir.isDirWritable()) {
4137 exportBufferAs(*doc_buffer, from_utf8(format));
4140 /* TODO/Review: Is it a problem to also export the children?
4141 See the update_unincluded flag */
4142 d.asyncBufferProcessing(format,
4145 &GuiViewPrivate::exportAndDestroy,
4147 nullptr, cmd.allowAsync());
4148 // TODO Inform user about success
4152 case LFUN_BUFFER_EXPORT_AS: {
4153 LASSERT(doc_buffer, break);
4154 docstring f = cmd.argument();
4156 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4157 exportBufferAs(*doc_buffer, f);
4161 case LFUN_BUFFER_UPDATE: {
4162 d.asyncBufferProcessing(argument,
4165 &GuiViewPrivate::compileAndDestroy,
4167 nullptr, cmd.allowAsync(), true);
4170 case LFUN_BUFFER_VIEW: {
4171 d.asyncBufferProcessing(argument,
4173 _("Previewing ..."),
4174 &GuiViewPrivate::previewAndDestroy,
4176 &Buffer::preview, cmd.allowAsync());
4179 case LFUN_MASTER_BUFFER_UPDATE: {
4180 d.asyncBufferProcessing(argument,
4181 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4183 &GuiViewPrivate::compileAndDestroy,
4185 nullptr, cmd.allowAsync(), true);
4188 case LFUN_MASTER_BUFFER_VIEW: {
4189 d.asyncBufferProcessing(argument,
4190 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4192 &GuiViewPrivate::previewAndDestroy,
4193 nullptr, &Buffer::preview, cmd.allowAsync());
4196 case LFUN_EXPORT_CANCEL: {
4197 Systemcall::killscript();
4200 case LFUN_BUFFER_SWITCH: {
4201 string const file_name = to_utf8(cmd.argument());
4202 if (!FileName::isAbsolute(file_name)) {
4204 dr.setMessage(_("Absolute filename expected."));
4208 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4211 dr.setMessage(_("Document not loaded"));
4215 // Do we open or switch to the buffer in this view ?
4216 if (workArea(*buffer)
4217 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4222 // Look for the buffer in other views
4223 QList<int> const ids = guiApp->viewIds();
4225 for (; i != ids.size(); ++i) {
4226 GuiView & gv = guiApp->view(ids[i]);
4227 if (gv.workArea(*buffer)) {
4229 gv.activateWindow();
4231 gv.setBuffer(buffer);
4236 // If necessary, open a new window as a last resort
4237 if (i == ids.size()) {
4238 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4244 case LFUN_BUFFER_NEXT:
4245 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4248 case LFUN_BUFFER_MOVE_NEXT:
4249 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4252 case LFUN_BUFFER_PREVIOUS:
4253 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4256 case LFUN_BUFFER_MOVE_PREVIOUS:
4257 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4260 case LFUN_BUFFER_CHKTEX:
4261 LASSERT(doc_buffer, break);
4262 doc_buffer->runChktex();
4265 case LFUN_COMMAND_EXECUTE: {
4266 command_execute_ = true;
4267 minibuffer_focus_ = true;
4270 case LFUN_DROP_LAYOUTS_CHOICE:
4271 d.layout_->showPopup();
4274 case LFUN_MENU_OPEN:
4275 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4276 menu->exec(QCursor::pos());
4279 case LFUN_FILE_INSERT: {
4280 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4281 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4282 dr.forceBufferUpdate();
4283 dr.screenUpdate(Update::Force);
4288 case LFUN_FILE_INSERT_PLAINTEXT:
4289 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4290 string const fname = to_utf8(cmd.argument());
4291 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4292 dr.setMessage(_("Absolute filename expected."));
4296 FileName filename(fname);
4297 if (fname.empty()) {
4298 FileDialog dlg(qt_("Select file to insert"));
4300 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4301 QStringList(qt_("All Files (*)")));
4303 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4304 dr.setMessage(_("Canceled."));
4308 filename.set(fromqstr(result.second));
4312 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4313 bv->dispatch(new_cmd, dr);
4318 case LFUN_BUFFER_RELOAD: {
4319 LASSERT(doc_buffer, break);
4322 bool drop = (cmd.argument() == "dump");
4325 if (!drop && !doc_buffer->isClean()) {
4326 docstring const file =
4327 makeDisplayPath(doc_buffer->absFileName(), 20);
4328 if (doc_buffer->notifiesExternalModification()) {
4329 docstring text = _("The current version will be lost. "
4330 "Are you sure you want to load the version on disk "
4331 "of the document %1$s?");
4332 ret = Alert::prompt(_("Reload saved document?"),
4333 bformat(text, file), 1, 1,
4334 _("&Reload"), _("&Cancel"));
4336 docstring text = _("Any changes will be lost. "
4337 "Are you sure you want to revert to the saved version "
4338 "of the document %1$s?");
4339 ret = Alert::prompt(_("Revert to saved document?"),
4340 bformat(text, file), 1, 1,
4341 _("&Revert"), _("&Cancel"));
4346 doc_buffer->markClean();
4347 reloadBuffer(*doc_buffer);
4348 dr.forceBufferUpdate();
4353 case LFUN_BUFFER_RESET_EXPORT:
4354 LASSERT(doc_buffer, break);
4355 doc_buffer->requireFreshStart(true);
4356 dr.setMessage(_("Buffer export reset."));
4359 case LFUN_BUFFER_WRITE:
4360 LASSERT(doc_buffer, break);
4361 saveBuffer(*doc_buffer);
4364 case LFUN_BUFFER_WRITE_AS:
4365 LASSERT(doc_buffer, break);
4366 renameBuffer(*doc_buffer, cmd.argument());
4369 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4370 LASSERT(doc_buffer, break);
4371 renameBuffer(*doc_buffer, cmd.argument(),
4372 LV_WRITE_AS_TEMPLATE);
4375 case LFUN_BUFFER_WRITE_ALL: {
4376 Buffer * first = theBufferList().first();
4379 message(_("Saving all documents..."));
4380 // We cannot use a for loop as the buffer list cycles.
4383 if (!b->isClean()) {
4385 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4387 b = theBufferList().next(b);
4388 } while (b != first);
4389 dr.setMessage(_("All documents saved."));
4393 case LFUN_MASTER_BUFFER_FORALL: {
4397 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4398 funcToRun.allowAsync(false);
4400 for (Buffer const * buf : doc_buffer->allRelatives()) {
4401 // Switch to other buffer view and resend cmd
4402 lyx::dispatch(FuncRequest(
4403 LFUN_BUFFER_SWITCH, buf->absFileName()));
4404 lyx::dispatch(funcToRun);
4407 lyx::dispatch(FuncRequest(
4408 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4412 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4413 LASSERT(doc_buffer, break);
4414 doc_buffer->clearExternalModification();
4417 case LFUN_BUFFER_CLOSE:
4421 case LFUN_BUFFER_CLOSE_ALL:
4425 case LFUN_DEVEL_MODE_TOGGLE:
4426 devel_mode_ = !devel_mode_;
4428 dr.setMessage(_("Developer mode is now enabled."));
4430 dr.setMessage(_("Developer mode is now disabled."));
4433 case LFUN_TOOLBAR_SET: {
4434 string const name = cmd.getArg(0);
4435 string const state = cmd.getArg(1);
4436 if (GuiToolbar * t = toolbar(name))
4441 case LFUN_TOOLBAR_TOGGLE: {
4442 string const name = cmd.getArg(0);
4443 if (GuiToolbar * t = toolbar(name))
4448 case LFUN_TOOLBAR_MOVABLE: {
4449 string const name = cmd.getArg(0);
4451 // toggle (all) toolbars movablility
4452 toolbarsMovable_ = !toolbarsMovable_;
4453 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4454 GuiToolbar * tb = toolbar(ti.name);
4455 if (tb && tb->isMovable() != toolbarsMovable_)
4456 // toggle toolbar movablity if it does not fit lock
4457 // (all) toolbars positions state silent = true, since
4458 // status bar notifications are slow
4461 if (toolbarsMovable_)
4462 dr.setMessage(_("Toolbars unlocked."));
4464 dr.setMessage(_("Toolbars locked."));
4465 } else if (GuiToolbar * t = toolbar(name)) {
4466 // toggle current toolbar movablity
4468 // update lock (all) toolbars positions
4469 updateLockToolbars();
4474 case LFUN_ICON_SIZE: {
4475 QSize size = d.iconSize(cmd.argument());
4477 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4478 size.width(), size.height()));
4482 case LFUN_DIALOG_UPDATE: {
4483 string const name = to_utf8(cmd.argument());
4484 if (name == "prefs" || name == "document")
4485 updateDialog(name, string());
4486 else if (name == "paragraph")
4487 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4488 else if (currentBufferView()) {
4489 Inset * inset = currentBufferView()->editedInset(name);
4490 // Can only update a dialog connected to an existing inset
4492 // FIXME: get rid of this indirection; GuiView ask the inset
4493 // if he is kind enough to update itself...
4494 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4495 //FIXME: pass DispatchResult here?
4496 inset->dispatch(currentBufferView()->cursor(), fr);
4502 case LFUN_DIALOG_TOGGLE: {
4503 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4504 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4505 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4509 case LFUN_DIALOG_DISCONNECT_INSET:
4510 disconnectDialog(to_utf8(cmd.argument()));
4513 case LFUN_DIALOG_HIDE: {
4514 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4518 case LFUN_DIALOG_SHOW: {
4519 string const name = cmd.getArg(0);
4520 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4522 if (name == "latexlog") {
4523 // getStatus checks that
4524 LASSERT(doc_buffer, break);
4525 Buffer::LogType type;
4526 string const logfile = doc_buffer->logName(&type);
4528 case Buffer::latexlog:
4531 case Buffer::buildlog:
4532 sdata = "literate ";
4535 sdata += Lexer::quoteString(logfile);
4536 showDialog("log", sdata);
4537 } else if (name == "vclog") {
4538 // getStatus checks that
4539 LASSERT(doc_buffer, break);
4540 string const sdata2 = "vc " +
4541 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4542 showDialog("log", sdata2);
4543 } else if (name == "symbols") {
4544 sdata = bv->cursor().getEncoding()->name();
4546 showDialog("symbols", sdata);
4547 } else if (name == "findreplace") {
4548 sdata = to_utf8(bv->cursor().selectionAsString(false));
4549 showDialog(name, sdata);
4551 } else if (name == "prefs" && isFullScreen()) {
4552 lfunUiToggle("fullscreen");
4553 showDialog("prefs", sdata);
4555 showDialog(name, sdata);
4560 dr.setMessage(cmd.argument());
4563 case LFUN_UI_TOGGLE: {
4564 string arg = cmd.getArg(0);
4565 if (!lfunUiToggle(arg)) {
4566 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4567 dr.setMessage(bformat(msg, from_utf8(arg)));
4569 // Make sure the keyboard focus stays in the work area.
4574 case LFUN_VIEW_SPLIT: {
4575 LASSERT(doc_buffer, break);
4576 string const orientation = cmd.getArg(0);
4577 d.splitter_->setOrientation(orientation == "vertical"
4578 ? Qt::Vertical : Qt::Horizontal);
4579 TabWorkArea * twa = addTabWorkArea();
4580 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4581 setCurrentWorkArea(wa);
4584 case LFUN_TAB_GROUP_CLOSE:
4585 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4586 closeTabWorkArea(twa);
4587 d.current_work_area_ = nullptr;
4588 twa = d.currentTabWorkArea();
4589 // Switch to the next GuiWorkArea in the found TabWorkArea.
4591 // Make sure the work area is up to date.
4592 setCurrentWorkArea(twa->currentWorkArea());
4594 setCurrentWorkArea(nullptr);
4599 case LFUN_VIEW_CLOSE:
4600 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4601 closeWorkArea(twa->currentWorkArea());
4602 d.current_work_area_ = nullptr;
4603 twa = d.currentTabWorkArea();
4604 // Switch to the next GuiWorkArea in the found TabWorkArea.
4606 // Make sure the work area is up to date.
4607 setCurrentWorkArea(twa->currentWorkArea());
4609 setCurrentWorkArea(nullptr);
4614 case LFUN_COMPLETION_INLINE:
4615 if (d.current_work_area_)
4616 d.current_work_area_->completer().showInline();
4619 case LFUN_COMPLETION_POPUP:
4620 if (d.current_work_area_)
4621 d.current_work_area_->completer().showPopup();
4626 if (d.current_work_area_)
4627 d.current_work_area_->completer().tab();
4630 case LFUN_COMPLETION_CANCEL:
4631 if (d.current_work_area_) {
4632 if (d.current_work_area_->completer().popupVisible())
4633 d.current_work_area_->completer().hidePopup();
4635 d.current_work_area_->completer().hideInline();
4639 case LFUN_COMPLETION_ACCEPT:
4640 if (d.current_work_area_)
4641 d.current_work_area_->completer().activate();
4644 case LFUN_BUFFER_ZOOM_IN:
4645 case LFUN_BUFFER_ZOOM_OUT:
4646 case LFUN_BUFFER_ZOOM: {
4647 if (cmd.argument().empty()) {
4648 if (cmd.action() == LFUN_BUFFER_ZOOM)
4650 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4655 if (cmd.action() == LFUN_BUFFER_ZOOM)
4656 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4657 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4658 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4660 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4663 // Actual zoom value: default zoom + fractional extra value
4664 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4665 if (zoom < static_cast<int>(zoom_min_))
4668 setCurrentZoom(zoom);
4670 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4671 lyxrc.currentZoom, lyxrc.defaultZoom));
4673 guiApp->fontLoader().update();
4674 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4678 case LFUN_VC_REGISTER:
4679 case LFUN_VC_RENAME:
4681 case LFUN_VC_CHECK_IN:
4682 case LFUN_VC_CHECK_OUT:
4683 case LFUN_VC_REPO_UPDATE:
4684 case LFUN_VC_LOCKING_TOGGLE:
4685 case LFUN_VC_REVERT:
4686 case LFUN_VC_UNDO_LAST:
4687 case LFUN_VC_COMMAND:
4688 case LFUN_VC_COMPARE:
4689 dispatchVC(cmd, dr);
4692 case LFUN_SERVER_GOTO_FILE_ROW:
4693 if(goToFileRow(to_utf8(cmd.argument())))
4694 dr.screenUpdate(Update::Force | Update::FitCursor);
4697 case LFUN_LYX_ACTIVATE:
4701 case LFUN_WINDOW_RAISE:
4707 case LFUN_FORWARD_SEARCH: {
4708 // it seems safe to assume we have a document buffer, since
4709 // getStatus wants one.
4710 LASSERT(doc_buffer, break);
4711 Buffer const * doc_master = doc_buffer->masterBuffer();
4712 FileName const path(doc_master->temppath());
4713 string const texname = doc_master->isChild(doc_buffer)
4714 ? DocFileName(changeExtension(
4715 doc_buffer->absFileName(),
4716 "tex")).mangledFileName()
4717 : doc_buffer->latexName();
4718 string const fulltexname =
4719 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4720 string const mastername =
4721 removeExtension(doc_master->latexName());
4722 FileName const dviname(addName(path.absFileName(),
4723 addExtension(mastername, "dvi")));
4724 FileName const pdfname(addName(path.absFileName(),
4725 addExtension(mastername, "pdf")));
4726 bool const have_dvi = dviname.exists();
4727 bool const have_pdf = pdfname.exists();
4728 if (!have_dvi && !have_pdf) {
4729 dr.setMessage(_("Please, preview the document first."));
4732 string outname = dviname.onlyFileName();
4733 string command = lyxrc.forward_search_dvi;
4734 if (!have_dvi || (have_pdf &&
4735 pdfname.lastModified() > dviname.lastModified())) {
4736 outname = pdfname.onlyFileName();
4737 command = lyxrc.forward_search_pdf;
4740 DocIterator cur = bv->cursor();
4741 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4742 LYXERR(Debug::ACTION, "Forward search: row:" << row
4744 if (row == -1 || command.empty()) {
4745 dr.setMessage(_("Couldn't proceed."));
4748 string texrow = convert<string>(row);
4750 command = subst(command, "$$n", texrow);
4751 command = subst(command, "$$f", fulltexname);
4752 command = subst(command, "$$t", texname);
4753 command = subst(command, "$$o", outname);
4755 volatile PathChanger p(path);
4757 one.startscript(Systemcall::DontWait, command);
4761 case LFUN_SPELLING_CONTINUOUSLY:
4762 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4763 dr.screenUpdate(Update::Force);
4766 case LFUN_CITATION_OPEN: {
4768 if (theFormats().getFormat("pdf"))
4769 pdfv = theFormats().getFormat("pdf")->viewer();
4770 if (theFormats().getFormat("ps"))
4771 psv = theFormats().getFormat("ps")->viewer();
4772 frontend::showTarget(argument, pdfv, psv);
4777 // The LFUN must be for one of BufferView, Buffer or Cursor;
4779 dispatchToBufferView(cmd, dr);
4783 // Need to update bv because many LFUNs here might have destroyed it
4784 bv = currentBufferView();
4786 // Clear non-empty selections
4787 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4789 Cursor & cur = bv->cursor();
4790 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4791 cur.clearSelection();
4797 bool GuiView::lfunUiToggle(string const & ui_component)
4799 if (ui_component == "scrollbar") {
4800 // hide() is of no help
4801 if (d.current_work_area_->verticalScrollBarPolicy() ==
4802 Qt::ScrollBarAlwaysOff)
4804 d.current_work_area_->setVerticalScrollBarPolicy(
4805 Qt::ScrollBarAsNeeded);
4807 d.current_work_area_->setVerticalScrollBarPolicy(
4808 Qt::ScrollBarAlwaysOff);
4809 } else if (ui_component == "statusbar") {
4810 statusBar()->setVisible(!statusBar()->isVisible());
4811 } else if (ui_component == "menubar") {
4812 menuBar()->setVisible(!menuBar()->isVisible());
4814 if (ui_component == "frame") {
4815 int const l = contentsMargins().left();
4817 //are the frames in default state?
4818 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4820 #if QT_VERSION > 0x050903
4821 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4823 setContentsMargins(-2, -2, -2, -2);
4825 #if QT_VERSION > 0x050903
4826 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4828 setContentsMargins(0, 0, 0, 0);
4831 if (ui_component == "fullscreen") {
4839 void GuiView::toggleFullScreen()
4841 setWindowState(windowState() ^ Qt::WindowFullScreen);
4845 Buffer const * GuiView::updateInset(Inset const * inset)
4850 Buffer const * inset_buffer = &(inset->buffer());
4852 for (int i = 0; i != d.splitter_->count(); ++i) {
4853 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4856 Buffer const * buffer = &(wa->bufferView().buffer());
4857 if (inset_buffer == buffer)
4858 wa->scheduleRedraw(true);
4860 return inset_buffer;
4864 void GuiView::restartCaret()
4866 /* When we move around, or type, it's nice to be able to see
4867 * the caret immediately after the keypress.
4869 if (d.current_work_area_)
4870 d.current_work_area_->startBlinkingCaret();
4872 // Take this occasion to update the other GUI elements.
4878 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4880 if (d.current_work_area_)
4881 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4886 // This list should be kept in sync with the list of insets in
4887 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4888 // dialog should have the same name as the inset.
4889 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4890 // docs in LyXAction.cpp.
4892 char const * const dialognames[] = {
4894 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4895 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4896 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4897 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4898 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4899 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4900 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4901 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4903 char const * const * const end_dialognames =
4904 dialognames + (sizeof(dialognames) / sizeof(char *));
4908 cmpCStr(char const * name) : name_(name) {}
4909 bool operator()(char const * other) {
4910 return strcmp(other, name_) == 0;
4917 bool isValidName(string const & name)
4919 return find_if(dialognames, end_dialognames,
4920 cmpCStr(name.c_str())) != end_dialognames;
4926 void GuiView::resetDialogs()
4928 // Make sure that no LFUN uses any GuiView.
4929 guiApp->setCurrentView(nullptr);
4933 constructToolbars();
4934 guiApp->menus().fillMenuBar(menuBar(), this, false);
4935 d.layout_->updateContents(true);
4936 // Now update controls with current buffer.
4937 guiApp->setCurrentView(this);
4943 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4945 for (QObject * child: widget->children()) {
4946 if (child->inherits("QGroupBox")) {
4947 QGroupBox * box = (QGroupBox*) child;
4950 flatGroupBoxes(child, flag);
4956 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4958 if (!isValidName(name))
4961 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4963 if (it != d.dialogs_.end()) {
4965 it->second->hideView();
4966 return it->second.get();
4969 Dialog * dialog = build(name);
4970 d.dialogs_[name].reset(dialog);
4971 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4972 // Force a uniform style for group boxes
4973 // On Mac non-flat works better, on Linux flat is standard
4974 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4976 if (lyxrc.allow_geometry_session)
4977 dialog->restoreSession();
4984 void GuiView::showDialog(string const & name, string const & sdata,
4987 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4991 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4997 const string name = fromqstr(qname);
4998 const string sdata = fromqstr(qdata);
5002 Dialog * dialog = findOrBuild(name, false);
5004 bool const visible = dialog->isVisibleView();
5005 dialog->showData(sdata);
5006 if (currentBufferView())
5007 currentBufferView()->editInset(name, inset);
5008 // We only set the focus to the new dialog if it was not yet
5009 // visible in order not to change the existing previous behaviour
5011 // activateWindow is needed for floating dockviews
5012 dialog->asQWidget()->raise();
5013 dialog->asQWidget()->activateWindow();
5014 if (dialog->wantInitialFocus())
5015 dialog->asQWidget()->setFocus();
5019 catch (ExceptionMessage const &) {
5027 bool GuiView::isDialogVisible(string const & name) const
5029 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5030 if (it == d.dialogs_.end())
5032 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5036 void GuiView::hideDialog(string const & name, Inset * inset)
5038 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5039 if (it == d.dialogs_.end())
5043 if (!currentBufferView())
5045 if (inset != currentBufferView()->editedInset(name))
5049 Dialog * const dialog = it->second.get();
5050 if (dialog->isVisibleView())
5052 if (currentBufferView())
5053 currentBufferView()->editInset(name, nullptr);
5057 void GuiView::disconnectDialog(string const & name)
5059 if (!isValidName(name))
5061 if (currentBufferView())
5062 currentBufferView()->editInset(name, nullptr);
5066 void GuiView::hideAll() const
5068 for(auto const & dlg_p : d.dialogs_)
5069 dlg_p.second->hideView();
5073 void GuiView::updateDialogs()
5075 for(auto const & dlg_p : d.dialogs_) {
5076 Dialog * dialog = dlg_p.second.get();
5078 if (dialog->needBufferOpen() && !documentBufferView())
5079 hideDialog(fromqstr(dialog->name()), nullptr);
5080 else if (dialog->isVisibleView())
5081 dialog->checkStatus();
5089 Dialog * GuiView::build(string const & name)
5091 return createDialog(*this, name);
5095 SEMenu::SEMenu(QWidget * parent)
5097 QAction * action = addAction(qt_("Disable Shell Escape"));
5098 connect(action, SIGNAL(triggered()),
5099 parent, SLOT(disableShellEscape()));
5102 } // namespace frontend
5105 #include "moc_GuiView.cpp"