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>
106 #include <QStackedWidget>
107 #include <QStatusBar>
108 #include <QSvgRenderer>
109 #include <QtConcurrentRun>
112 #include <QWindowStateChangeEvent>
115 // sync with GuiAlert.cpp
116 #define EXPORT_in_THREAD 1
119 #include "support/bind.h"
123 #ifdef HAVE_SYS_TIME_H
124 # include <sys/time.h>
132 using namespace lyx::support;
136 using support::addExtension;
137 using support::changeExtension;
138 using support::removeExtension;
144 class BackgroundWidget : public QWidget
147 BackgroundWidget(int width, int height)
148 : width_(width), height_(height)
150 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
151 if (!lyxrc.show_banner)
153 /// The text to be written on top of the pixmap
154 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
155 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
156 /// The text to be written on top of the pixmap
157 QString const text = lyx_version ?
158 qt_("version ") + lyx_version : qt_("unknown version");
159 #if QT_VERSION >= 0x050000
160 QString imagedir = "images/";
161 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
162 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
163 if (svgRenderer.isValid()) {
164 splash_ = QPixmap(splashSize());
165 QPainter painter(&splash_);
166 svgRenderer.render(&painter);
167 splash_.setDevicePixelRatio(pixelRatio());
169 splash_ = getPixmap("images/", "banner", "png");
172 splash_ = getPixmap("images/", "banner", "svgz,png");
175 QPainter pain(&splash_);
176 pain.setPen(QColor(0, 0, 0));
177 qreal const fsize = fontSize();
180 qreal locscale = htextsize.toFloat(&ok);
183 QPointF const position = textPosition(false);
184 QPointF const hposition = textPosition(true);
185 QRectF const hrect(hposition, splashSize());
187 "widget pixel ratio: " << pixelRatio() <<
188 " splash pixel ratio: " << splashPixelRatio() <<
189 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
191 // The font used to display the version info
192 font.setStyleHint(QFont::SansSerif);
193 font.setWeight(QFont::Bold);
194 font.setPointSizeF(fsize);
196 pain.drawText(position, text);
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Normal);
200 font.setPointSizeF(hfsize);
201 // Check how long the logo gets with the current font
202 // and adapt if the font is running wider than what
204 GuiFontMetrics fm(font);
205 // Split the title into lines to measure the longest line
206 // in the current l7n.
207 QStringList titlesegs = htext.split('\n');
209 int hline = fm.maxHeight();
210 QStringList::const_iterator sit;
211 for (QString const & seg : titlesegs) {
212 if (fm.width(seg) > wline)
213 wline = fm.width(seg);
215 // The longest line in the reference font (for English)
216 // is 180. Calculate scale factor from that.
217 double const wscale = wline > 0 ? (180.0 / wline) : 1;
218 // Now do the same for the height (necessary for condensed fonts)
219 double const hscale = (34.0 / hline);
220 // take the lower of the two scale factors.
221 double const scale = min(wscale, hscale);
222 // Now rescale. Also consider l7n's offset factor.
223 font.setPointSizeF(hfsize * scale * locscale);
226 pain.drawText(hrect, Qt::AlignLeft, htext);
227 setFocusPolicy(Qt::StrongFocus);
230 void paintEvent(QPaintEvent *) override
232 int const w = width_;
233 int const h = height_;
234 int const x = (width() - w) / 2;
235 int const y = (height() - h) / 2;
237 "widget pixel ratio: " << pixelRatio() <<
238 " splash pixel ratio: " << splashPixelRatio() <<
239 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
241 pain.drawPixmap(x, y, w, h, splash_);
244 void keyPressEvent(QKeyEvent * ev) override
247 setKeySymbol(&sym, ev);
249 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
261 /// Current ratio between physical pixels and device-independent pixels
262 double pixelRatio() const {
263 #if QT_VERSION >= 0x050000
264 return qt_scale_factor * devicePixelRatio();
270 qreal fontSize() const {
271 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
274 QPointF textPosition(bool const heading) const {
275 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
276 : QPointF(width_/2 - 18, height_/2 + 45);
279 QSize splashSize() const {
281 static_cast<unsigned int>(width_ * pixelRatio()),
282 static_cast<unsigned int>(height_ * pixelRatio()));
285 /// Ratio between physical pixels and device-independent pixels of splash image
286 double splashPixelRatio() const {
287 #if QT_VERSION >= 0x050000
288 return splash_.devicePixelRatio();
296 /// Toolbar store providing access to individual toolbars by name.
297 typedef map<string, GuiToolbar *> ToolbarMap;
299 typedef shared_ptr<Dialog> DialogPtr;
304 class GuiView::GuiViewPrivate
307 GuiViewPrivate(GuiViewPrivate const &);
308 void operator=(GuiViewPrivate const &);
310 GuiViewPrivate(GuiView * gv)
311 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
312 layout_(nullptr), autosave_timeout_(5000),
315 // hardcode here the platform specific icon size
316 smallIconSize = 16; // scaling problems
317 normalIconSize = 20; // ok, default if iconsize.png is missing
318 bigIconSize = 26; // better for some math icons
319 hugeIconSize = 32; // better for hires displays
322 // if it exists, use width of iconsize.png as normal size
323 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
324 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
326 QImage image(toqstr(fn.absFileName()));
327 if (image.width() < int(smallIconSize))
328 normalIconSize = smallIconSize;
329 else if (image.width() > int(giantIconSize))
330 normalIconSize = giantIconSize;
332 normalIconSize = image.width();
335 splitter_ = new QSplitter;
336 bg_widget_ = new BackgroundWidget(400, 250);
337 stack_widget_ = new QStackedWidget;
338 stack_widget_->addWidget(bg_widget_);
339 stack_widget_->addWidget(splitter_);
342 // TODO cleanup, remove the singleton, handle multiple Windows?
343 progress_ = ProgressInterface::instance();
344 if (!dynamic_cast<GuiProgress*>(progress_)) {
345 progress_ = new GuiProgress; // TODO who deletes it
346 ProgressInterface::setInstance(progress_);
349 dynamic_cast<GuiProgress*>(progress_),
350 SIGNAL(updateStatusBarMessage(QString const&)),
351 gv, SLOT(updateStatusBarMessage(QString const&)));
353 dynamic_cast<GuiProgress*>(progress_),
354 SIGNAL(clearMessageText()),
355 gv, SLOT(clearMessageText()));
362 delete stack_widget_;
367 stack_widget_->setCurrentWidget(bg_widget_);
368 bg_widget_->setUpdatesEnabled(true);
369 bg_widget_->setFocus();
372 int tabWorkAreaCount()
374 return splitter_->count();
377 TabWorkArea * tabWorkArea(int i)
379 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
382 TabWorkArea * currentTabWorkArea()
384 int areas = tabWorkAreaCount();
386 // The first TabWorkArea is always the first one, if any.
387 return tabWorkArea(0);
389 for (int i = 0; i != areas; ++i) {
390 TabWorkArea * twa = tabWorkArea(i);
391 if (current_main_work_area_ == twa->currentWorkArea())
395 // None has the focus so we just take the first one.
396 return tabWorkArea(0);
399 int countWorkAreasOf(Buffer & buf)
401 int areas = tabWorkAreaCount();
403 for (int i = 0; i != areas; ++i) {
404 TabWorkArea * twa = tabWorkArea(i);
405 if (twa->workArea(buf))
411 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
413 if (processing_thread_watcher_.isRunning()) {
414 // we prefer to cancel this preview in order to keep a snappy
418 processing_thread_watcher_.setFuture(f);
421 QSize iconSize(docstring const & icon_size)
424 if (icon_size == "small")
425 size = smallIconSize;
426 else if (icon_size == "normal")
427 size = normalIconSize;
428 else if (icon_size == "big")
430 else if (icon_size == "huge")
432 else if (icon_size == "giant")
433 size = giantIconSize;
435 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
437 if (size < smallIconSize)
438 size = smallIconSize;
440 return QSize(size, size);
443 QSize iconSize(QString const & icon_size)
445 return iconSize(qstring_to_ucs4(icon_size));
448 string & iconSize(QSize const & qsize)
450 LATTEST(qsize.width() == qsize.height());
452 static string icon_size;
454 unsigned int size = qsize.width();
456 if (size < smallIconSize)
457 size = smallIconSize;
459 if (size == smallIconSize)
461 else if (size == normalIconSize)
462 icon_size = "normal";
463 else if (size == bigIconSize)
465 else if (size == hugeIconSize)
467 else if (size == giantIconSize)
470 icon_size = convert<string>(size);
475 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
476 Buffer * buffer, string const & format);
477 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
484 static Buffer::ExportStatus runAndDestroy(const T& func,
485 Buffer const * orig, Buffer * buffer, string const & format);
487 // TODO syncFunc/previewFunc: use bind
488 bool asyncBufferProcessing(string const & argument,
489 Buffer const * used_buffer,
490 docstring const & msg,
491 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
492 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
493 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
494 bool allow_async, bool use_tmpdir = false);
496 QVector<GuiWorkArea*> guiWorkAreas();
500 GuiWorkArea * current_work_area_;
501 GuiWorkArea * current_main_work_area_;
502 QSplitter * splitter_;
503 QStackedWidget * stack_widget_;
504 BackgroundWidget * bg_widget_;
506 ToolbarMap toolbars_;
507 ProgressInterface* progress_;
508 /// The main layout box.
510 * \warning Don't Delete! The layout box is actually owned by
511 * whichever toolbar contains it. All the GuiView class needs is a
512 * means of accessing it.
514 * FIXME: replace that with a proper model so that we are not limited
515 * to only one dialog.
520 map<string, DialogPtr> dialogs_;
523 QTimer statusbar_timer_;
524 /// auto-saving of buffers
525 Timeout autosave_timeout_;
528 TocModels toc_models_;
531 QFutureWatcher<docstring> autosave_watcher_;
532 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
534 string last_export_format;
535 string processing_format;
537 static QSet<Buffer const *> busyBuffers;
539 unsigned int smallIconSize;
540 unsigned int normalIconSize;
541 unsigned int bigIconSize;
542 unsigned int hugeIconSize;
543 unsigned int giantIconSize;
545 /// flag against a race condition due to multiclicks, see bug #1119
549 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
552 GuiView::GuiView(int id)
553 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
554 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
557 connect(this, SIGNAL(bufferViewChanged()),
558 this, SLOT(onBufferViewChanged()));
560 // GuiToolbars *must* be initialised before the menu bar.
561 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
564 // set ourself as the current view. This is needed for the menu bar
565 // filling, at least for the static special menu item on Mac. Otherwise
566 // they are greyed out.
567 guiApp->setCurrentView(this);
569 // Fill up the menu bar.
570 guiApp->menus().fillMenuBar(menuBar(), this, true);
572 setCentralWidget(d.stack_widget_);
574 // Start autosave timer
575 if (lyxrc.autosave) {
576 // The connection is closed when this is destroyed.
577 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
578 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
579 d.autosave_timeout_.start();
581 connect(&d.statusbar_timer_, SIGNAL(timeout()),
582 this, SLOT(clearMessage()));
584 // We don't want to keep the window in memory if it is closed.
585 setAttribute(Qt::WA_DeleteOnClose, true);
587 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
588 // QIcon::fromTheme was introduced in Qt 4.6
589 #if (QT_VERSION >= 0x040600)
590 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
591 // since the icon is provided in the application bundle. We use a themed
592 // version when available and use the bundled one as fallback.
593 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
595 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
601 // use tabbed dock area for multiple docks
602 // (such as "source" and "messages")
603 setDockOptions(QMainWindow::ForceTabbedDocks);
606 // use document mode tabs on docks
607 setDocumentMode(true);
611 setAcceptDrops(true);
613 // add busy indicator to statusbar
614 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
615 statusBar()->addPermanentWidget(busylabel);
616 search_mode mode = theGuiApp()->imageSearchMode();
617 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
618 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
619 busylabel->setMovie(busyanim);
623 connect(&d.processing_thread_watcher_, SIGNAL(started()),
624 busylabel, SLOT(show()));
625 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
626 busylabel, SLOT(hide()));
627 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
629 QFontMetrics const fm(statusBar()->fontMetrics());
630 int const iconheight = max(int(d.normalIconSize), fm.height());
631 QSize const iconsize(iconheight, iconheight);
633 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
634 shell_escape_ = new QLabel(statusBar());
635 shell_escape_->setPixmap(shellescape);
636 shell_escape_->setScaledContents(true);
637 shell_escape_->setAlignment(Qt::AlignCenter);
638 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
639 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
640 "external commands for this document. "
641 "Right click to change."));
642 SEMenu * menu = new SEMenu(this);
643 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
644 menu, SLOT(showMenu(QPoint)));
645 shell_escape_->hide();
646 statusBar()->addPermanentWidget(shell_escape_);
648 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
649 read_only_ = new QLabel(statusBar());
650 read_only_->setPixmap(readonly);
651 read_only_->setScaledContents(true);
652 read_only_->setAlignment(Qt::AlignCenter);
654 statusBar()->addPermanentWidget(read_only_);
656 version_control_ = new QLabel(statusBar());
657 version_control_->setAlignment(Qt::AlignCenter);
658 version_control_->setFrameStyle(QFrame::StyledPanel);
659 version_control_->hide();
660 statusBar()->addPermanentWidget(version_control_);
662 statusBar()->setSizeGripEnabled(true);
665 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
666 SLOT(autoSaveThreadFinished()));
668 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
669 SLOT(processingThreadStarted()));
670 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
671 SLOT(processingThreadFinished()));
673 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
674 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
676 // set custom application bars context menu, e.g. tool bar and menu bar
677 setContextMenuPolicy(Qt::CustomContextMenu);
678 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
679 SLOT(toolBarPopup(const QPoint &)));
681 // Forbid too small unresizable window because it can happen
682 // with some window manager under X11.
683 setMinimumSize(300, 200);
685 if (lyxrc.allow_geometry_session) {
686 // Now take care of session management.
691 // no session handling, default to a sane size.
692 setGeometry(50, 50, 690, 510);
695 // clear session data if any.
697 settings.remove("views");
707 void GuiView::disableShellEscape()
709 BufferView * bv = documentBufferView();
712 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
713 bv->buffer().params().shell_escape = false;
714 bv->processUpdateFlags(Update::Force);
718 void GuiView::checkCancelBackground()
720 docstring const ttl = _("Cancel Export?");
721 docstring const msg = _("Do you want to cancel the background export process?");
723 Alert::prompt(ttl, msg, 1, 1,
724 _("&Cancel export"), _("Co&ntinue"));
726 Systemcall::killscript();
730 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
732 QVector<GuiWorkArea*> areas;
733 for (int i = 0; i < tabWorkAreaCount(); i++) {
734 TabWorkArea* ta = tabWorkArea(i);
735 for (int u = 0; u < ta->count(); u++) {
736 areas << ta->workArea(u);
742 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
743 string const & format)
745 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
748 case Buffer::ExportSuccess:
749 msg = bformat(_("Successful export to format: %1$s"), fmt);
751 case Buffer::ExportCancel:
752 msg = _("Document export cancelled.");
754 case Buffer::ExportError:
755 case Buffer::ExportNoPathToFormat:
756 case Buffer::ExportTexPathHasSpaces:
757 case Buffer::ExportConverterError:
758 msg = bformat(_("Error while exporting format: %1$s"), fmt);
760 case Buffer::PreviewSuccess:
761 msg = bformat(_("Successful preview of format: %1$s"), fmt);
763 case Buffer::PreviewError:
764 msg = bformat(_("Error while previewing format: %1$s"), fmt);
766 case Buffer::ExportKilled:
767 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
774 void GuiView::processingThreadStarted()
779 void GuiView::processingThreadFinished()
781 QFutureWatcher<Buffer::ExportStatus> const * watcher =
782 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
784 Buffer::ExportStatus const status = watcher->result();
785 handleExportStatus(this, status, d.processing_format);
788 BufferView const * const bv = currentBufferView();
789 if (bv && !bv->buffer().errorList("Export").empty()) {
794 bool const error = (status != Buffer::ExportSuccess &&
795 status != Buffer::PreviewSuccess &&
796 status != Buffer::ExportCancel);
798 ErrorList & el = bv->buffer().errorList(d.last_export_format);
799 // at this point, we do not know if buffer-view or
800 // master-buffer-view was called. If there was an export error,
801 // and the current buffer's error log is empty, we guess that
802 // it must be master-buffer-view that was called so we set
804 errors(d.last_export_format, el.empty());
809 void GuiView::autoSaveThreadFinished()
811 QFutureWatcher<docstring> const * watcher =
812 static_cast<QFutureWatcher<docstring> const *>(sender());
813 message(watcher->result());
818 void GuiView::saveLayout() const
821 settings.setValue("zoom_ratio", zoom_ratio_);
822 settings.setValue("devel_mode", devel_mode_);
823 settings.beginGroup("views");
824 settings.beginGroup(QString::number(id_));
825 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
826 settings.setValue("pos", pos());
827 settings.setValue("size", size());
829 settings.setValue("geometry", saveGeometry());
830 settings.setValue("layout", saveState(0));
831 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
835 void GuiView::saveUISettings() const
839 // Save the toolbar private states
840 for (auto const & tb_p : d.toolbars_)
841 tb_p.second->saveSession(settings);
842 // Now take care of all other dialogs
843 for (auto const & dlg_p : d.dialogs_)
844 dlg_p.second->saveSession(settings);
848 bool GuiView::restoreLayout()
851 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
852 // Actual zoom value: default zoom + fractional offset
853 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
854 if (zoom < static_cast<int>(zoom_min_))
856 lyxrc.currentZoom = zoom;
857 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
858 settings.beginGroup("views");
859 settings.beginGroup(QString::number(id_));
860 QString const icon_key = "icon_size";
861 if (!settings.contains(icon_key))
864 //code below is skipped when when ~/.config/LyX is (re)created
865 setIconSize(d.iconSize(settings.value(icon_key).toString()));
867 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
868 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
869 QSize size = settings.value("size", QSize(690, 510)).toSize();
873 // Work-around for bug #6034: the window ends up in an undetermined
874 // state when trying to restore a maximized window when it is
875 // already maximized.
876 if (!(windowState() & Qt::WindowMaximized))
877 if (!restoreGeometry(settings.value("geometry").toByteArray()))
878 setGeometry(50, 50, 690, 510);
881 // Make sure layout is correctly oriented.
882 setLayoutDirection(qApp->layoutDirection());
884 // Allow the toc and view-source dock widget to be restored if needed.
886 if ((dialog = findOrBuild("toc", true)))
887 // see bug 5082. At least setup title and enabled state.
888 // Visibility will be adjusted by restoreState below.
889 dialog->prepareView();
890 if ((dialog = findOrBuild("view-source", true)))
891 dialog->prepareView();
892 if ((dialog = findOrBuild("progress", true)))
893 dialog->prepareView();
895 if (!restoreState(settings.value("layout").toByteArray(), 0))
898 // init the toolbars that have not been restored
899 for (auto const & tb_p : guiApp->toolbars()) {
900 GuiToolbar * tb = toolbar(tb_p.name);
901 if (tb && !tb->isRestored())
902 initToolbar(tb_p.name);
905 // update lock (all) toolbars positions
906 updateLockToolbars();
913 GuiToolbar * GuiView::toolbar(string const & name)
915 ToolbarMap::iterator it = d.toolbars_.find(name);
916 if (it != d.toolbars_.end())
919 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
924 void GuiView::updateLockToolbars()
926 toolbarsMovable_ = false;
927 for (ToolbarInfo const & info : guiApp->toolbars()) {
928 GuiToolbar * tb = toolbar(info.name);
929 if (tb && tb->isMovable())
930 toolbarsMovable_ = true;
935 void GuiView::constructToolbars()
937 for (auto const & tb_p : d.toolbars_)
941 // I don't like doing this here, but the standard toolbar
942 // destroys this object when it's destroyed itself (vfr)
943 d.layout_ = new LayoutBox(*this);
944 d.stack_widget_->addWidget(d.layout_);
945 d.layout_->move(0,0);
947 // extracts the toolbars from the backend
948 for (ToolbarInfo const & inf : guiApp->toolbars())
949 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
953 void GuiView::initToolbars()
955 // extracts the toolbars from the backend
956 for (ToolbarInfo const & inf : guiApp->toolbars())
957 initToolbar(inf.name);
961 void GuiView::initToolbar(string const & name)
963 GuiToolbar * tb = toolbar(name);
966 int const visibility = guiApp->toolbars().defaultVisibility(name);
967 bool newline = !(visibility & Toolbars::SAMEROW);
968 tb->setVisible(false);
969 tb->setVisibility(visibility);
971 if (visibility & Toolbars::TOP) {
973 addToolBarBreak(Qt::TopToolBarArea);
974 addToolBar(Qt::TopToolBarArea, tb);
977 if (visibility & Toolbars::BOTTOM) {
979 addToolBarBreak(Qt::BottomToolBarArea);
980 addToolBar(Qt::BottomToolBarArea, tb);
983 if (visibility & Toolbars::LEFT) {
985 addToolBarBreak(Qt::LeftToolBarArea);
986 addToolBar(Qt::LeftToolBarArea, tb);
989 if (visibility & Toolbars::RIGHT) {
991 addToolBarBreak(Qt::RightToolBarArea);
992 addToolBar(Qt::RightToolBarArea, tb);
995 if (visibility & Toolbars::ON)
996 tb->setVisible(true);
998 tb->setMovable(true);
1002 TocModels & GuiView::tocModels()
1004 return d.toc_models_;
1008 void GuiView::setFocus()
1010 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1011 QMainWindow::setFocus();
1015 bool GuiView::hasFocus() const
1017 if (currentWorkArea())
1018 return currentWorkArea()->hasFocus();
1019 if (currentMainWorkArea())
1020 return currentMainWorkArea()->hasFocus();
1021 return d.bg_widget_->hasFocus();
1025 void GuiView::focusInEvent(QFocusEvent * e)
1027 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1028 QMainWindow::focusInEvent(e);
1029 // Make sure guiApp points to the correct view.
1030 guiApp->setCurrentView(this);
1031 if (currentWorkArea())
1032 currentWorkArea()->setFocus();
1033 else if (currentMainWorkArea())
1034 currentMainWorkArea()->setFocus();
1036 d.bg_widget_->setFocus();
1040 void GuiView::showEvent(QShowEvent * e)
1042 LYXERR(Debug::GUI, "Passed Geometry "
1043 << size().height() << "x" << size().width()
1044 << "+" << pos().x() << "+" << pos().y());
1046 if (d.splitter_->count() == 0)
1047 // No work area, switch to the background widget.
1051 QMainWindow::showEvent(e);
1055 bool GuiView::closeScheduled()
1062 bool GuiView::prepareAllBuffersForLogout()
1064 Buffer * first = theBufferList().first();
1068 // First, iterate over all buffers and ask the users if unsaved
1069 // changes should be saved.
1070 // We cannot use a for loop as the buffer list cycles.
1073 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1075 b = theBufferList().next(b);
1076 } while (b != first);
1078 // Next, save session state
1079 // When a view/window was closed before without quitting LyX, there
1080 // are already entries in the lastOpened list.
1081 theSession().lastOpened().clear();
1088 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1089 ** is responsibility of the container (e.g., dialog)
1091 void GuiView::closeEvent(QCloseEvent * close_event)
1093 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1095 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1096 Alert::warning(_("Exit LyX"),
1097 _("LyX could not be closed because documents are being processed by LyX."));
1098 close_event->setAccepted(false);
1102 // If the user pressed the x (so we didn't call closeView
1103 // programmatically), we want to clear all existing entries.
1105 theSession().lastOpened().clear();
1110 // it can happen that this event arrives without selecting the view,
1111 // e.g. when clicking the close button on a background window.
1113 if (!closeWorkAreaAll()) {
1115 close_event->ignore();
1119 // Make sure that nothing will use this to be closed View.
1120 guiApp->unregisterView(this);
1122 if (isFullScreen()) {
1123 // Switch off fullscreen before closing.
1128 // Make sure the timer time out will not trigger a statusbar update.
1129 d.statusbar_timer_.stop();
1131 // Saving fullscreen requires additional tweaks in the toolbar code.
1132 // It wouldn't also work under linux natively.
1133 if (lyxrc.allow_geometry_session) {
1138 close_event->accept();
1142 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1144 if (event->mimeData()->hasUrls())
1146 /// \todo Ask lyx-devel is this is enough:
1147 /// if (event->mimeData()->hasFormat("text/plain"))
1148 /// event->acceptProposedAction();
1152 void GuiView::dropEvent(QDropEvent * event)
1154 QList<QUrl> files = event->mimeData()->urls();
1155 if (files.isEmpty())
1158 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1159 for (int i = 0; i != files.size(); ++i) {
1160 string const file = os::internal_path(fromqstr(
1161 files.at(i).toLocalFile()));
1165 string const ext = support::getExtension(file);
1166 vector<const Format *> found_formats;
1168 // Find all formats that have the correct extension.
1169 for (const Format * fmt : theConverters().importableFormats())
1170 if (fmt->hasExtension(ext))
1171 found_formats.push_back(fmt);
1174 if (!found_formats.empty()) {
1175 if (found_formats.size() > 1) {
1176 //FIXME: show a dialog to choose the correct importable format
1177 LYXERR(Debug::FILES,
1178 "Multiple importable formats found, selecting first");
1180 string const arg = found_formats[0]->name() + " " + file;
1181 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1184 //FIXME: do we have to explicitly check whether it's a lyx file?
1185 LYXERR(Debug::FILES,
1186 "No formats found, trying to open it as a lyx file");
1187 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1189 // add the functions to the queue
1190 guiApp->addToFuncRequestQueue(cmd);
1193 // now process the collected functions. We perform the events
1194 // asynchronously. This prevents potential problems in case the
1195 // BufferView is closed within an event.
1196 guiApp->processFuncRequestQueueAsync();
1200 void GuiView::message(docstring const & str)
1202 if (ForkedProcess::iAmAChild())
1205 // call is moved to GUI-thread by GuiProgress
1206 d.progress_->appendMessage(toqstr(str));
1210 void GuiView::clearMessageText()
1212 message(docstring());
1216 void GuiView::updateStatusBarMessage(QString const & str)
1218 statusBar()->showMessage(str);
1219 d.statusbar_timer_.stop();
1220 d.statusbar_timer_.start(3000);
1224 void GuiView::clearMessage()
1226 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1227 // the hasFocus function mostly returns false, even if the focus is on
1228 // a workarea in this view.
1232 d.statusbar_timer_.stop();
1236 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1238 if (wa != d.current_work_area_
1239 || wa->bufferView().buffer().isInternal())
1241 Buffer const & buf = wa->bufferView().buffer();
1242 // Set the windows title
1243 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1244 if (buf.notifiesExternalModification()) {
1245 title = bformat(_("%1$s (modified externally)"), title);
1246 // If the external modification status has changed, then maybe the status of
1247 // buffer-save has changed too.
1251 title += from_ascii(" - LyX");
1253 setWindowTitle(toqstr(title));
1254 // Sets the path for the window: this is used by OSX to
1255 // allow a context click on the title bar showing a menu
1256 // with the path up to the file
1257 setWindowFilePath(toqstr(buf.absFileName()));
1258 // Tell Qt whether the current document is changed
1259 setWindowModified(!buf.isClean());
1261 if (buf.params().shell_escape)
1262 shell_escape_->show();
1264 shell_escape_->hide();
1266 if (buf.hasReadonlyFlag())
1271 if (buf.lyxvc().inUse()) {
1272 version_control_->show();
1273 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1275 version_control_->hide();
1279 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1281 if (d.current_work_area_)
1282 // disconnect the current work area from all slots
1283 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1285 disconnectBufferView();
1286 connectBufferView(wa->bufferView());
1287 connectBuffer(wa->bufferView().buffer());
1288 d.current_work_area_ = wa;
1289 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1290 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1291 QObject::connect(wa, SIGNAL(busy(bool)),
1292 this, SLOT(setBusy(bool)));
1293 // connection of a signal to a signal
1294 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1295 this, SIGNAL(bufferViewChanged()));
1296 Q_EMIT updateWindowTitle(wa);
1297 Q_EMIT bufferViewChanged();
1301 void GuiView::onBufferViewChanged()
1304 // Buffer-dependent dialogs must be updated. This is done here because
1305 // some dialogs require buffer()->text.
1310 void GuiView::on_lastWorkAreaRemoved()
1313 // We already are in a close event. Nothing more to do.
1316 if (d.splitter_->count() > 1)
1317 // We have a splitter so don't close anything.
1320 // Reset and updates the dialogs.
1321 Q_EMIT bufferViewChanged();
1326 if (lyxrc.open_buffers_in_tabs)
1327 // Nothing more to do, the window should stay open.
1330 if (guiApp->viewIds().size() > 1) {
1336 // On Mac we also close the last window because the application stay
1337 // resident in memory. On other platforms we don't close the last
1338 // window because this would quit the application.
1344 void GuiView::updateStatusBar()
1346 // let the user see the explicit message
1347 if (d.statusbar_timer_.isActive())
1354 void GuiView::showMessage()
1358 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1359 if (msg.isEmpty()) {
1360 BufferView const * bv = currentBufferView();
1362 msg = toqstr(bv->cursor().currentState(devel_mode_));
1364 msg = qt_("Welcome to LyX!");
1366 statusBar()->showMessage(msg);
1370 bool GuiView::event(QEvent * e)
1374 // Useful debug code:
1375 //case QEvent::ActivationChange:
1376 //case QEvent::WindowDeactivate:
1377 //case QEvent::Paint:
1378 //case QEvent::Enter:
1379 //case QEvent::Leave:
1380 //case QEvent::HoverEnter:
1381 //case QEvent::HoverLeave:
1382 //case QEvent::HoverMove:
1383 //case QEvent::StatusTip:
1384 //case QEvent::DragEnter:
1385 //case QEvent::DragLeave:
1386 //case QEvent::Drop:
1389 case QEvent::WindowStateChange: {
1390 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1391 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1392 bool result = QMainWindow::event(e);
1393 bool nfstate = (windowState() & Qt::WindowFullScreen);
1394 if (!ofstate && nfstate) {
1395 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1396 // switch to full-screen state
1397 if (lyxrc.full_screen_statusbar)
1398 statusBar()->hide();
1399 if (lyxrc.full_screen_menubar)
1401 if (lyxrc.full_screen_toolbars) {
1402 for (auto const & tb_p : d.toolbars_)
1403 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1404 tb_p.second->hide();
1406 for (int i = 0; i != d.splitter_->count(); ++i)
1407 d.tabWorkArea(i)->setFullScreen(true);
1408 #if QT_VERSION > 0x050903
1409 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1410 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1412 setContentsMargins(-2, -2, -2, -2);
1414 hideDialogs("prefs", nullptr);
1415 } else if (ofstate && !nfstate) {
1416 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1417 // switch back from full-screen state
1418 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1419 statusBar()->show();
1420 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1422 if (lyxrc.full_screen_toolbars) {
1423 for (auto const & tb_p : d.toolbars_)
1424 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1425 tb_p.second->show();
1428 for (int i = 0; i != d.splitter_->count(); ++i)
1429 d.tabWorkArea(i)->setFullScreen(false);
1430 #if QT_VERSION > 0x050903
1431 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1433 setContentsMargins(0, 0, 0, 0);
1437 case QEvent::WindowActivate: {
1438 GuiView * old_view = guiApp->currentView();
1439 if (this == old_view) {
1441 return QMainWindow::event(e);
1443 if (old_view && old_view->currentBufferView()) {
1444 // save current selection to the selection buffer to allow
1445 // middle-button paste in this window.
1446 cap::saveSelection(old_view->currentBufferView()->cursor());
1448 guiApp->setCurrentView(this);
1449 if (d.current_work_area_)
1450 on_currentWorkAreaChanged(d.current_work_area_);
1454 return QMainWindow::event(e);
1457 case QEvent::ShortcutOverride: {
1459 if (isFullScreen() && menuBar()->isHidden()) {
1460 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1461 // FIXME: we should also try to detect special LyX shortcut such as
1462 // Alt-P and Alt-M. Right now there is a hack in
1463 // GuiWorkArea::processKeySym() that hides again the menubar for
1465 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1467 return QMainWindow::event(e);
1470 return QMainWindow::event(e);
1473 case QEvent::ApplicationPaletteChange: {
1474 // runtime switch from/to dark mode
1476 return QMainWindow::event(e);
1480 return QMainWindow::event(e);
1484 void GuiView::resetWindowTitle()
1486 setWindowTitle(qt_("LyX"));
1489 bool GuiView::focusNextPrevChild(bool /*next*/)
1496 bool GuiView::busy() const
1502 void GuiView::setBusy(bool busy)
1504 bool const busy_before = busy_ > 0;
1505 busy ? ++busy_ : --busy_;
1506 if ((busy_ > 0) == busy_before)
1507 // busy state didn't change
1511 QApplication::setOverrideCursor(Qt::WaitCursor);
1514 QApplication::restoreOverrideCursor();
1519 void GuiView::resetCommandExecute()
1521 command_execute_ = false;
1526 double GuiView::pixelRatio() const
1528 #if QT_VERSION >= 0x050000
1529 return qt_scale_factor * devicePixelRatio();
1536 GuiWorkArea * GuiView::workArea(int index)
1538 if (TabWorkArea * twa = d.currentTabWorkArea())
1539 if (index < twa->count())
1540 return twa->workArea(index);
1545 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1547 if (currentWorkArea()
1548 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1549 return currentWorkArea();
1550 if (TabWorkArea * twa = d.currentTabWorkArea())
1551 return twa->workArea(buffer);
1556 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1558 // Automatically create a TabWorkArea if there are none yet.
1559 TabWorkArea * tab_widget = d.splitter_->count()
1560 ? d.currentTabWorkArea() : addTabWorkArea();
1561 return tab_widget->addWorkArea(buffer, *this);
1565 TabWorkArea * GuiView::addTabWorkArea()
1567 TabWorkArea * twa = new TabWorkArea;
1568 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1569 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1570 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1571 this, SLOT(on_lastWorkAreaRemoved()));
1573 d.splitter_->addWidget(twa);
1574 d.stack_widget_->setCurrentWidget(d.splitter_);
1579 GuiWorkArea const * GuiView::currentWorkArea() const
1581 return d.current_work_area_;
1585 GuiWorkArea * GuiView::currentWorkArea()
1587 return d.current_work_area_;
1591 GuiWorkArea const * GuiView::currentMainWorkArea() const
1593 if (!d.currentTabWorkArea())
1595 return d.currentTabWorkArea()->currentWorkArea();
1599 GuiWorkArea * GuiView::currentMainWorkArea()
1601 if (!d.currentTabWorkArea())
1603 return d.currentTabWorkArea()->currentWorkArea();
1607 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1609 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1611 d.current_work_area_ = nullptr;
1613 Q_EMIT bufferViewChanged();
1617 // FIXME: I've no clue why this is here and why it accesses
1618 // theGuiApp()->currentView, which might be 0 (bug 6464).
1619 // See also 27525 (vfr).
1620 if (theGuiApp()->currentView() == this
1621 && theGuiApp()->currentView()->currentWorkArea() == wa)
1624 if (currentBufferView())
1625 cap::saveSelection(currentBufferView()->cursor());
1627 theGuiApp()->setCurrentView(this);
1628 d.current_work_area_ = wa;
1630 // We need to reset this now, because it will need to be
1631 // right if the tabWorkArea gets reset in the for loop. We
1632 // will change it back if we aren't in that case.
1633 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1634 d.current_main_work_area_ = wa;
1636 for (int i = 0; i != d.splitter_->count(); ++i) {
1637 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1638 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1639 << ", Current main wa: " << currentMainWorkArea());
1644 d.current_main_work_area_ = old_cmwa;
1646 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1647 on_currentWorkAreaChanged(wa);
1648 BufferView & bv = wa->bufferView();
1649 bv.cursor().fixIfBroken();
1651 wa->setUpdatesEnabled(true);
1652 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1656 void GuiView::removeWorkArea(GuiWorkArea * wa)
1658 LASSERT(wa, return);
1659 if (wa == d.current_work_area_) {
1661 disconnectBufferView();
1662 d.current_work_area_ = nullptr;
1663 d.current_main_work_area_ = nullptr;
1666 bool found_twa = false;
1667 for (int i = 0; i != d.splitter_->count(); ++i) {
1668 TabWorkArea * twa = d.tabWorkArea(i);
1669 if (twa->removeWorkArea(wa)) {
1670 // Found in this tab group, and deleted the GuiWorkArea.
1672 if (twa->count() != 0) {
1673 if (d.current_work_area_ == nullptr)
1674 // This means that we are closing the current GuiWorkArea, so
1675 // switch to the next GuiWorkArea in the found TabWorkArea.
1676 setCurrentWorkArea(twa->currentWorkArea());
1678 // No more WorkAreas in this tab group, so delete it.
1685 // It is not a tabbed work area (i.e., the search work area), so it
1686 // should be deleted by other means.
1687 LASSERT(found_twa, return);
1689 if (d.current_work_area_ == nullptr) {
1690 if (d.splitter_->count() != 0) {
1691 TabWorkArea * twa = d.currentTabWorkArea();
1692 setCurrentWorkArea(twa->currentWorkArea());
1694 // No more work areas, switch to the background widget.
1695 setCurrentWorkArea(nullptr);
1701 LayoutBox * GuiView::getLayoutDialog() const
1707 void GuiView::updateLayoutList()
1710 d.layout_->updateContents(false);
1714 void GuiView::updateToolbars()
1716 if (d.current_work_area_) {
1718 if (d.current_work_area_->bufferView().cursor().inMathed()
1719 && !d.current_work_area_->bufferView().cursor().inRegexped())
1720 context |= Toolbars::MATH;
1721 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1722 context |= Toolbars::TABLE;
1723 if (currentBufferView()->buffer().areChangesPresent()
1724 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1725 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1726 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1727 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1728 context |= Toolbars::REVIEW;
1729 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1730 context |= Toolbars::MATHMACROTEMPLATE;
1731 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1732 context |= Toolbars::IPA;
1733 if (command_execute_)
1734 context |= Toolbars::MINIBUFFER;
1735 if (minibuffer_focus_) {
1736 context |= Toolbars::MINIBUFFER_FOCUS;
1737 minibuffer_focus_ = false;
1740 for (auto const & tb_p : d.toolbars_)
1741 tb_p.second->update(context);
1743 for (auto const & tb_p : d.toolbars_)
1744 tb_p.second->update();
1748 void GuiView::refillToolbars()
1750 for (auto const & tb_p : d.toolbars_)
1751 tb_p.second->refill();
1755 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1757 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1758 LASSERT(newBuffer, return);
1760 GuiWorkArea * wa = workArea(*newBuffer);
1761 if (wa == nullptr) {
1763 newBuffer->masterBuffer()->updateBuffer();
1765 wa = addWorkArea(*newBuffer);
1766 // scroll to the position when the BufferView was last closed
1767 if (lyxrc.use_lastfilepos) {
1768 LastFilePosSection::FilePos filepos =
1769 theSession().lastFilePos().load(newBuffer->fileName());
1770 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1773 //Disconnect the old buffer...there's no new one.
1776 connectBuffer(*newBuffer);
1777 connectBufferView(wa->bufferView());
1779 setCurrentWorkArea(wa);
1783 void GuiView::connectBuffer(Buffer & buf)
1785 buf.setGuiDelegate(this);
1789 void GuiView::disconnectBuffer()
1791 if (d.current_work_area_)
1792 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1796 void GuiView::connectBufferView(BufferView & bv)
1798 bv.setGuiDelegate(this);
1802 void GuiView::disconnectBufferView()
1804 if (d.current_work_area_)
1805 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1809 void GuiView::errors(string const & error_type, bool from_master)
1811 BufferView const * const bv = currentBufferView();
1815 ErrorList const & el = from_master ?
1816 bv->buffer().masterBuffer()->errorList(error_type) :
1817 bv->buffer().errorList(error_type);
1822 string err = error_type;
1824 err = "from_master|" + error_type;
1825 showDialog("errorlist", err);
1829 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1831 d.toc_models_.updateItem(toqstr(type), dit);
1835 void GuiView::structureChanged()
1837 // This is called from the Buffer, which has no way to ensure that cursors
1838 // in BufferView remain valid.
1839 if (documentBufferView())
1840 documentBufferView()->cursor().sanitize();
1841 // FIXME: This is slightly expensive, though less than the tocBackend update
1842 // (#9880). This also resets the view in the Toc Widget (#6675).
1843 d.toc_models_.reset(documentBufferView());
1844 // Navigator needs more than a simple update in this case. It needs to be
1846 updateDialog("toc", "");
1850 void GuiView::updateDialog(string const & name, string const & sdata)
1852 if (!isDialogVisible(name))
1855 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1856 if (it == d.dialogs_.end())
1859 Dialog * const dialog = it->second.get();
1860 if (dialog->isVisibleView())
1861 dialog->initialiseParams(sdata);
1865 BufferView * GuiView::documentBufferView()
1867 return currentMainWorkArea()
1868 ? ¤tMainWorkArea()->bufferView()
1873 BufferView const * GuiView::documentBufferView() const
1875 return currentMainWorkArea()
1876 ? ¤tMainWorkArea()->bufferView()
1881 BufferView * GuiView::currentBufferView()
1883 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1887 BufferView const * GuiView::currentBufferView() const
1889 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1893 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1894 Buffer const * orig, Buffer * clone)
1896 bool const success = clone->autoSave();
1898 busyBuffers.remove(orig);
1900 ? _("Automatic save done.")
1901 : _("Automatic save failed!");
1905 void GuiView::autoSave()
1907 LYXERR(Debug::INFO, "Running autoSave()");
1909 Buffer * buffer = documentBufferView()
1910 ? &documentBufferView()->buffer() : nullptr;
1912 resetAutosaveTimers();
1916 GuiViewPrivate::busyBuffers.insert(buffer);
1917 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1918 buffer, buffer->cloneBufferOnly());
1919 d.autosave_watcher_.setFuture(f);
1920 resetAutosaveTimers();
1924 void GuiView::resetAutosaveTimers()
1927 d.autosave_timeout_.restart();
1931 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1934 Buffer * buf = currentBufferView()
1935 ? ¤tBufferView()->buffer() : nullptr;
1936 Buffer * doc_buffer = documentBufferView()
1937 ? &(documentBufferView()->buffer()) : nullptr;
1940 /* In LyX/Mac, when a dialog is open, the menus of the
1941 application can still be accessed without giving focus to
1942 the main window. In this case, we want to disable the menu
1943 entries that are buffer-related.
1944 This code must not be used on Linux and Windows, since it
1945 would disable buffer-related entries when hovering over the
1946 menu (see bug #9574).
1948 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1954 // Check whether we need a buffer
1955 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1956 // no, exit directly
1957 flag.message(from_utf8(N_("Command not allowed with"
1958 "out any document open")));
1959 flag.setEnabled(false);
1963 if (cmd.origin() == FuncRequest::TOC) {
1964 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1965 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1966 flag.setEnabled(false);
1970 switch(cmd.action()) {
1971 case LFUN_BUFFER_IMPORT:
1974 case LFUN_MASTER_BUFFER_EXPORT:
1976 && (doc_buffer->parent() != nullptr
1977 || doc_buffer->hasChildren())
1978 && !d.processing_thread_watcher_.isRunning()
1979 // this launches a dialog, which would be in the wrong Buffer
1980 && !(::lyx::operator==(cmd.argument(), "custom"));
1983 case LFUN_MASTER_BUFFER_UPDATE:
1984 case LFUN_MASTER_BUFFER_VIEW:
1986 && (doc_buffer->parent() != nullptr
1987 || doc_buffer->hasChildren())
1988 && !d.processing_thread_watcher_.isRunning();
1991 case LFUN_BUFFER_UPDATE:
1992 case LFUN_BUFFER_VIEW: {
1993 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1997 string format = to_utf8(cmd.argument());
1998 if (cmd.argument().empty())
1999 format = doc_buffer->params().getDefaultOutputFormat();
2000 enable = doc_buffer->params().isExportable(format, true);
2004 case LFUN_BUFFER_RELOAD:
2005 enable = doc_buffer && !doc_buffer->isUnnamed()
2006 && doc_buffer->fileName().exists()
2007 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2010 case LFUN_BUFFER_RESET_EXPORT:
2011 enable = doc_buffer != nullptr;
2014 case LFUN_BUFFER_CHILD_OPEN:
2015 enable = doc_buffer != nullptr;
2018 case LFUN_MASTER_BUFFER_FORALL: {
2019 if (doc_buffer == nullptr) {
2020 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2024 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2025 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2026 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2031 for (Buffer * buf : doc_buffer->allRelatives()) {
2032 GuiWorkArea * wa = workArea(*buf);
2035 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2036 enable = flag.enabled();
2043 case LFUN_BUFFER_WRITE:
2044 enable = doc_buffer && (doc_buffer->isUnnamed()
2045 || (!doc_buffer->isClean()
2046 || cmd.argument() == "force"));
2049 //FIXME: This LFUN should be moved to GuiApplication.
2050 case LFUN_BUFFER_WRITE_ALL: {
2051 // We enable the command only if there are some modified buffers
2052 Buffer * first = theBufferList().first();
2057 // We cannot use a for loop as the buffer list is a cycle.
2059 if (!b->isClean()) {
2063 b = theBufferList().next(b);
2064 } while (b != first);
2068 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2069 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2072 case LFUN_BUFFER_EXPORT: {
2073 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2077 return doc_buffer->getStatus(cmd, flag);
2080 case LFUN_BUFFER_EXPORT_AS:
2081 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2086 case LFUN_BUFFER_WRITE_AS:
2087 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2088 enable = doc_buffer != nullptr;
2091 case LFUN_EXPORT_CANCEL:
2092 enable = d.processing_thread_watcher_.isRunning();
2095 case LFUN_BUFFER_CLOSE:
2096 case LFUN_VIEW_CLOSE:
2097 enable = doc_buffer != nullptr;
2100 case LFUN_BUFFER_CLOSE_ALL:
2101 enable = theBufferList().last() != theBufferList().first();
2104 case LFUN_BUFFER_CHKTEX: {
2105 // hide if we have no checktex command
2106 if (lyxrc.chktex_command.empty()) {
2107 flag.setUnknown(true);
2111 if (!doc_buffer || !doc_buffer->params().isLatex()
2112 || d.processing_thread_watcher_.isRunning()) {
2113 // grey out, don't hide
2121 case LFUN_VIEW_SPLIT:
2122 if (cmd.getArg(0) == "vertical")
2123 enable = doc_buffer && (d.splitter_->count() == 1 ||
2124 d.splitter_->orientation() == Qt::Vertical);
2126 enable = doc_buffer && (d.splitter_->count() == 1 ||
2127 d.splitter_->orientation() == Qt::Horizontal);
2130 case LFUN_TAB_GROUP_CLOSE:
2131 enable = d.tabWorkAreaCount() > 1;
2134 case LFUN_DEVEL_MODE_TOGGLE:
2135 flag.setOnOff(devel_mode_);
2138 case LFUN_TOOLBAR_SET: {
2139 string const name = cmd.getArg(0);
2140 string const state = cmd.getArg(1);
2141 if (name.empty() || state.empty()) {
2143 docstring const msg =
2144 _("Function toolbar-set requires two arguments!");
2148 if (state != "on" && state != "off" && state != "auto") {
2150 docstring const msg =
2151 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2156 if (GuiToolbar * t = toolbar(name)) {
2157 bool const autovis = t->visibility() & Toolbars::AUTO;
2159 flag.setOnOff(t->isVisible() && !autovis);
2160 else if (state == "off")
2161 flag.setOnOff(!t->isVisible() && !autovis);
2162 else if (state == "auto")
2163 flag.setOnOff(autovis);
2166 docstring const msg =
2167 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2173 case LFUN_TOOLBAR_TOGGLE: {
2174 string const name = cmd.getArg(0);
2175 if (GuiToolbar * t = toolbar(name))
2176 flag.setOnOff(t->isVisible());
2179 docstring const msg =
2180 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2186 case LFUN_TOOLBAR_MOVABLE: {
2187 string const name = cmd.getArg(0);
2188 // use negation since locked == !movable
2190 // toolbar name * locks all toolbars
2191 flag.setOnOff(!toolbarsMovable_);
2192 else if (GuiToolbar * t = toolbar(name))
2193 flag.setOnOff(!(t->isMovable()));
2196 docstring const msg =
2197 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2203 case LFUN_ICON_SIZE:
2204 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2207 case LFUN_DROP_LAYOUTS_CHOICE:
2208 enable = buf != nullptr;
2211 case LFUN_UI_TOGGLE:
2212 flag.setOnOff(isFullScreen());
2215 case LFUN_DIALOG_DISCONNECT_INSET:
2218 case LFUN_DIALOG_HIDE:
2219 // FIXME: should we check if the dialog is shown?
2222 case LFUN_DIALOG_TOGGLE:
2223 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2226 case LFUN_DIALOG_SHOW: {
2227 string const name = cmd.getArg(0);
2229 enable = name == "aboutlyx"
2230 || name == "file" //FIXME: should be removed.
2231 || name == "lyxfiles"
2233 || name == "texinfo"
2234 || name == "progress"
2235 || name == "compare";
2236 else if (name == "character" || name == "symbols"
2237 || name == "mathdelimiter" || name == "mathmatrix") {
2238 if (!buf || buf->isReadonly())
2241 Cursor const & cur = currentBufferView()->cursor();
2242 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2245 else if (name == "latexlog")
2246 enable = FileName(doc_buffer->logName()).isReadableFile();
2247 else if (name == "spellchecker")
2248 enable = theSpellChecker()
2249 && !doc_buffer->text().empty();
2250 else if (name == "vclog")
2251 enable = doc_buffer->lyxvc().inUse();
2255 case LFUN_DIALOG_UPDATE: {
2256 string const name = cmd.getArg(0);
2258 enable = name == "prefs";
2262 case LFUN_COMMAND_EXECUTE:
2264 case LFUN_MENU_OPEN:
2265 // Nothing to check.
2268 case LFUN_COMPLETION_INLINE:
2269 if (!d.current_work_area_
2270 || !d.current_work_area_->completer().inlinePossible(
2271 currentBufferView()->cursor()))
2275 case LFUN_COMPLETION_POPUP:
2276 if (!d.current_work_area_
2277 || !d.current_work_area_->completer().popupPossible(
2278 currentBufferView()->cursor()))
2283 if (!d.current_work_area_
2284 || !d.current_work_area_->completer().inlinePossible(
2285 currentBufferView()->cursor()))
2289 case LFUN_COMPLETION_ACCEPT:
2290 if (!d.current_work_area_
2291 || (!d.current_work_area_->completer().popupVisible()
2292 && !d.current_work_area_->completer().inlineVisible()
2293 && !d.current_work_area_->completer().completionAvailable()))
2297 case LFUN_COMPLETION_CANCEL:
2298 if (!d.current_work_area_
2299 || (!d.current_work_area_->completer().popupVisible()
2300 && !d.current_work_area_->completer().inlineVisible()))
2304 case LFUN_BUFFER_ZOOM_OUT:
2305 case LFUN_BUFFER_ZOOM_IN: {
2306 // only diff between these two is that the default for ZOOM_OUT
2308 bool const neg_zoom =
2309 convert<int>(cmd.argument()) < 0 ||
2310 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2311 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2312 docstring const msg =
2313 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2317 enable = doc_buffer;
2321 case LFUN_BUFFER_ZOOM: {
2322 bool const less_than_min_zoom =
2323 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2324 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2325 docstring const msg =
2326 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2331 enable = doc_buffer;
2335 case LFUN_BUFFER_MOVE_NEXT:
2336 case LFUN_BUFFER_MOVE_PREVIOUS:
2337 // we do not cycle when moving
2338 case LFUN_BUFFER_NEXT:
2339 case LFUN_BUFFER_PREVIOUS:
2340 // because we cycle, it doesn't matter whether on first or last
2341 enable = (d.currentTabWorkArea()->count() > 1);
2343 case LFUN_BUFFER_SWITCH:
2344 // toggle on the current buffer, but do not toggle off
2345 // the other ones (is that a good idea?)
2347 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2348 flag.setOnOff(true);
2351 case LFUN_VC_REGISTER:
2352 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2354 case LFUN_VC_RENAME:
2355 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2358 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2360 case LFUN_VC_CHECK_IN:
2361 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2363 case LFUN_VC_CHECK_OUT:
2364 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2366 case LFUN_VC_LOCKING_TOGGLE:
2367 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2368 && doc_buffer->lyxvc().lockingToggleEnabled();
2369 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2371 case LFUN_VC_REVERT:
2372 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2373 && !doc_buffer->hasReadonlyFlag();
2375 case LFUN_VC_UNDO_LAST:
2376 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2378 case LFUN_VC_REPO_UPDATE:
2379 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2381 case LFUN_VC_COMMAND: {
2382 if (cmd.argument().empty())
2384 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2388 case LFUN_VC_COMPARE:
2389 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2392 case LFUN_SERVER_GOTO_FILE_ROW:
2393 case LFUN_LYX_ACTIVATE:
2394 case LFUN_WINDOW_RAISE:
2396 case LFUN_FORWARD_SEARCH:
2397 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2400 case LFUN_FILE_INSERT_PLAINTEXT:
2401 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2402 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2405 case LFUN_SPELLING_CONTINUOUSLY:
2406 flag.setOnOff(lyxrc.spellcheck_continuously);
2409 case LFUN_CITATION_OPEN:
2418 flag.setEnabled(false);
2424 static FileName selectTemplateFile()
2426 FileDialog dlg(qt_("Select template file"));
2427 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2428 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2430 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2431 QStringList(qt_("LyX Documents (*.lyx)")));
2433 if (result.first == FileDialog::Later)
2435 if (result.second.isEmpty())
2437 return FileName(fromqstr(result.second));
2441 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2445 Buffer * newBuffer = nullptr;
2447 newBuffer = checkAndLoadLyXFile(filename);
2448 } catch (ExceptionMessage const &) {
2455 message(_("Document not loaded."));
2459 setBuffer(newBuffer);
2460 newBuffer->errors("Parse");
2463 theSession().lastFiles().add(filename);
2464 theSession().writeFile();
2471 void GuiView::openDocument(string const & fname)
2473 string initpath = lyxrc.document_path;
2475 if (documentBufferView()) {
2476 string const trypath = documentBufferView()->buffer().filePath();
2477 // If directory is writeable, use this as default.
2478 if (FileName(trypath).isDirWritable())
2484 if (fname.empty()) {
2485 FileDialog dlg(qt_("Select document to open"));
2486 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2487 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2489 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2490 FileDialog::Result result =
2491 dlg.open(toqstr(initpath), filter);
2493 if (result.first == FileDialog::Later)
2496 filename = fromqstr(result.second);
2498 // check selected filename
2499 if (filename.empty()) {
2500 message(_("Canceled."));
2506 // get absolute path of file and add ".lyx" to the filename if
2508 FileName const fullname =
2509 fileSearch(string(), filename, "lyx", support::may_not_exist);
2510 if (!fullname.empty())
2511 filename = fullname.absFileName();
2513 if (!fullname.onlyPath().isDirectory()) {
2514 Alert::warning(_("Invalid filename"),
2515 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2516 from_utf8(fullname.absFileName())));
2520 // if the file doesn't exist and isn't already open (bug 6645),
2521 // let the user create one
2522 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2523 !LyXVC::file_not_found_hook(fullname)) {
2524 // the user specifically chose this name. Believe him.
2525 Buffer * const b = newFile(filename, string(), true);
2531 docstring const disp_fn = makeDisplayPath(filename);
2532 message(bformat(_("Opening document %1$s..."), disp_fn));
2535 Buffer * buf = loadDocument(fullname);
2537 str2 = bformat(_("Document %1$s opened."), disp_fn);
2538 if (buf->lyxvc().inUse())
2539 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2540 " " + _("Version control detected.");
2542 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2547 // FIXME: clean that
2548 static bool import(GuiView * lv, FileName const & filename,
2549 string const & format, ErrorList & errorList)
2551 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2553 string loader_format;
2554 vector<string> loaders = theConverters().loaders();
2555 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2556 for (string const & loader : loaders) {
2557 if (!theConverters().isReachable(format, loader))
2560 string const tofile =
2561 support::changeExtension(filename.absFileName(),
2562 theFormats().extension(loader));
2563 if (theConverters().convert(nullptr, filename, FileName(tofile),
2564 filename, format, loader, errorList) != Converters::SUCCESS)
2566 loader_format = loader;
2569 if (loader_format.empty()) {
2570 frontend::Alert::error(_("Couldn't import file"),
2571 bformat(_("No information for importing the format %1$s."),
2572 translateIfPossible(theFormats().prettyName(format))));
2576 loader_format = format;
2578 if (loader_format == "lyx") {
2579 Buffer * buf = lv->loadDocument(lyxfile);
2583 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2587 bool as_paragraphs = loader_format == "textparagraph";
2588 string filename2 = (loader_format == format) ? filename.absFileName()
2589 : support::changeExtension(filename.absFileName(),
2590 theFormats().extension(loader_format));
2591 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2593 guiApp->setCurrentView(lv);
2594 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2601 void GuiView::importDocument(string const & argument)
2604 string filename = split(argument, format, ' ');
2606 LYXERR(Debug::INFO, format << " file: " << filename);
2608 // need user interaction
2609 if (filename.empty()) {
2610 string initpath = lyxrc.document_path;
2611 if (documentBufferView()) {
2612 string const trypath = documentBufferView()->buffer().filePath();
2613 // If directory is writeable, use this as default.
2614 if (FileName(trypath).isDirWritable())
2618 docstring const text = bformat(_("Select %1$s file to import"),
2619 translateIfPossible(theFormats().prettyName(format)));
2621 FileDialog dlg(toqstr(text));
2622 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2623 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2625 docstring filter = translateIfPossible(theFormats().prettyName(format));
2628 filter += from_utf8(theFormats().extensions(format));
2631 FileDialog::Result result =
2632 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2634 if (result.first == FileDialog::Later)
2637 filename = fromqstr(result.second);
2639 // check selected filename
2640 if (filename.empty())
2641 message(_("Canceled."));
2644 if (filename.empty())
2647 // get absolute path of file
2648 FileName const fullname(support::makeAbsPath(filename));
2650 // Can happen if the user entered a path into the dialog
2652 if (fullname.onlyFileName().empty()) {
2653 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2654 "Aborting import."),
2655 from_utf8(fullname.absFileName()));
2656 frontend::Alert::error(_("File name error"), msg);
2657 message(_("Canceled."));
2662 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2664 // Check if the document already is open
2665 Buffer * buf = theBufferList().getBuffer(lyxfile);
2668 if (!closeBuffer()) {
2669 message(_("Canceled."));
2674 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2676 // if the file exists already, and we didn't do
2677 // -i lyx thefile.lyx, warn
2678 if (lyxfile.exists() && fullname != lyxfile) {
2680 docstring text = bformat(_("The document %1$s already exists.\n\n"
2681 "Do you want to overwrite that document?"), displaypath);
2682 int const ret = Alert::prompt(_("Overwrite document?"),
2683 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2686 message(_("Canceled."));
2691 message(bformat(_("Importing %1$s..."), displaypath));
2692 ErrorList errorList;
2693 if (import(this, fullname, format, errorList))
2694 message(_("imported."));
2696 message(_("file not imported!"));
2698 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2702 void GuiView::newDocument(string const & filename, string templatefile,
2705 FileName initpath(lyxrc.document_path);
2706 if (documentBufferView()) {
2707 FileName const trypath(documentBufferView()->buffer().filePath());
2708 // If directory is writeable, use this as default.
2709 if (trypath.isDirWritable())
2713 if (from_template) {
2714 if (templatefile.empty())
2715 templatefile = selectTemplateFile().absFileName();
2716 if (templatefile.empty())
2721 if (filename.empty())
2722 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2724 b = newFile(filename, templatefile, true);
2729 // If no new document could be created, it is unsure
2730 // whether there is a valid BufferView.
2731 if (currentBufferView())
2732 // Ensure the cursor is correctly positioned on screen.
2733 currentBufferView()->showCursor();
2737 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2739 BufferView * bv = documentBufferView();
2744 FileName filename(to_utf8(fname));
2745 if (filename.empty()) {
2746 // Launch a file browser
2748 string initpath = lyxrc.document_path;
2749 string const trypath = bv->buffer().filePath();
2750 // If directory is writeable, use this as default.
2751 if (FileName(trypath).isDirWritable())
2755 FileDialog dlg(qt_("Select LyX document to insert"));
2756 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2757 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2759 FileDialog::Result result = dlg.open(toqstr(initpath),
2760 QStringList(qt_("LyX Documents (*.lyx)")));
2762 if (result.first == FileDialog::Later)
2766 filename.set(fromqstr(result.second));
2768 // check selected filename
2769 if (filename.empty()) {
2770 // emit message signal.
2771 message(_("Canceled."));
2776 bv->insertLyXFile(filename, ignorelang);
2777 bv->buffer().errors("Parse");
2782 string const GuiView::getTemplatesPath(Buffer & b)
2784 // We start off with the user's templates path
2785 string result = addPath(package().user_support().absFileName(), "templates");
2786 // Check for the document language
2787 string const langcode = b.params().language->code();
2788 string const shortcode = langcode.substr(0, 2);
2789 if (!langcode.empty() && shortcode != "en") {
2790 string subpath = addPath(result, shortcode);
2791 string subpath_long = addPath(result, langcode);
2792 // If we have a subdirectory for the language already,
2794 FileName sp = FileName(subpath);
2795 if (sp.isDirectory())
2797 else if (FileName(subpath_long).isDirectory())
2798 result = subpath_long;
2800 // Ask whether we should create such a subdirectory
2801 docstring const text =
2802 bformat(_("It is suggested to save the template in a subdirectory\n"
2803 "appropriate to the document language (%1$s).\n"
2804 "This subdirectory does not exists yet.\n"
2805 "Do you want to create it?"),
2806 _(b.params().language->display()));
2807 if (Alert::prompt(_("Create Language Directory?"),
2808 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2809 // If the user agreed, we try to create it and report if this failed.
2810 if (!sp.createDirectory(0777))
2811 Alert::error(_("Subdirectory creation failed!"),
2812 _("Could not create subdirectory.\n"
2813 "The template will be saved in the parent directory."));
2819 // Do we have a layout category?
2820 string const cat = b.params().baseClass() ?
2821 b.params().baseClass()->category()
2824 string subpath = addPath(result, cat);
2825 // If we have a subdirectory for the category already,
2827 FileName sp = FileName(subpath);
2828 if (sp.isDirectory())
2831 // Ask whether we should create such a subdirectory
2832 docstring const text =
2833 bformat(_("It is suggested to save the template in a subdirectory\n"
2834 "appropriate to the layout category (%1$s).\n"
2835 "This subdirectory does not exists yet.\n"
2836 "Do you want to create it?"),
2838 if (Alert::prompt(_("Create Category Directory?"),
2839 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2840 // If the user agreed, we try to create it and report if this failed.
2841 if (!sp.createDirectory(0777))
2842 Alert::error(_("Subdirectory creation failed!"),
2843 _("Could not create subdirectory.\n"
2844 "The template will be saved in the parent directory."));
2854 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2856 FileName fname = b.fileName();
2857 FileName const oldname = fname;
2858 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2860 if (!newname.empty()) {
2863 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2865 fname = support::makeAbsPath(to_utf8(newname),
2866 oldname.onlyPath().absFileName());
2868 // Switch to this Buffer.
2871 // No argument? Ask user through dialog.
2873 QString const title = as_template ? qt_("Choose a filename to save template as")
2874 : qt_("Choose a filename to save document as");
2875 FileDialog dlg(title);
2876 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2877 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2879 if (!isLyXFileName(fname.absFileName()))
2880 fname.changeExtension(".lyx");
2882 string const path = as_template ?
2884 : fname.onlyPath().absFileName();
2885 FileDialog::Result result =
2886 dlg.save(toqstr(path),
2887 QStringList(qt_("LyX Documents (*.lyx)")),
2888 toqstr(fname.onlyFileName()));
2890 if (result.first == FileDialog::Later)
2893 fname.set(fromqstr(result.second));
2898 if (!isLyXFileName(fname.absFileName()))
2899 fname.changeExtension(".lyx");
2902 // fname is now the new Buffer location.
2904 // if there is already a Buffer open with this name, we do not want
2905 // to have another one. (the second test makes sure we're not just
2906 // trying to overwrite ourselves, which is fine.)
2907 if (theBufferList().exists(fname) && fname != oldname
2908 && theBufferList().getBuffer(fname) != &b) {
2909 docstring const text =
2910 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2911 "Please close it before attempting to overwrite it.\n"
2912 "Do you want to choose a new filename?"),
2913 from_utf8(fname.absFileName()));
2914 int const ret = Alert::prompt(_("Chosen File Already Open"),
2915 text, 0, 1, _("&Rename"), _("&Cancel"));
2917 case 0: return renameBuffer(b, docstring(), kind);
2918 case 1: return false;
2923 bool const existsLocal = fname.exists();
2924 bool const existsInVC = LyXVC::fileInVC(fname);
2925 if (existsLocal || existsInVC) {
2926 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2927 if (kind != LV_WRITE_AS && existsInVC) {
2928 // renaming to a name that is already in VC
2930 docstring text = bformat(_("The document %1$s "
2931 "is already registered.\n\n"
2932 "Do you want to choose a new name?"),
2934 docstring const title = (kind == LV_VC_RENAME) ?
2935 _("Rename document?") : _("Copy document?");
2936 docstring const button = (kind == LV_VC_RENAME) ?
2937 _("&Rename") : _("&Copy");
2938 int const ret = Alert::prompt(title, text, 0, 1,
2939 button, _("&Cancel"));
2941 case 0: return renameBuffer(b, docstring(), kind);
2942 case 1: return false;
2947 docstring text = bformat(_("The document %1$s "
2948 "already exists.\n\n"
2949 "Do you want to overwrite that document?"),
2951 int const ret = Alert::prompt(_("Overwrite document?"),
2952 text, 0, 2, _("&Overwrite"),
2953 _("&Rename"), _("&Cancel"));
2956 case 1: return renameBuffer(b, docstring(), kind);
2957 case 2: return false;
2963 case LV_VC_RENAME: {
2964 string msg = b.lyxvc().rename(fname);
2967 message(from_utf8(msg));
2971 string msg = b.lyxvc().copy(fname);
2974 message(from_utf8(msg));
2978 case LV_WRITE_AS_TEMPLATE:
2981 // LyXVC created the file already in case of LV_VC_RENAME or
2982 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2983 // relative paths of included stuff right if we moved e.g. from
2984 // /a/b.lyx to /a/c/b.lyx.
2986 bool const saved = saveBuffer(b, fname);
2993 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2995 FileName fname = b.fileName();
2997 FileDialog dlg(qt_("Choose a filename to export the document as"));
2998 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3001 QString const anyformat = qt_("Guess from extension (*.*)");
3004 vector<Format const *> export_formats;
3005 for (Format const & f : theFormats())
3006 if (f.documentFormat())
3007 export_formats.push_back(&f);
3008 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3009 map<QString, string> fmap;
3012 for (Format const * f : export_formats) {
3013 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3014 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3016 from_ascii(f->extension())));
3017 types << loc_filter;
3018 fmap[loc_filter] = f->name();
3019 if (from_ascii(f->name()) == iformat) {
3020 filter = loc_filter;
3021 ext = f->extension();
3024 string ofname = fname.onlyFileName();
3026 ofname = support::changeExtension(ofname, ext);
3027 FileDialog::Result result =
3028 dlg.save(toqstr(fname.onlyPath().absFileName()),
3032 if (result.first != FileDialog::Chosen)
3036 fname.set(fromqstr(result.second));
3037 if (filter == anyformat)
3038 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3040 fmt_name = fmap[filter];
3041 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3042 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3044 if (fmt_name.empty() || fname.empty())
3047 // fname is now the new Buffer location.
3048 if (FileName(fname).exists()) {
3049 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3050 docstring text = bformat(_("The document %1$s already "
3051 "exists.\n\nDo you want to "
3052 "overwrite that document?"),
3054 int const ret = Alert::prompt(_("Overwrite document?"),
3055 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3058 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3059 case 2: return false;
3063 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3066 return dr.dispatched();
3070 bool GuiView::saveBuffer(Buffer & b)
3072 return saveBuffer(b, FileName());
3076 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3078 if (workArea(b) && workArea(b)->inDialogMode())
3081 if (fn.empty() && b.isUnnamed())
3082 return renameBuffer(b, docstring());
3084 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3086 theSession().lastFiles().add(b.fileName());
3087 theSession().writeFile();
3091 // Switch to this Buffer.
3094 // FIXME: we don't tell the user *WHY* the save failed !!
3095 docstring const file = makeDisplayPath(b.absFileName(), 30);
3096 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3097 "Do you want to rename the document and "
3098 "try again?"), file);
3099 int const ret = Alert::prompt(_("Rename and save?"),
3100 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3103 if (!renameBuffer(b, docstring()))
3112 return saveBuffer(b, fn);
3116 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3118 return closeWorkArea(wa, false);
3122 // We only want to close the buffer if it is not visible in other workareas
3123 // of the same view, nor in other views, and if this is not a child
3124 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3126 Buffer & buf = wa->bufferView().buffer();
3128 bool last_wa = d.countWorkAreasOf(buf) == 1
3129 && !inOtherView(buf) && !buf.parent();
3131 bool close_buffer = last_wa;
3134 if (lyxrc.close_buffer_with_last_view == "yes")
3136 else if (lyxrc.close_buffer_with_last_view == "no")
3137 close_buffer = false;
3140 if (buf.isUnnamed())
3141 file = from_utf8(buf.fileName().onlyFileName());
3143 file = buf.fileName().displayName(30);
3144 docstring const text = bformat(
3145 _("Last view on document %1$s is being closed.\n"
3146 "Would you like to close or hide the document?\n"
3148 "Hidden documents can be displayed back through\n"
3149 "the menu: View->Hidden->...\n"
3151 "To remove this question, set your preference in:\n"
3152 " Tools->Preferences->Look&Feel->UserInterface\n"
3154 int ret = Alert::prompt(_("Close or hide document?"),
3155 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3158 close_buffer = (ret == 0);
3162 return closeWorkArea(wa, close_buffer);
3166 bool GuiView::closeBuffer()
3168 GuiWorkArea * wa = currentMainWorkArea();
3169 // coverity complained about this
3170 // it seems unnecessary, but perhaps is worth the check
3171 LASSERT(wa, return false);
3173 setCurrentWorkArea(wa);
3174 Buffer & buf = wa->bufferView().buffer();
3175 return closeWorkArea(wa, !buf.parent());
3179 void GuiView::writeSession() const {
3180 GuiWorkArea const * active_wa = currentMainWorkArea();
3181 for (int i = 0; i < d.splitter_->count(); ++i) {
3182 TabWorkArea * twa = d.tabWorkArea(i);
3183 for (int j = 0; j < twa->count(); ++j) {
3184 GuiWorkArea * wa = twa->workArea(j);
3185 Buffer & buf = wa->bufferView().buffer();
3186 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3192 bool GuiView::closeBufferAll()
3195 for (auto & buf : theBufferList()) {
3196 if (!saveBufferIfNeeded(*buf, false)) {
3197 // Closing has been cancelled, so abort.
3202 // Close the workareas in all other views
3203 QList<int> const ids = guiApp->viewIds();
3204 for (int i = 0; i != ids.size(); ++i) {
3205 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3209 // Close our own workareas
3210 if (!closeWorkAreaAll())
3217 bool GuiView::closeWorkAreaAll()
3219 setCurrentWorkArea(currentMainWorkArea());
3221 // We might be in a situation that there is still a tabWorkArea, but
3222 // there are no tabs anymore. This can happen when we get here after a
3223 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3224 // many TabWorkArea's have no documents anymore.
3227 // We have to call count() each time, because it can happen that
3228 // more than one splitter will disappear in one iteration (bug 5998).
3229 while (d.splitter_->count() > empty_twa) {
3230 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3232 if (twa->count() == 0)
3235 setCurrentWorkArea(twa->currentWorkArea());
3236 if (!closeTabWorkArea(twa))
3244 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3249 Buffer & buf = wa->bufferView().buffer();
3251 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3252 Alert::warning(_("Close document"),
3253 _("Document could not be closed because it is being processed by LyX."));
3258 return closeBuffer(buf);
3260 if (!inMultiTabs(wa))
3261 if (!saveBufferIfNeeded(buf, true))
3269 bool GuiView::closeBuffer(Buffer & buf)
3271 bool success = true;
3272 for (Buffer * child_buf : buf.getChildren()) {
3273 if (theBufferList().isOthersChild(&buf, child_buf)) {
3274 child_buf->setParent(nullptr);
3278 // FIXME: should we look in other tabworkareas?
3279 // ANSWER: I don't think so. I've tested, and if the child is
3280 // open in some other window, it closes without a problem.
3281 GuiWorkArea * child_wa = workArea(*child_buf);
3284 // If we are in a close_event all children will be closed in some time,
3285 // so no need to do it here. This will ensure that the children end up
3286 // in the session file in the correct order. If we close the master
3287 // buffer, we can close or release the child buffers here too.
3289 success = closeWorkArea(child_wa, true);
3293 // In this case the child buffer is open but hidden.
3294 // Even in this case, children can be dirty (e.g.,
3295 // after a label change in the master, see #11405).
3296 // Therefore, check this
3297 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3298 // If we are in a close_event all children will be closed in some time,
3299 // so no need to do it here. This will ensure that the children end up
3300 // in the session file in the correct order. If we close the master
3301 // buffer, we can close or release the child buffers here too.
3304 // Save dirty buffers also if closing_!
3305 if (saveBufferIfNeeded(*child_buf, false)) {
3306 child_buf->removeAutosaveFile();
3307 theBufferList().release(child_buf);
3309 // Saving of dirty children has been cancelled.
3310 // Cancel the whole process.
3317 // goto bookmark to update bookmark pit.
3318 // FIXME: we should update only the bookmarks related to this buffer!
3319 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3320 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3321 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3322 guiApp->gotoBookmark(i, false, false);
3324 if (saveBufferIfNeeded(buf, false)) {
3325 buf.removeAutosaveFile();
3326 theBufferList().release(&buf);
3330 // open all children again to avoid a crash because of dangling
3331 // pointers (bug 6603)
3337 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3339 while (twa == d.currentTabWorkArea()) {
3340 twa->setCurrentIndex(twa->count() - 1);
3342 GuiWorkArea * wa = twa->currentWorkArea();
3343 Buffer & b = wa->bufferView().buffer();
3345 // We only want to close the buffer if the same buffer is not visible
3346 // in another view, and if this is not a child and if we are closing
3347 // a view (not a tabgroup).
3348 bool const close_buffer =
3349 !inOtherView(b) && !b.parent() && closing_;
3351 if (!closeWorkArea(wa, close_buffer))
3358 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3360 if (buf.isClean() || buf.paragraphs().empty())
3363 // Switch to this Buffer.
3369 if (buf.isUnnamed()) {
3370 file = from_utf8(buf.fileName().onlyFileName());
3373 FileName filename = buf.fileName();
3375 file = filename.displayName(30);
3376 exists = filename.exists();
3379 // Bring this window to top before asking questions.
3384 if (hiding && buf.isUnnamed()) {
3385 docstring const text = bformat(_("The document %1$s has not been "
3386 "saved yet.\n\nDo you want to save "
3387 "the document?"), file);
3388 ret = Alert::prompt(_("Save new document?"),
3389 text, 0, 1, _("&Save"), _("&Cancel"));
3393 docstring const text = exists ?
3394 bformat(_("The document %1$s has unsaved changes."
3395 "\n\nDo you want to save the document or "
3396 "discard the changes?"), file) :
3397 bformat(_("The document %1$s has not been saved yet."
3398 "\n\nDo you want to save the document or "
3399 "discard it entirely?"), file);
3400 docstring const title = exists ?
3401 _("Save changed document?") : _("Save document?");
3402 ret = Alert::prompt(title, text, 0, 2,
3403 _("&Save"), _("&Discard"), _("&Cancel"));
3408 if (!saveBuffer(buf))
3412 // If we crash after this we could have no autosave file
3413 // but I guess this is really improbable (Jug).
3414 // Sometimes improbable things happen:
3415 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3416 // buf.removeAutosaveFile();
3418 // revert all changes
3429 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3431 Buffer & buf = wa->bufferView().buffer();
3433 for (int i = 0; i != d.splitter_->count(); ++i) {
3434 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3435 if (wa_ && wa_ != wa)
3438 return inOtherView(buf);
3442 bool GuiView::inOtherView(Buffer & buf)
3444 QList<int> const ids = guiApp->viewIds();
3446 for (int i = 0; i != ids.size(); ++i) {
3450 if (guiApp->view(ids[i]).workArea(buf))
3457 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3459 if (!documentBufferView())
3462 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3463 Buffer * const curbuf = &documentBufferView()->buffer();
3464 int nwa = twa->count();
3465 for (int i = 0; i < nwa; ++i) {
3466 if (&workArea(i)->bufferView().buffer() == curbuf) {
3468 if (np == NEXTBUFFER)
3469 next_index = (i == nwa - 1 ? 0 : i + 1);
3471 next_index = (i == 0 ? nwa - 1 : i - 1);
3473 twa->moveTab(i, next_index);
3475 setBuffer(&workArea(next_index)->bufferView().buffer());
3483 /// make sure the document is saved
3484 static bool ensureBufferClean(Buffer * buffer)
3486 LASSERT(buffer, return false);
3487 if (buffer->isClean() && !buffer->isUnnamed())
3490 docstring const file = buffer->fileName().displayName(30);
3493 if (!buffer->isUnnamed()) {
3494 text = bformat(_("The document %1$s has unsaved "
3495 "changes.\n\nDo you want to save "
3496 "the document?"), file);
3497 title = _("Save changed document?");
3500 text = bformat(_("The document %1$s has not been "
3501 "saved yet.\n\nDo you want to save "
3502 "the document?"), file);
3503 title = _("Save new document?");
3505 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3508 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3510 return buffer->isClean() && !buffer->isUnnamed();
3514 bool GuiView::reloadBuffer(Buffer & buf)
3516 currentBufferView()->cursor().reset();
3517 Buffer::ReadStatus status = buf.reload();
3518 return status == Buffer::ReadSuccess;
3522 void GuiView::checkExternallyModifiedBuffers()
3524 for (Buffer * buf : theBufferList()) {
3525 if (buf->fileName().exists() && buf->isChecksumModified()) {
3526 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3527 " Reload now? Any local changes will be lost."),
3528 from_utf8(buf->absFileName()));
3529 int const ret = Alert::prompt(_("Reload externally changed document?"),
3530 text, 0, 1, _("&Reload"), _("&Cancel"));
3538 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3540 Buffer * buffer = documentBufferView()
3541 ? &(documentBufferView()->buffer()) : nullptr;
3543 switch (cmd.action()) {
3544 case LFUN_VC_REGISTER:
3545 if (!buffer || !ensureBufferClean(buffer))
3547 if (!buffer->lyxvc().inUse()) {
3548 if (buffer->lyxvc().registrer()) {
3549 reloadBuffer(*buffer);
3550 dr.clearMessageUpdate();
3555 case LFUN_VC_RENAME:
3556 case LFUN_VC_COPY: {
3557 if (!buffer || !ensureBufferClean(buffer))
3559 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3560 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3561 // Some changes are not yet committed.
3562 // We test here and not in getStatus(), since
3563 // this test is expensive.
3565 LyXVC::CommandResult ret =
3566 buffer->lyxvc().checkIn(log);
3568 if (ret == LyXVC::ErrorCommand ||
3569 ret == LyXVC::VCSuccess)
3570 reloadBuffer(*buffer);
3571 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3572 frontend::Alert::error(
3573 _("Revision control error."),
3574 _("Document could not be checked in."));
3578 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3579 LV_VC_RENAME : LV_VC_COPY;
3580 renameBuffer(*buffer, cmd.argument(), kind);
3585 case LFUN_VC_CHECK_IN:
3586 if (!buffer || !ensureBufferClean(buffer))
3588 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3590 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3592 // Only skip reloading if the checkin was cancelled or
3593 // an error occurred before the real checkin VCS command
3594 // was executed, since the VCS might have changed the
3595 // file even if it could not checkin successfully.
3596 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3597 reloadBuffer(*buffer);
3601 case LFUN_VC_CHECK_OUT:
3602 if (!buffer || !ensureBufferClean(buffer))
3604 if (buffer->lyxvc().inUse()) {
3605 dr.setMessage(buffer->lyxvc().checkOut());
3606 reloadBuffer(*buffer);
3610 case LFUN_VC_LOCKING_TOGGLE:
3611 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3613 if (buffer->lyxvc().inUse()) {
3614 string res = buffer->lyxvc().lockingToggle();
3616 frontend::Alert::error(_("Revision control error."),
3617 _("Error when setting the locking property."));
3620 reloadBuffer(*buffer);
3625 case LFUN_VC_REVERT:
3628 if (buffer->lyxvc().revert()) {
3629 reloadBuffer(*buffer);
3630 dr.clearMessageUpdate();
3634 case LFUN_VC_UNDO_LAST:
3637 buffer->lyxvc().undoLast();
3638 reloadBuffer(*buffer);
3639 dr.clearMessageUpdate();
3642 case LFUN_VC_REPO_UPDATE:
3645 if (ensureBufferClean(buffer)) {
3646 dr.setMessage(buffer->lyxvc().repoUpdate());
3647 checkExternallyModifiedBuffers();
3651 case LFUN_VC_COMMAND: {
3652 string flag = cmd.getArg(0);
3653 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3656 if (contains(flag, 'M')) {
3657 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3660 string path = cmd.getArg(1);
3661 if (contains(path, "$$p") && buffer)
3662 path = subst(path, "$$p", buffer->filePath());
3663 LYXERR(Debug::LYXVC, "Directory: " << path);
3665 if (!pp.isReadableDirectory()) {
3666 lyxerr << _("Directory is not accessible.") << endl;
3669 support::PathChanger p(pp);
3671 string command = cmd.getArg(2);
3672 if (command.empty())
3675 command = subst(command, "$$i", buffer->absFileName());
3676 command = subst(command, "$$p", buffer->filePath());
3678 command = subst(command, "$$m", to_utf8(message));
3679 LYXERR(Debug::LYXVC, "Command: " << command);
3681 one.startscript(Systemcall::Wait, command);
3685 if (contains(flag, 'I'))
3686 buffer->markDirty();
3687 if (contains(flag, 'R'))
3688 reloadBuffer(*buffer);
3693 case LFUN_VC_COMPARE: {
3694 if (cmd.argument().empty()) {
3695 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3701 string rev1 = cmd.getArg(0);
3705 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3708 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3709 f2 = buffer->absFileName();
3711 string rev2 = cmd.getArg(1);
3715 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3719 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3720 f1 << "\n" << f2 << "\n" );
3721 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3722 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3732 void GuiView::openChildDocument(string const & fname)
3734 LASSERT(documentBufferView(), return);
3735 Buffer & buffer = documentBufferView()->buffer();
3736 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3737 documentBufferView()->saveBookmark(false);
3738 Buffer * child = nullptr;
3739 if (theBufferList().exists(filename)) {
3740 child = theBufferList().getBuffer(filename);
3743 message(bformat(_("Opening child document %1$s..."),
3744 makeDisplayPath(filename.absFileName())));
3745 child = loadDocument(filename, false);
3747 // Set the parent name of the child document.
3748 // This makes insertion of citations and references in the child work,
3749 // when the target is in the parent or another child document.
3751 child->setParent(&buffer);
3755 bool GuiView::goToFileRow(string const & argument)
3759 size_t i = argument.find_last_of(' ');
3760 if (i != string::npos) {
3761 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3762 istringstream is(argument.substr(i + 1));
3767 if (i == string::npos) {
3768 LYXERR0("Wrong argument: " << argument);
3771 Buffer * buf = nullptr;
3772 string const realtmp = package().temp_dir().realPath();
3773 // We have to use os::path_prefix_is() here, instead of
3774 // simply prefixIs(), because the file name comes from
3775 // an external application and may need case adjustment.
3776 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3777 buf = theBufferList().getBufferFromTmp(file_name, true);
3778 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3779 << (buf ? " success" : " failed"));
3781 // Must replace extension of the file to be .lyx
3782 // and get full path
3783 FileName const s = fileSearch(string(),
3784 support::changeExtension(file_name, ".lyx"), "lyx");
3785 // Either change buffer or load the file
3786 if (theBufferList().exists(s))
3787 buf = theBufferList().getBuffer(s);
3788 else if (s.exists()) {
3789 buf = loadDocument(s);
3794 _("File does not exist: %1$s"),
3795 makeDisplayPath(file_name)));
3801 _("No buffer for file: %1$s."),
3802 makeDisplayPath(file_name))
3807 bool success = documentBufferView()->setCursorFromRow(row);
3809 LYXERR(Debug::LATEX,
3810 "setCursorFromRow: invalid position for row " << row);
3811 frontend::Alert::error(_("Inverse Search Failed"),
3812 _("Invalid position requested by inverse search.\n"
3813 "You may need to update the viewed document."));
3819 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3821 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3822 menu->exec(QCursor::pos());
3827 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3828 Buffer const * orig, Buffer * clone, string const & format)
3830 Buffer::ExportStatus const status = func(format);
3832 // the cloning operation will have produced a clone of the entire set of
3833 // documents, starting from the master. so we must delete those.
3834 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3836 busyBuffers.remove(orig);
3841 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3842 Buffer const * orig, Buffer * clone, string const & format)
3844 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3846 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3850 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3851 Buffer const * orig, Buffer * clone, string const & format)
3853 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3855 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3859 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3860 Buffer const * orig, Buffer * clone, string const & format)
3862 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3864 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3868 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3869 Buffer const * used_buffer,
3870 docstring const & msg,
3871 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3872 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3873 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3874 bool allow_async, bool use_tmpdir)
3879 string format = argument;
3881 format = used_buffer->params().getDefaultOutputFormat();
3882 processing_format = format;
3884 progress_->clearMessages();
3887 #if EXPORT_in_THREAD
3889 GuiViewPrivate::busyBuffers.insert(used_buffer);
3890 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3891 if (!cloned_buffer) {
3892 Alert::error(_("Export Error"),
3893 _("Error cloning the Buffer."));
3896 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3901 setPreviewFuture(f);
3902 last_export_format = used_buffer->params().bufferFormat();
3905 // We are asynchronous, so we don't know here anything about the success
3908 Buffer::ExportStatus status;
3910 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3911 } else if (previewFunc) {
3912 status = (used_buffer->*previewFunc)(format);
3915 handleExportStatus(gv_, status, format);
3917 return (status == Buffer::ExportSuccess
3918 || status == Buffer::PreviewSuccess);
3922 Buffer::ExportStatus status;
3924 status = (used_buffer->*syncFunc)(format, true);
3925 } else if (previewFunc) {
3926 status = (used_buffer->*previewFunc)(format);
3929 handleExportStatus(gv_, status, format);
3931 return (status == Buffer::ExportSuccess
3932 || status == Buffer::PreviewSuccess);
3936 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3938 BufferView * bv = currentBufferView();
3939 LASSERT(bv, return);
3941 // Let the current BufferView dispatch its own actions.
3942 bv->dispatch(cmd, dr);
3943 if (dr.dispatched()) {
3944 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3945 updateDialog("document", "");
3949 // Try with the document BufferView dispatch if any.
3950 BufferView * doc_bv = documentBufferView();
3951 if (doc_bv && doc_bv != bv) {
3952 doc_bv->dispatch(cmd, dr);
3953 if (dr.dispatched()) {
3954 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3955 updateDialog("document", "");
3960 // Then let the current Cursor dispatch its own actions.
3961 bv->cursor().dispatch(cmd);
3963 // update completion. We do it here and not in
3964 // processKeySym to avoid another redraw just for a
3965 // changed inline completion
3966 if (cmd.origin() == FuncRequest::KEYBOARD) {
3967 if (cmd.action() == LFUN_SELF_INSERT
3968 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3969 updateCompletion(bv->cursor(), true, true);
3970 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3971 updateCompletion(bv->cursor(), false, true);
3973 updateCompletion(bv->cursor(), false, false);
3976 dr = bv->cursor().result();
3980 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3982 BufferView * bv = currentBufferView();
3983 // By default we won't need any update.
3984 dr.screenUpdate(Update::None);
3985 // assume cmd will be dispatched
3986 dr.dispatched(true);
3988 Buffer * doc_buffer = documentBufferView()
3989 ? &(documentBufferView()->buffer()) : nullptr;
3991 if (cmd.origin() == FuncRequest::TOC) {
3992 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3993 toc->doDispatch(bv->cursor(), cmd, dr);
3997 string const argument = to_utf8(cmd.argument());
3999 switch(cmd.action()) {
4000 case LFUN_BUFFER_CHILD_OPEN:
4001 openChildDocument(to_utf8(cmd.argument()));
4004 case LFUN_BUFFER_IMPORT:
4005 importDocument(to_utf8(cmd.argument()));
4008 case LFUN_MASTER_BUFFER_EXPORT:
4010 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4012 case LFUN_BUFFER_EXPORT: {
4015 // GCC only sees strfwd.h when building merged
4016 if (::lyx::operator==(cmd.argument(), "custom")) {
4017 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4018 // so the following test should not be needed.
4019 // In principle, we could try to switch to such a view...
4020 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4021 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4025 string const dest = cmd.getArg(1);
4026 FileName target_dir;
4027 if (!dest.empty() && FileName::isAbsolute(dest))
4028 target_dir = FileName(support::onlyPath(dest));
4030 target_dir = doc_buffer->fileName().onlyPath();
4032 string const format = (argument.empty() || argument == "default") ?
4033 doc_buffer->params().getDefaultOutputFormat() : argument;
4035 if ((dest.empty() && doc_buffer->isUnnamed())
4036 || !target_dir.isDirWritable()) {
4037 exportBufferAs(*doc_buffer, from_utf8(format));
4040 /* TODO/Review: Is it a problem to also export the children?
4041 See the update_unincluded flag */
4042 d.asyncBufferProcessing(format,
4045 &GuiViewPrivate::exportAndDestroy,
4047 nullptr, cmd.allowAsync());
4048 // TODO Inform user about success
4052 case LFUN_BUFFER_EXPORT_AS: {
4053 LASSERT(doc_buffer, break);
4054 docstring f = cmd.argument();
4056 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4057 exportBufferAs(*doc_buffer, f);
4061 case LFUN_BUFFER_UPDATE: {
4062 d.asyncBufferProcessing(argument,
4065 &GuiViewPrivate::compileAndDestroy,
4067 nullptr, cmd.allowAsync(), true);
4070 case LFUN_BUFFER_VIEW: {
4071 d.asyncBufferProcessing(argument,
4073 _("Previewing ..."),
4074 &GuiViewPrivate::previewAndDestroy,
4076 &Buffer::preview, cmd.allowAsync());
4079 case LFUN_MASTER_BUFFER_UPDATE: {
4080 d.asyncBufferProcessing(argument,
4081 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4083 &GuiViewPrivate::compileAndDestroy,
4085 nullptr, cmd.allowAsync(), true);
4088 case LFUN_MASTER_BUFFER_VIEW: {
4089 d.asyncBufferProcessing(argument,
4090 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4092 &GuiViewPrivate::previewAndDestroy,
4093 nullptr, &Buffer::preview, cmd.allowAsync());
4096 case LFUN_EXPORT_CANCEL: {
4097 Systemcall::killscript();
4100 case LFUN_BUFFER_SWITCH: {
4101 string const file_name = to_utf8(cmd.argument());
4102 if (!FileName::isAbsolute(file_name)) {
4104 dr.setMessage(_("Absolute filename expected."));
4108 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4111 dr.setMessage(_("Document not loaded"));
4115 // Do we open or switch to the buffer in this view ?
4116 if (workArea(*buffer)
4117 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4122 // Look for the buffer in other views
4123 QList<int> const ids = guiApp->viewIds();
4125 for (; i != ids.size(); ++i) {
4126 GuiView & gv = guiApp->view(ids[i]);
4127 if (gv.workArea(*buffer)) {
4129 gv.activateWindow();
4131 gv.setBuffer(buffer);
4136 // If necessary, open a new window as a last resort
4137 if (i == ids.size()) {
4138 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4144 case LFUN_BUFFER_NEXT:
4145 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4148 case LFUN_BUFFER_MOVE_NEXT:
4149 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4152 case LFUN_BUFFER_PREVIOUS:
4153 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4156 case LFUN_BUFFER_MOVE_PREVIOUS:
4157 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4160 case LFUN_BUFFER_CHKTEX:
4161 LASSERT(doc_buffer, break);
4162 doc_buffer->runChktex();
4165 case LFUN_COMMAND_EXECUTE: {
4166 command_execute_ = true;
4167 minibuffer_focus_ = true;
4170 case LFUN_DROP_LAYOUTS_CHOICE:
4171 d.layout_->showPopup();
4174 case LFUN_MENU_OPEN:
4175 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4176 menu->exec(QCursor::pos());
4179 case LFUN_FILE_INSERT: {
4180 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4181 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4182 dr.forceBufferUpdate();
4183 dr.screenUpdate(Update::Force);
4188 case LFUN_FILE_INSERT_PLAINTEXT:
4189 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4190 string const fname = to_utf8(cmd.argument());
4191 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4192 dr.setMessage(_("Absolute filename expected."));
4196 FileName filename(fname);
4197 if (fname.empty()) {
4198 FileDialog dlg(qt_("Select file to insert"));
4200 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4201 QStringList(qt_("All Files (*)")));
4203 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4204 dr.setMessage(_("Canceled."));
4208 filename.set(fromqstr(result.second));
4212 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4213 bv->dispatch(new_cmd, dr);
4218 case LFUN_BUFFER_RELOAD: {
4219 LASSERT(doc_buffer, break);
4222 bool drop = (cmd.argument() == "dump");
4225 if (!drop && !doc_buffer->isClean()) {
4226 docstring const file =
4227 makeDisplayPath(doc_buffer->absFileName(), 20);
4228 if (doc_buffer->notifiesExternalModification()) {
4229 docstring text = _("The current version will be lost. "
4230 "Are you sure you want to load the version on disk "
4231 "of the document %1$s?");
4232 ret = Alert::prompt(_("Reload saved document?"),
4233 bformat(text, file), 1, 1,
4234 _("&Reload"), _("&Cancel"));
4236 docstring text = _("Any changes will be lost. "
4237 "Are you sure you want to revert to the saved version "
4238 "of the document %1$s?");
4239 ret = Alert::prompt(_("Revert to saved document?"),
4240 bformat(text, file), 1, 1,
4241 _("&Revert"), _("&Cancel"));
4246 doc_buffer->markClean();
4247 reloadBuffer(*doc_buffer);
4248 dr.forceBufferUpdate();
4253 case LFUN_BUFFER_RESET_EXPORT:
4254 LASSERT(doc_buffer, break);
4255 doc_buffer->requireFreshStart(true);
4256 dr.setMessage(_("Buffer export reset."));
4259 case LFUN_BUFFER_WRITE:
4260 LASSERT(doc_buffer, break);
4261 saveBuffer(*doc_buffer);
4264 case LFUN_BUFFER_WRITE_AS:
4265 LASSERT(doc_buffer, break);
4266 renameBuffer(*doc_buffer, cmd.argument());
4269 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4270 LASSERT(doc_buffer, break);
4271 renameBuffer(*doc_buffer, cmd.argument(),
4272 LV_WRITE_AS_TEMPLATE);
4275 case LFUN_BUFFER_WRITE_ALL: {
4276 Buffer * first = theBufferList().first();
4279 message(_("Saving all documents..."));
4280 // We cannot use a for loop as the buffer list cycles.
4283 if (!b->isClean()) {
4285 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4287 b = theBufferList().next(b);
4288 } while (b != first);
4289 dr.setMessage(_("All documents saved."));
4293 case LFUN_MASTER_BUFFER_FORALL: {
4297 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4298 funcToRun.allowAsync(false);
4300 for (Buffer const * buf : doc_buffer->allRelatives()) {
4301 // Switch to other buffer view and resend cmd
4302 lyx::dispatch(FuncRequest(
4303 LFUN_BUFFER_SWITCH, buf->absFileName()));
4304 lyx::dispatch(funcToRun);
4307 lyx::dispatch(FuncRequest(
4308 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4312 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4313 LASSERT(doc_buffer, break);
4314 doc_buffer->clearExternalModification();
4317 case LFUN_BUFFER_CLOSE:
4321 case LFUN_BUFFER_CLOSE_ALL:
4325 case LFUN_DEVEL_MODE_TOGGLE:
4326 devel_mode_ = !devel_mode_;
4328 dr.setMessage(_("Developer mode is now enabled."));
4330 dr.setMessage(_("Developer mode is now disabled."));
4333 case LFUN_TOOLBAR_SET: {
4334 string const name = cmd.getArg(0);
4335 string const state = cmd.getArg(1);
4336 if (GuiToolbar * t = toolbar(name))
4341 case LFUN_TOOLBAR_TOGGLE: {
4342 string const name = cmd.getArg(0);
4343 if (GuiToolbar * t = toolbar(name))
4348 case LFUN_TOOLBAR_MOVABLE: {
4349 string const name = cmd.getArg(0);
4351 // toggle (all) toolbars movablility
4352 toolbarsMovable_ = !toolbarsMovable_;
4353 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4354 GuiToolbar * tb = toolbar(ti.name);
4355 if (tb && tb->isMovable() != toolbarsMovable_)
4356 // toggle toolbar movablity if it does not fit lock
4357 // (all) toolbars positions state silent = true, since
4358 // status bar notifications are slow
4361 if (toolbarsMovable_)
4362 dr.setMessage(_("Toolbars unlocked."));
4364 dr.setMessage(_("Toolbars locked."));
4365 } else if (GuiToolbar * t = toolbar(name)) {
4366 // toggle current toolbar movablity
4368 // update lock (all) toolbars positions
4369 updateLockToolbars();
4374 case LFUN_ICON_SIZE: {
4375 QSize size = d.iconSize(cmd.argument());
4377 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4378 size.width(), size.height()));
4382 case LFUN_DIALOG_UPDATE: {
4383 string const name = to_utf8(cmd.argument());
4384 if (name == "prefs" || name == "document")
4385 updateDialog(name, string());
4386 else if (name == "paragraph")
4387 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4388 else if (currentBufferView()) {
4389 Inset * inset = currentBufferView()->editedInset(name);
4390 // Can only update a dialog connected to an existing inset
4392 // FIXME: get rid of this indirection; GuiView ask the inset
4393 // if he is kind enough to update itself...
4394 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4395 //FIXME: pass DispatchResult here?
4396 inset->dispatch(currentBufferView()->cursor(), fr);
4402 case LFUN_DIALOG_TOGGLE: {
4403 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4404 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4405 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4409 case LFUN_DIALOG_DISCONNECT_INSET:
4410 disconnectDialog(to_utf8(cmd.argument()));
4413 case LFUN_DIALOG_HIDE: {
4414 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4418 case LFUN_DIALOG_SHOW: {
4419 string const name = cmd.getArg(0);
4420 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4422 if (name == "latexlog") {
4423 // getStatus checks that
4424 LASSERT(doc_buffer, break);
4425 Buffer::LogType type;
4426 string const logfile = doc_buffer->logName(&type);
4428 case Buffer::latexlog:
4431 case Buffer::buildlog:
4432 sdata = "literate ";
4435 sdata += Lexer::quoteString(logfile);
4436 showDialog("log", sdata);
4437 } else if (name == "vclog") {
4438 // getStatus checks that
4439 LASSERT(doc_buffer, break);
4440 string const sdata2 = "vc " +
4441 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4442 showDialog("log", sdata2);
4443 } else if (name == "symbols") {
4444 sdata = bv->cursor().getEncoding()->name();
4446 showDialog("symbols", sdata);
4447 } else if (name == "findreplace") {
4448 sdata = to_utf8(bv->cursor().selectionAsString(false));
4449 showDialog(name, sdata);
4451 } else if (name == "prefs" && isFullScreen()) {
4452 lfunUiToggle("fullscreen");
4453 showDialog("prefs", sdata);
4455 showDialog(name, sdata);
4460 dr.setMessage(cmd.argument());
4463 case LFUN_UI_TOGGLE: {
4464 string arg = cmd.getArg(0);
4465 if (!lfunUiToggle(arg)) {
4466 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4467 dr.setMessage(bformat(msg, from_utf8(arg)));
4469 // Make sure the keyboard focus stays in the work area.
4474 case LFUN_VIEW_SPLIT: {
4475 LASSERT(doc_buffer, break);
4476 string const orientation = cmd.getArg(0);
4477 d.splitter_->setOrientation(orientation == "vertical"
4478 ? Qt::Vertical : Qt::Horizontal);
4479 TabWorkArea * twa = addTabWorkArea();
4480 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4481 setCurrentWorkArea(wa);
4484 case LFUN_TAB_GROUP_CLOSE:
4485 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4486 closeTabWorkArea(twa);
4487 d.current_work_area_ = nullptr;
4488 twa = d.currentTabWorkArea();
4489 // Switch to the next GuiWorkArea in the found TabWorkArea.
4491 // Make sure the work area is up to date.
4492 setCurrentWorkArea(twa->currentWorkArea());
4494 setCurrentWorkArea(nullptr);
4499 case LFUN_VIEW_CLOSE:
4500 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4501 closeWorkArea(twa->currentWorkArea());
4502 d.current_work_area_ = nullptr;
4503 twa = d.currentTabWorkArea();
4504 // Switch to the next GuiWorkArea in the found TabWorkArea.
4506 // Make sure the work area is up to date.
4507 setCurrentWorkArea(twa->currentWorkArea());
4509 setCurrentWorkArea(nullptr);
4514 case LFUN_COMPLETION_INLINE:
4515 if (d.current_work_area_)
4516 d.current_work_area_->completer().showInline();
4519 case LFUN_COMPLETION_POPUP:
4520 if (d.current_work_area_)
4521 d.current_work_area_->completer().showPopup();
4526 if (d.current_work_area_)
4527 d.current_work_area_->completer().tab();
4530 case LFUN_COMPLETION_CANCEL:
4531 if (d.current_work_area_) {
4532 if (d.current_work_area_->completer().popupVisible())
4533 d.current_work_area_->completer().hidePopup();
4535 d.current_work_area_->completer().hideInline();
4539 case LFUN_COMPLETION_ACCEPT:
4540 if (d.current_work_area_)
4541 d.current_work_area_->completer().activate();
4544 case LFUN_BUFFER_ZOOM_IN:
4545 case LFUN_BUFFER_ZOOM_OUT:
4546 case LFUN_BUFFER_ZOOM: {
4547 if (cmd.argument().empty()) {
4548 if (cmd.action() == LFUN_BUFFER_ZOOM)
4550 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4555 if (cmd.action() == LFUN_BUFFER_ZOOM)
4556 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4557 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4558 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4560 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4563 // Actual zoom value: default zoom + fractional extra value
4564 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4565 if (zoom < static_cast<int>(zoom_min_))
4568 lyxrc.currentZoom = zoom;
4570 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4571 lyxrc.currentZoom, lyxrc.defaultZoom));
4573 guiApp->fontLoader().update();
4574 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4578 case LFUN_VC_REGISTER:
4579 case LFUN_VC_RENAME:
4581 case LFUN_VC_CHECK_IN:
4582 case LFUN_VC_CHECK_OUT:
4583 case LFUN_VC_REPO_UPDATE:
4584 case LFUN_VC_LOCKING_TOGGLE:
4585 case LFUN_VC_REVERT:
4586 case LFUN_VC_UNDO_LAST:
4587 case LFUN_VC_COMMAND:
4588 case LFUN_VC_COMPARE:
4589 dispatchVC(cmd, dr);
4592 case LFUN_SERVER_GOTO_FILE_ROW:
4593 if(goToFileRow(to_utf8(cmd.argument())))
4594 dr.screenUpdate(Update::Force | Update::FitCursor);
4597 case LFUN_LYX_ACTIVATE:
4601 case LFUN_WINDOW_RAISE:
4607 case LFUN_FORWARD_SEARCH: {
4608 // it seems safe to assume we have a document buffer, since
4609 // getStatus wants one.
4610 LASSERT(doc_buffer, break);
4611 Buffer const * doc_master = doc_buffer->masterBuffer();
4612 FileName const path(doc_master->temppath());
4613 string const texname = doc_master->isChild(doc_buffer)
4614 ? DocFileName(changeExtension(
4615 doc_buffer->absFileName(),
4616 "tex")).mangledFileName()
4617 : doc_buffer->latexName();
4618 string const fulltexname =
4619 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4620 string const mastername =
4621 removeExtension(doc_master->latexName());
4622 FileName const dviname(addName(path.absFileName(),
4623 addExtension(mastername, "dvi")));
4624 FileName const pdfname(addName(path.absFileName(),
4625 addExtension(mastername, "pdf")));
4626 bool const have_dvi = dviname.exists();
4627 bool const have_pdf = pdfname.exists();
4628 if (!have_dvi && !have_pdf) {
4629 dr.setMessage(_("Please, preview the document first."));
4632 string outname = dviname.onlyFileName();
4633 string command = lyxrc.forward_search_dvi;
4634 if (!have_dvi || (have_pdf &&
4635 pdfname.lastModified() > dviname.lastModified())) {
4636 outname = pdfname.onlyFileName();
4637 command = lyxrc.forward_search_pdf;
4640 DocIterator cur = bv->cursor();
4641 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4642 LYXERR(Debug::ACTION, "Forward search: row:" << row
4644 if (row == -1 || command.empty()) {
4645 dr.setMessage(_("Couldn't proceed."));
4648 string texrow = convert<string>(row);
4650 command = subst(command, "$$n", texrow);
4651 command = subst(command, "$$f", fulltexname);
4652 command = subst(command, "$$t", texname);
4653 command = subst(command, "$$o", outname);
4655 volatile PathChanger p(path);
4657 one.startscript(Systemcall::DontWait, command);
4661 case LFUN_SPELLING_CONTINUOUSLY:
4662 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4663 dr.screenUpdate(Update::Force);
4666 case LFUN_CITATION_OPEN: {
4668 if (theFormats().getFormat("pdf"))
4669 pdfv = theFormats().getFormat("pdf")->viewer();
4670 if (theFormats().getFormat("ps"))
4671 psv = theFormats().getFormat("ps")->viewer();
4672 frontend::showTarget(argument, pdfv, psv);
4677 // The LFUN must be for one of BufferView, Buffer or Cursor;
4679 dispatchToBufferView(cmd, dr);
4683 // Need to update bv because many LFUNs here might have destroyed it
4684 bv = currentBufferView();
4686 // Clear non-empty selections
4687 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4689 Cursor & cur = bv->cursor();
4690 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4691 cur.clearSelection();
4697 bool GuiView::lfunUiToggle(string const & ui_component)
4699 if (ui_component == "scrollbar") {
4700 // hide() is of no help
4701 if (d.current_work_area_->verticalScrollBarPolicy() ==
4702 Qt::ScrollBarAlwaysOff)
4704 d.current_work_area_->setVerticalScrollBarPolicy(
4705 Qt::ScrollBarAsNeeded);
4707 d.current_work_area_->setVerticalScrollBarPolicy(
4708 Qt::ScrollBarAlwaysOff);
4709 } else if (ui_component == "statusbar") {
4710 statusBar()->setVisible(!statusBar()->isVisible());
4711 } else if (ui_component == "menubar") {
4712 menuBar()->setVisible(!menuBar()->isVisible());
4714 if (ui_component == "frame") {
4715 int const l = contentsMargins().left();
4717 //are the frames in default state?
4718 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4720 #if QT_VERSION > 0x050903
4721 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4723 setContentsMargins(-2, -2, -2, -2);
4725 #if QT_VERSION > 0x050903
4726 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4728 setContentsMargins(0, 0, 0, 0);
4731 if (ui_component == "fullscreen") {
4739 void GuiView::toggleFullScreen()
4741 setWindowState(windowState() ^ Qt::WindowFullScreen);
4745 Buffer const * GuiView::updateInset(Inset const * inset)
4750 Buffer const * inset_buffer = &(inset->buffer());
4752 for (int i = 0; i != d.splitter_->count(); ++i) {
4753 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4756 Buffer const * buffer = &(wa->bufferView().buffer());
4757 if (inset_buffer == buffer)
4758 wa->scheduleRedraw(true);
4760 return inset_buffer;
4764 void GuiView::restartCaret()
4766 /* When we move around, or type, it's nice to be able to see
4767 * the caret immediately after the keypress.
4769 if (d.current_work_area_)
4770 d.current_work_area_->startBlinkingCaret();
4772 // Take this occasion to update the other GUI elements.
4778 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4780 if (d.current_work_area_)
4781 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4786 // This list should be kept in sync with the list of insets in
4787 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4788 // dialog should have the same name as the inset.
4789 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4790 // docs in LyXAction.cpp.
4792 char const * const dialognames[] = {
4794 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4795 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4796 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4797 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4798 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4799 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4800 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4801 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4803 char const * const * const end_dialognames =
4804 dialognames + (sizeof(dialognames) / sizeof(char *));
4808 cmpCStr(char const * name) : name_(name) {}
4809 bool operator()(char const * other) {
4810 return strcmp(other, name_) == 0;
4817 bool isValidName(string const & name)
4819 return find_if(dialognames, end_dialognames,
4820 cmpCStr(name.c_str())) != end_dialognames;
4826 void GuiView::resetDialogs()
4828 // Make sure that no LFUN uses any GuiView.
4829 guiApp->setCurrentView(nullptr);
4833 constructToolbars();
4834 guiApp->menus().fillMenuBar(menuBar(), this, false);
4835 d.layout_->updateContents(true);
4836 // Now update controls with current buffer.
4837 guiApp->setCurrentView(this);
4843 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4845 for (QObject * child: widget->children()) {
4846 if (child->inherits("QGroupBox")) {
4847 QGroupBox * box = (QGroupBox*) child;
4850 flatGroupBoxes(child, flag);
4856 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4858 if (!isValidName(name))
4861 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4863 if (it != d.dialogs_.end()) {
4865 it->second->hideView();
4866 return it->second.get();
4869 Dialog * dialog = build(name);
4870 d.dialogs_[name].reset(dialog);
4871 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4872 // Force a uniform style for group boxes
4873 // On Mac non-flat works better, on Linux flat is standard
4874 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4876 if (lyxrc.allow_geometry_session)
4877 dialog->restoreSession();
4884 void GuiView::showDialog(string const & name, string const & sdata,
4887 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4891 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4897 const string name = fromqstr(qname);
4898 const string sdata = fromqstr(qdata);
4902 Dialog * dialog = findOrBuild(name, false);
4904 bool const visible = dialog->isVisibleView();
4905 dialog->showData(sdata);
4906 if (currentBufferView())
4907 currentBufferView()->editInset(name, inset);
4908 // We only set the focus to the new dialog if it was not yet
4909 // visible in order not to change the existing previous behaviour
4911 // activateWindow is needed for floating dockviews
4912 dialog->asQWidget()->raise();
4913 dialog->asQWidget()->activateWindow();
4914 if (dialog->wantInitialFocus())
4915 dialog->asQWidget()->setFocus();
4919 catch (ExceptionMessage const &) {
4927 bool GuiView::isDialogVisible(string const & name) const
4929 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4930 if (it == d.dialogs_.end())
4932 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4936 void GuiView::hideDialog(string const & name, Inset * inset)
4938 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4939 if (it == d.dialogs_.end())
4943 if (!currentBufferView())
4945 if (inset != currentBufferView()->editedInset(name))
4949 Dialog * const dialog = it->second.get();
4950 if (dialog->isVisibleView())
4952 if (currentBufferView())
4953 currentBufferView()->editInset(name, nullptr);
4957 void GuiView::disconnectDialog(string const & name)
4959 if (!isValidName(name))
4961 if (currentBufferView())
4962 currentBufferView()->editInset(name, nullptr);
4966 void GuiView::hideAll() const
4968 for(auto const & dlg_p : d.dialogs_)
4969 dlg_p.second->hideView();
4973 void GuiView::updateDialogs()
4975 for(auto const & dlg_p : d.dialogs_) {
4976 Dialog * dialog = dlg_p.second.get();
4978 if (dialog->needBufferOpen() && !documentBufferView())
4979 hideDialog(fromqstr(dialog->name()), nullptr);
4980 else if (dialog->isVisibleView())
4981 dialog->checkStatus();
4989 Dialog * GuiView::build(string const & name)
4991 return createDialog(*this, name);
4995 SEMenu::SEMenu(QWidget * parent)
4997 QAction * action = addAction(qt_("Disable Shell Escape"));
4998 connect(action, SIGNAL(triggered()),
4999 parent, SLOT(disableShellEscape()));
5002 } // namespace frontend
5005 #include "moc_GuiView.cpp"