3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiClickableLabel.h"
23 #include "GuiCompleter.h"
24 #include "GuiFontMetrics.h"
25 #include "GuiKeySymbol.h"
27 #include "GuiToolbar.h"
28 #include "GuiWorkArea.h"
29 #include "GuiProgress.h"
30 #include "LayoutBox.h"
34 #include "qt_helpers.h"
35 #include "support/filetools.h"
37 #include "frontends/alert.h"
38 #include "frontends/KeySymbol.h"
40 #include "buffer_funcs.h"
42 #include "BufferList.h"
43 #include "BufferParams.h"
44 #include "BufferView.h"
46 #include "Converter.h"
48 #include "CutAndPaste.h"
50 #include "ErrorList.h"
52 #include "FuncStatus.h"
53 #include "FuncRequest.h"
54 #include "KeySymbol.h"
56 #include "LayoutFile.h"
58 #include "LyXAction.h"
62 #include "Paragraph.h"
63 #include "SpellChecker.h"
70 #include "support/convert.h"
71 #include "support/debug.h"
72 #include "support/ExceptionMessage.h"
73 #include "support/FileName.h"
74 #include "support/gettext.h"
75 #include "support/ForkedCalls.h"
76 #include "support/lassert.h"
77 #include "support/lstrings.h"
78 #include "support/os.h"
79 #include "support/Package.h"
80 #include "support/PathChanger.h"
81 #include "support/Systemcall.h"
82 #include "support/Timeout.h"
83 #include "support/ProgressInterface.h"
86 #include <QApplication>
87 #include <QCloseEvent>
88 #include <QDragEnterEvent>
91 #include <QFutureWatcher>
103 #include <QShowEvent>
105 #include <QStackedWidget>
106 #include <QStatusBar>
107 #include <QSvgRenderer>
108 #include <QtConcurrentRun>
111 #include <QWindowStateChangeEvent>
114 // sync with GuiAlert.cpp
115 #define EXPORT_in_THREAD 1
118 #include "support/bind.h"
122 #ifdef HAVE_SYS_TIME_H
123 # include <sys/time.h>
131 using namespace lyx::support;
135 using support::addExtension;
136 using support::changeExtension;
137 using support::removeExtension;
143 class BackgroundWidget : public QWidget
146 BackgroundWidget(int width, int height)
147 : width_(width), height_(height)
149 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
150 if (!lyxrc.show_banner)
152 /// The text to be written on top of the pixmap
153 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
154 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
155 /// The text to be written on top of the pixmap
156 QString const text = lyx_version ?
157 qt_("version ") + lyx_version : qt_("unknown version");
158 #if QT_VERSION >= 0x050000
159 QString imagedir = "images/";
160 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
161 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
162 if (svgRenderer.isValid()) {
163 splash_ = QPixmap(splashSize());
164 QPainter painter(&splash_);
165 svgRenderer.render(&painter);
166 splash_.setDevicePixelRatio(pixelRatio());
168 splash_ = getPixmap("images/", "banner", "png");
171 splash_ = getPixmap("images/", "banner", "svgz,png");
174 QPainter pain(&splash_);
175 pain.setPen(QColor(0, 0, 0));
176 qreal const fsize = fontSize();
179 qreal locscale = htextsize.toFloat(&ok);
182 QPointF const position = textPosition(false);
183 QPointF const hposition = textPosition(true);
184 QRectF const hrect(hposition, splashSize());
186 "widget pixel ratio: " << pixelRatio() <<
187 " splash pixel ratio: " << splashPixelRatio() <<
188 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
190 // The font used to display the version info
191 font.setStyleHint(QFont::SansSerif);
192 font.setWeight(QFont::Bold);
193 font.setPointSizeF(fsize);
195 pain.drawText(position, text);
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Normal);
199 font.setPointSizeF(hfsize);
200 // Check how long the logo gets with the current font
201 // and adapt if the font is running wider than what
203 GuiFontMetrics fm(font);
204 // Split the title into lines to measure the longest line
205 // in the current l7n.
206 QStringList titlesegs = htext.split('\n');
208 int hline = fm.maxHeight();
209 QStringList::const_iterator sit;
210 for (QString const & seg : titlesegs) {
211 if (fm.width(seg) > wline)
212 wline = fm.width(seg);
214 // The longest line in the reference font (for English)
215 // is 180. Calculate scale factor from that.
216 double const wscale = wline > 0 ? (180.0 / wline) : 1;
217 // Now do the same for the height (necessary for condensed fonts)
218 double const hscale = (34.0 / hline);
219 // take the lower of the two scale factors.
220 double const scale = min(wscale, hscale);
221 // Now rescale. Also consider l7n's offset factor.
222 font.setPointSizeF(hfsize * scale * locscale);
225 pain.drawText(hrect, Qt::AlignLeft, htext);
226 setFocusPolicy(Qt::StrongFocus);
229 void paintEvent(QPaintEvent *) override
231 int const w = width_;
232 int const h = height_;
233 int const x = (width() - w) / 2;
234 int const y = (height() - h) / 2;
236 "widget pixel ratio: " << pixelRatio() <<
237 " splash pixel ratio: " << splashPixelRatio() <<
238 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
240 pain.drawPixmap(x, y, w, h, splash_);
243 void keyPressEvent(QKeyEvent * ev) override
246 setKeySymbol(&sym, ev);
248 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
260 /// Current ratio between physical pixels and device-independent pixels
261 double pixelRatio() const {
262 #if QT_VERSION >= 0x050000
263 return qt_scale_factor * devicePixelRatio();
269 qreal fontSize() const {
270 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
273 QPointF textPosition(bool const heading) const {
274 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
275 : QPointF(width_/2 - 18, height_/2 + 45);
278 QSize splashSize() const {
280 static_cast<unsigned int>(width_ * pixelRatio()),
281 static_cast<unsigned int>(height_ * pixelRatio()));
284 /// Ratio between physical pixels and device-independent pixels of splash image
285 double splashPixelRatio() const {
286 #if QT_VERSION >= 0x050000
287 return splash_.devicePixelRatio();
295 /// Toolbar store providing access to individual toolbars by name.
296 typedef map<string, GuiToolbar *> ToolbarMap;
298 typedef shared_ptr<Dialog> DialogPtr;
303 class GuiView::GuiViewPrivate
306 GuiViewPrivate(GuiViewPrivate const &);
307 void operator=(GuiViewPrivate const &);
309 GuiViewPrivate(GuiView * gv)
310 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
311 layout_(nullptr), autosave_timeout_(5000),
314 // hardcode here the platform specific icon size
315 smallIconSize = 16; // scaling problems
316 normalIconSize = 20; // ok, default if iconsize.png is missing
317 bigIconSize = 26; // better for some math icons
318 hugeIconSize = 32; // better for hires displays
321 // if it exists, use width of iconsize.png as normal size
322 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
323 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
325 QImage image(toqstr(fn.absFileName()));
326 if (image.width() < int(smallIconSize))
327 normalIconSize = smallIconSize;
328 else if (image.width() > int(giantIconSize))
329 normalIconSize = giantIconSize;
331 normalIconSize = image.width();
334 splitter_ = new QSplitter;
335 bg_widget_ = new BackgroundWidget(400, 250);
336 stack_widget_ = new QStackedWidget;
337 stack_widget_->addWidget(bg_widget_);
338 stack_widget_->addWidget(splitter_);
341 // TODO cleanup, remove the singleton, handle multiple Windows?
342 progress_ = ProgressInterface::instance();
343 if (!dynamic_cast<GuiProgress*>(progress_)) {
344 progress_ = new GuiProgress; // TODO who deletes it
345 ProgressInterface::setInstance(progress_);
348 dynamic_cast<GuiProgress*>(progress_),
349 SIGNAL(updateStatusBarMessage(QString const&)),
350 gv, SLOT(updateStatusBarMessage(QString const&)));
352 dynamic_cast<GuiProgress*>(progress_),
353 SIGNAL(clearMessageText()),
354 gv, SLOT(clearMessageText()));
361 delete stack_widget_;
366 stack_widget_->setCurrentWidget(bg_widget_);
367 bg_widget_->setUpdatesEnabled(true);
368 bg_widget_->setFocus();
371 int tabWorkAreaCount()
373 return splitter_->count();
376 TabWorkArea * tabWorkArea(int i)
378 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
381 TabWorkArea * currentTabWorkArea()
383 int areas = tabWorkAreaCount();
385 // The first TabWorkArea is always the first one, if any.
386 return tabWorkArea(0);
388 for (int i = 0; i != areas; ++i) {
389 TabWorkArea * twa = tabWorkArea(i);
390 if (current_main_work_area_ == twa->currentWorkArea())
394 // None has the focus so we just take the first one.
395 return tabWorkArea(0);
398 int countWorkAreasOf(Buffer & buf)
400 int areas = tabWorkAreaCount();
402 for (int i = 0; i != areas; ++i) {
403 TabWorkArea * twa = tabWorkArea(i);
404 if (twa->workArea(buf))
410 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
412 if (processing_thread_watcher_.isRunning()) {
413 // we prefer to cancel this preview in order to keep a snappy
417 processing_thread_watcher_.setFuture(f);
420 QSize iconSize(docstring const & icon_size)
423 if (icon_size == "small")
424 size = smallIconSize;
425 else if (icon_size == "normal")
426 size = normalIconSize;
427 else if (icon_size == "big")
429 else if (icon_size == "huge")
431 else if (icon_size == "giant")
432 size = giantIconSize;
434 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
436 if (size < smallIconSize)
437 size = smallIconSize;
439 return QSize(size, size);
442 QSize iconSize(QString const & icon_size)
444 return iconSize(qstring_to_ucs4(icon_size));
447 string & iconSize(QSize const & qsize)
449 LATTEST(qsize.width() == qsize.height());
451 static string icon_size;
453 unsigned int size = qsize.width();
455 if (size < smallIconSize)
456 size = smallIconSize;
458 if (size == smallIconSize)
460 else if (size == normalIconSize)
461 icon_size = "normal";
462 else if (size == bigIconSize)
464 else if (size == hugeIconSize)
466 else if (size == giantIconSize)
469 icon_size = convert<string>(size);
474 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
475 Buffer * buffer, string const & format);
476 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
483 static Buffer::ExportStatus runAndDestroy(const T& func,
484 Buffer const * orig, Buffer * buffer, string const & format);
486 // TODO syncFunc/previewFunc: use bind
487 bool asyncBufferProcessing(string const & argument,
488 Buffer const * used_buffer,
489 docstring const & msg,
490 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
491 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
492 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 QVector<GuiWorkArea*> guiWorkAreas();
499 GuiWorkArea * current_work_area_;
500 GuiWorkArea * current_main_work_area_;
501 QSplitter * splitter_;
502 QStackedWidget * stack_widget_;
503 BackgroundWidget * bg_widget_;
505 ToolbarMap toolbars_;
506 ProgressInterface* progress_;
507 /// The main layout box.
509 * \warning Don't Delete! The layout box is actually owned by
510 * whichever toolbar contains it. All the GuiView class needs is a
511 * means of accessing it.
513 * FIXME: replace that with a proper model so that we are not limited
514 * to only one dialog.
519 map<string, DialogPtr> dialogs_;
522 QTimer statusbar_timer_;
523 /// auto-saving of buffers
524 Timeout autosave_timeout_;
527 TocModels toc_models_;
530 QFutureWatcher<docstring> autosave_watcher_;
531 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
533 string last_export_format;
534 string processing_format;
536 static QSet<Buffer const *> busyBuffers;
538 unsigned int smallIconSize;
539 unsigned int normalIconSize;
540 unsigned int bigIconSize;
541 unsigned int hugeIconSize;
542 unsigned int giantIconSize;
544 /// flag against a race condition due to multiclicks, see bug #1119
548 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
551 GuiView::GuiView(int id)
552 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
553 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
556 connect(this, SIGNAL(bufferViewChanged()),
557 this, SLOT(onBufferViewChanged()));
559 // GuiToolbars *must* be initialised before the menu bar.
560 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
563 // set ourself as the current view. This is needed for the menu bar
564 // filling, at least for the static special menu item on Mac. Otherwise
565 // they are greyed out.
566 guiApp->setCurrentView(this);
568 // Fill up the menu bar.
569 guiApp->menus().fillMenuBar(menuBar(), this, true);
571 setCentralWidget(d.stack_widget_);
573 // Start autosave timer
574 if (lyxrc.autosave) {
575 // The connection is closed when this is destroyed.
576 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
577 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
578 d.autosave_timeout_.start();
580 connect(&d.statusbar_timer_, SIGNAL(timeout()),
581 this, SLOT(clearMessage()));
583 // We don't want to keep the window in memory if it is closed.
584 setAttribute(Qt::WA_DeleteOnClose, true);
586 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
587 // QIcon::fromTheme was introduced in Qt 4.6
588 #if (QT_VERSION >= 0x040600)
589 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
590 // since the icon is provided in the application bundle. We use a themed
591 // version when available and use the bundled one as fallback.
592 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
594 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
600 // use tabbed dock area for multiple docks
601 // (such as "source" and "messages")
602 setDockOptions(QMainWindow::ForceTabbedDocks);
605 setAcceptDrops(true);
607 // add busy indicator to statusbar
608 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
609 statusBar()->addPermanentWidget(busylabel);
610 search_mode mode = theGuiApp()->imageSearchMode();
611 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
612 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
613 busylabel->setMovie(busyanim);
617 connect(&d.processing_thread_watcher_, SIGNAL(started()),
618 busylabel, SLOT(show()));
619 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
620 busylabel, SLOT(hide()));
621 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
623 QFontMetrics const fm(statusBar()->fontMetrics());
624 int const iconheight = max(int(d.normalIconSize), fm.height());
625 QSize const iconsize(iconheight, iconheight);
627 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
628 shell_escape_ = new QLabel(statusBar());
629 shell_escape_->setPixmap(shellescape);
630 shell_escape_->setScaledContents(true);
631 shell_escape_->setAlignment(Qt::AlignCenter);
632 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
633 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
634 "external commands for this document. "
635 "Right click to change."));
636 SEMenu * menu = new SEMenu(this);
637 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
638 menu, SLOT(showMenu(QPoint)));
639 shell_escape_->hide();
640 statusBar()->addPermanentWidget(shell_escape_);
642 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
643 read_only_ = new QLabel(statusBar());
644 read_only_->setPixmap(readonly);
645 read_only_->setScaledContents(true);
646 read_only_->setAlignment(Qt::AlignCenter);
648 statusBar()->addPermanentWidget(read_only_);
650 version_control_ = new QLabel(statusBar());
651 version_control_->setAlignment(Qt::AlignCenter);
652 version_control_->setFrameStyle(QFrame::StyledPanel);
653 version_control_->hide();
654 statusBar()->addPermanentWidget(version_control_);
656 statusBar()->setSizeGripEnabled(true);
659 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
660 SLOT(autoSaveThreadFinished()));
662 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
663 SLOT(processingThreadStarted()));
664 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
665 SLOT(processingThreadFinished()));
667 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
668 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
670 // set custom application bars context menu, e.g. tool bar and menu bar
671 setContextMenuPolicy(Qt::CustomContextMenu);
672 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
673 SLOT(toolBarPopup(const QPoint &)));
675 // Forbid too small unresizable window because it can happen
676 // with some window manager under X11.
677 setMinimumSize(300, 200);
679 if (lyxrc.allow_geometry_session) {
680 // Now take care of session management.
685 // no session handling, default to a sane size.
686 setGeometry(50, 50, 690, 510);
689 // clear session data if any.
691 settings.remove("views");
701 void GuiView::disableShellEscape()
703 BufferView * bv = documentBufferView();
706 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
707 bv->buffer().params().shell_escape = false;
708 bv->processUpdateFlags(Update::Force);
712 void GuiView::checkCancelBackground()
714 docstring const ttl = _("Cancel Export?");
715 docstring const msg = _("Do you want to cancel the background export process?");
717 Alert::prompt(ttl, msg, 1, 1,
718 _("&Cancel export"), _("Co&ntinue"));
720 Systemcall::killscript();
724 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
726 QVector<GuiWorkArea*> areas;
727 for (int i = 0; i < tabWorkAreaCount(); i++) {
728 TabWorkArea* ta = tabWorkArea(i);
729 for (int u = 0; u < ta->count(); u++) {
730 areas << ta->workArea(u);
736 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
737 string const & format)
739 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
742 case Buffer::ExportSuccess:
743 msg = bformat(_("Successful export to format: %1$s"), fmt);
745 case Buffer::ExportCancel:
746 msg = _("Document export cancelled.");
748 case Buffer::ExportError:
749 case Buffer::ExportNoPathToFormat:
750 case Buffer::ExportTexPathHasSpaces:
751 case Buffer::ExportConverterError:
752 msg = bformat(_("Error while exporting format: %1$s"), fmt);
754 case Buffer::PreviewSuccess:
755 msg = bformat(_("Successful preview of format: %1$s"), fmt);
757 case Buffer::PreviewError:
758 msg = bformat(_("Error while previewing format: %1$s"), fmt);
760 case Buffer::ExportKilled:
761 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
768 void GuiView::processingThreadStarted()
773 void GuiView::processingThreadFinished()
775 QFutureWatcher<Buffer::ExportStatus> const * watcher =
776 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
778 Buffer::ExportStatus const status = watcher->result();
779 handleExportStatus(this, status, d.processing_format);
782 BufferView const * const bv = currentBufferView();
783 if (bv && !bv->buffer().errorList("Export").empty()) {
788 bool const error = (status != Buffer::ExportSuccess &&
789 status != Buffer::PreviewSuccess &&
790 status != Buffer::ExportCancel);
792 ErrorList & el = bv->buffer().errorList(d.last_export_format);
793 // at this point, we do not know if buffer-view or
794 // master-buffer-view was called. If there was an export error,
795 // and the current buffer's error log is empty, we guess that
796 // it must be master-buffer-view that was called so we set
798 errors(d.last_export_format, el.empty());
803 void GuiView::autoSaveThreadFinished()
805 QFutureWatcher<docstring> const * watcher =
806 static_cast<QFutureWatcher<docstring> const *>(sender());
807 message(watcher->result());
812 void GuiView::saveLayout() const
815 settings.setValue("zoom_ratio", zoom_ratio_);
816 settings.setValue("devel_mode", devel_mode_);
817 settings.beginGroup("views");
818 settings.beginGroup(QString::number(id_));
819 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
820 settings.setValue("pos", pos());
821 settings.setValue("size", size());
823 settings.setValue("geometry", saveGeometry());
824 settings.setValue("layout", saveState(0));
825 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
829 void GuiView::saveUISettings() const
833 // Save the toolbar private states
834 for (auto const & tb_p : d.toolbars_)
835 tb_p.second->saveSession(settings);
836 // Now take care of all other dialogs
837 for (auto const & dlg_p : d.dialogs_)
838 dlg_p.second->saveSession(settings);
842 bool GuiView::restoreLayout()
845 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
846 // Actual zoom value: default zoom + fractional offset
847 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
848 if (zoom < static_cast<int>(zoom_min_))
850 lyxrc.currentZoom = zoom;
851 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
852 settings.beginGroup("views");
853 settings.beginGroup(QString::number(id_));
854 QString const icon_key = "icon_size";
855 if (!settings.contains(icon_key))
858 //code below is skipped when when ~/.config/LyX is (re)created
859 setIconSize(d.iconSize(settings.value(icon_key).toString()));
861 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
862 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
863 QSize size = settings.value("size", QSize(690, 510)).toSize();
867 // Work-around for bug #6034: the window ends up in an undetermined
868 // state when trying to restore a maximized window when it is
869 // already maximized.
870 if (!(windowState() & Qt::WindowMaximized))
871 if (!restoreGeometry(settings.value("geometry").toByteArray()))
872 setGeometry(50, 50, 690, 510);
875 // Make sure layout is correctly oriented.
876 setLayoutDirection(qApp->layoutDirection());
878 // Allow the toc and view-source dock widget to be restored if needed.
880 if ((dialog = findOrBuild("toc", true)))
881 // see bug 5082. At least setup title and enabled state.
882 // Visibility will be adjusted by restoreState below.
883 dialog->prepareView();
884 if ((dialog = findOrBuild("view-source", true)))
885 dialog->prepareView();
886 if ((dialog = findOrBuild("progress", true)))
887 dialog->prepareView();
889 if (!restoreState(settings.value("layout").toByteArray(), 0))
892 // init the toolbars that have not been restored
893 for (auto const & tb_p : guiApp->toolbars()) {
894 GuiToolbar * tb = toolbar(tb_p.name);
895 if (tb && !tb->isRestored())
896 initToolbar(tb_p.name);
899 // update lock (all) toolbars positions
900 updateLockToolbars();
907 GuiToolbar * GuiView::toolbar(string const & name)
909 ToolbarMap::iterator it = d.toolbars_.find(name);
910 if (it != d.toolbars_.end())
913 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
918 void GuiView::updateLockToolbars()
920 toolbarsMovable_ = false;
921 for (ToolbarInfo const & info : guiApp->toolbars()) {
922 GuiToolbar * tb = toolbar(info.name);
923 if (tb && tb->isMovable())
924 toolbarsMovable_ = true;
929 void GuiView::constructToolbars()
931 for (auto const & tb_p : d.toolbars_)
935 // I don't like doing this here, but the standard toolbar
936 // destroys this object when it's destroyed itself (vfr)
937 d.layout_ = new LayoutBox(*this);
938 d.stack_widget_->addWidget(d.layout_);
939 d.layout_->move(0,0);
941 // extracts the toolbars from the backend
942 for (ToolbarInfo const & inf : guiApp->toolbars())
943 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
947 void GuiView::initToolbars()
949 // extracts the toolbars from the backend
950 for (ToolbarInfo const & inf : guiApp->toolbars())
951 initToolbar(inf.name);
955 void GuiView::initToolbar(string const & name)
957 GuiToolbar * tb = toolbar(name);
960 int const visibility = guiApp->toolbars().defaultVisibility(name);
961 bool newline = !(visibility & Toolbars::SAMEROW);
962 tb->setVisible(false);
963 tb->setVisibility(visibility);
965 if (visibility & Toolbars::TOP) {
967 addToolBarBreak(Qt::TopToolBarArea);
968 addToolBar(Qt::TopToolBarArea, tb);
971 if (visibility & Toolbars::BOTTOM) {
973 addToolBarBreak(Qt::BottomToolBarArea);
974 addToolBar(Qt::BottomToolBarArea, tb);
977 if (visibility & Toolbars::LEFT) {
979 addToolBarBreak(Qt::LeftToolBarArea);
980 addToolBar(Qt::LeftToolBarArea, tb);
983 if (visibility & Toolbars::RIGHT) {
985 addToolBarBreak(Qt::RightToolBarArea);
986 addToolBar(Qt::RightToolBarArea, tb);
989 if (visibility & Toolbars::ON)
990 tb->setVisible(true);
992 tb->setMovable(true);
996 TocModels & GuiView::tocModels()
998 return d.toc_models_;
1002 void GuiView::setFocus()
1004 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1005 QMainWindow::setFocus();
1009 bool GuiView::hasFocus() const
1011 if (currentWorkArea())
1012 return currentWorkArea()->hasFocus();
1013 if (currentMainWorkArea())
1014 return currentMainWorkArea()->hasFocus();
1015 return d.bg_widget_->hasFocus();
1019 void GuiView::focusInEvent(QFocusEvent * e)
1021 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1022 QMainWindow::focusInEvent(e);
1023 // Make sure guiApp points to the correct view.
1024 guiApp->setCurrentView(this);
1025 if (currentWorkArea())
1026 currentWorkArea()->setFocus();
1027 else if (currentMainWorkArea())
1028 currentMainWorkArea()->setFocus();
1030 d.bg_widget_->setFocus();
1034 void GuiView::showEvent(QShowEvent * e)
1036 LYXERR(Debug::GUI, "Passed Geometry "
1037 << size().height() << "x" << size().width()
1038 << "+" << pos().x() << "+" << pos().y());
1040 if (d.splitter_->count() == 0)
1041 // No work area, switch to the background widget.
1045 QMainWindow::showEvent(e);
1049 bool GuiView::closeScheduled()
1056 bool GuiView::prepareAllBuffersForLogout()
1058 Buffer * first = theBufferList().first();
1062 // First, iterate over all buffers and ask the users if unsaved
1063 // changes should be saved.
1064 // We cannot use a for loop as the buffer list cycles.
1067 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1069 b = theBufferList().next(b);
1070 } while (b != first);
1072 // Next, save session state
1073 // When a view/window was closed before without quitting LyX, there
1074 // are already entries in the lastOpened list.
1075 theSession().lastOpened().clear();
1082 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1083 ** is responsibility of the container (e.g., dialog)
1085 void GuiView::closeEvent(QCloseEvent * close_event)
1087 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1089 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1090 Alert::warning(_("Exit LyX"),
1091 _("LyX could not be closed because documents are being processed by LyX."));
1092 close_event->setAccepted(false);
1096 // If the user pressed the x (so we didn't call closeView
1097 // programmatically), we want to clear all existing entries.
1099 theSession().lastOpened().clear();
1104 // it can happen that this event arrives without selecting the view,
1105 // e.g. when clicking the close button on a background window.
1107 if (!closeWorkAreaAll()) {
1109 close_event->ignore();
1113 // Make sure that nothing will use this to be closed View.
1114 guiApp->unregisterView(this);
1116 if (isFullScreen()) {
1117 // Switch off fullscreen before closing.
1122 // Make sure the timer time out will not trigger a statusbar update.
1123 d.statusbar_timer_.stop();
1125 // Saving fullscreen requires additional tweaks in the toolbar code.
1126 // It wouldn't also work under linux natively.
1127 if (lyxrc.allow_geometry_session) {
1132 close_event->accept();
1136 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1138 if (event->mimeData()->hasUrls())
1140 /// \todo Ask lyx-devel is this is enough:
1141 /// if (event->mimeData()->hasFormat("text/plain"))
1142 /// event->acceptProposedAction();
1146 void GuiView::dropEvent(QDropEvent * event)
1148 QList<QUrl> files = event->mimeData()->urls();
1149 if (files.isEmpty())
1152 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1153 for (int i = 0; i != files.size(); ++i) {
1154 string const file = os::internal_path(fromqstr(
1155 files.at(i).toLocalFile()));
1159 string const ext = support::getExtension(file);
1160 vector<const Format *> found_formats;
1162 // Find all formats that have the correct extension.
1163 for (const Format * fmt : theConverters().importableFormats())
1164 if (fmt->hasExtension(ext))
1165 found_formats.push_back(fmt);
1168 if (!found_formats.empty()) {
1169 if (found_formats.size() > 1) {
1170 //FIXME: show a dialog to choose the correct importable format
1171 LYXERR(Debug::FILES,
1172 "Multiple importable formats found, selecting first");
1174 string const arg = found_formats[0]->name() + " " + file;
1175 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1178 //FIXME: do we have to explicitly check whether it's a lyx file?
1179 LYXERR(Debug::FILES,
1180 "No formats found, trying to open it as a lyx file");
1181 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1183 // add the functions to the queue
1184 guiApp->addToFuncRequestQueue(cmd);
1187 // now process the collected functions. We perform the events
1188 // asynchronously. This prevents potential problems in case the
1189 // BufferView is closed within an event.
1190 guiApp->processFuncRequestQueueAsync();
1194 void GuiView::message(docstring const & str)
1196 if (ForkedProcess::iAmAChild())
1199 // call is moved to GUI-thread by GuiProgress
1200 d.progress_->appendMessage(toqstr(str));
1204 void GuiView::clearMessageText()
1206 message(docstring());
1210 void GuiView::updateStatusBarMessage(QString const & str)
1212 statusBar()->showMessage(str);
1213 d.statusbar_timer_.stop();
1214 d.statusbar_timer_.start(3000);
1218 void GuiView::clearMessage()
1220 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1221 // the hasFocus function mostly returns false, even if the focus is on
1222 // a workarea in this view.
1226 d.statusbar_timer_.stop();
1230 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1232 if (wa != d.current_work_area_
1233 || wa->bufferView().buffer().isInternal())
1235 Buffer const & buf = wa->bufferView().buffer();
1236 // Set the windows title
1237 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1238 if (buf.notifiesExternalModification()) {
1239 title = bformat(_("%1$s (modified externally)"), title);
1240 // If the external modification status has changed, then maybe the status of
1241 // buffer-save has changed too.
1245 title += from_ascii(" - LyX");
1247 setWindowTitle(toqstr(title));
1248 // Sets the path for the window: this is used by OSX to
1249 // allow a context click on the title bar showing a menu
1250 // with the path up to the file
1251 setWindowFilePath(toqstr(buf.absFileName()));
1252 // Tell Qt whether the current document is changed
1253 setWindowModified(!buf.isClean());
1255 if (buf.params().shell_escape)
1256 shell_escape_->show();
1258 shell_escape_->hide();
1260 if (buf.hasReadonlyFlag())
1265 if (buf.lyxvc().inUse()) {
1266 version_control_->show();
1267 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1269 version_control_->hide();
1273 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1275 if (d.current_work_area_)
1276 // disconnect the current work area from all slots
1277 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1279 disconnectBufferView();
1280 connectBufferView(wa->bufferView());
1281 connectBuffer(wa->bufferView().buffer());
1282 d.current_work_area_ = wa;
1283 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1284 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1285 QObject::connect(wa, SIGNAL(busy(bool)),
1286 this, SLOT(setBusy(bool)));
1287 // connection of a signal to a signal
1288 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1289 this, SIGNAL(bufferViewChanged()));
1290 Q_EMIT updateWindowTitle(wa);
1291 Q_EMIT bufferViewChanged();
1295 void GuiView::onBufferViewChanged()
1298 // Buffer-dependent dialogs must be updated. This is done here because
1299 // some dialogs require buffer()->text.
1304 void GuiView::on_lastWorkAreaRemoved()
1307 // We already are in a close event. Nothing more to do.
1310 if (d.splitter_->count() > 1)
1311 // We have a splitter so don't close anything.
1314 // Reset and updates the dialogs.
1315 Q_EMIT bufferViewChanged();
1320 if (lyxrc.open_buffers_in_tabs)
1321 // Nothing more to do, the window should stay open.
1324 if (guiApp->viewIds().size() > 1) {
1330 // On Mac we also close the last window because the application stay
1331 // resident in memory. On other platforms we don't close the last
1332 // window because this would quit the application.
1338 void GuiView::updateStatusBar()
1340 // let the user see the explicit message
1341 if (d.statusbar_timer_.isActive())
1348 void GuiView::showMessage()
1352 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1353 if (msg.isEmpty()) {
1354 BufferView const * bv = currentBufferView();
1356 msg = toqstr(bv->cursor().currentState(devel_mode_));
1358 msg = qt_("Welcome to LyX!");
1360 statusBar()->showMessage(msg);
1364 bool GuiView::event(QEvent * e)
1368 // Useful debug code:
1369 //case QEvent::ActivationChange:
1370 //case QEvent::WindowDeactivate:
1371 //case QEvent::Paint:
1372 //case QEvent::Enter:
1373 //case QEvent::Leave:
1374 //case QEvent::HoverEnter:
1375 //case QEvent::HoverLeave:
1376 //case QEvent::HoverMove:
1377 //case QEvent::StatusTip:
1378 //case QEvent::DragEnter:
1379 //case QEvent::DragLeave:
1380 //case QEvent::Drop:
1383 case QEvent::WindowStateChange: {
1384 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1385 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1386 bool result = QMainWindow::event(e);
1387 bool nfstate = (windowState() & Qt::WindowFullScreen);
1388 if (!ofstate && nfstate) {
1389 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1390 // switch to full-screen state
1391 if (lyxrc.full_screen_statusbar)
1392 statusBar()->hide();
1393 if (lyxrc.full_screen_menubar)
1395 if (lyxrc.full_screen_toolbars) {
1396 for (auto const & tb_p : d.toolbars_)
1397 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1398 tb_p.second->hide();
1400 for (int i = 0; i != d.splitter_->count(); ++i)
1401 d.tabWorkArea(i)->setFullScreen(true);
1402 setContentsMargins(-2, -2, -2, -2);
1404 hideDialogs("prefs", nullptr);
1405 } else if (ofstate && !nfstate) {
1406 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1407 // switch back from full-screen state
1408 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1409 statusBar()->show();
1410 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1412 if (lyxrc.full_screen_toolbars) {
1413 for (auto const & tb_p : d.toolbars_)
1414 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1415 tb_p.second->show();
1418 for (int i = 0; i != d.splitter_->count(); ++i)
1419 d.tabWorkArea(i)->setFullScreen(false);
1420 setContentsMargins(0, 0, 0, 0);
1424 case QEvent::WindowActivate: {
1425 GuiView * old_view = guiApp->currentView();
1426 if (this == old_view) {
1428 return QMainWindow::event(e);
1430 if (old_view && old_view->currentBufferView()) {
1431 // save current selection to the selection buffer to allow
1432 // middle-button paste in this window.
1433 cap::saveSelection(old_view->currentBufferView()->cursor());
1435 guiApp->setCurrentView(this);
1436 if (d.current_work_area_)
1437 on_currentWorkAreaChanged(d.current_work_area_);
1441 return QMainWindow::event(e);
1444 case QEvent::ShortcutOverride: {
1446 if (isFullScreen() && menuBar()->isHidden()) {
1447 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1448 // FIXME: we should also try to detect special LyX shortcut such as
1449 // Alt-P and Alt-M. Right now there is a hack in
1450 // GuiWorkArea::processKeySym() that hides again the menubar for
1452 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1454 return QMainWindow::event(e);
1457 return QMainWindow::event(e);
1461 return QMainWindow::event(e);
1465 void GuiView::resetWindowTitle()
1467 setWindowTitle(qt_("LyX"));
1470 bool GuiView::focusNextPrevChild(bool /*next*/)
1477 bool GuiView::busy() const
1483 void GuiView::setBusy(bool busy)
1485 bool const busy_before = busy_ > 0;
1486 busy ? ++busy_ : --busy_;
1487 if ((busy_ > 0) == busy_before)
1488 // busy state didn't change
1492 QApplication::setOverrideCursor(Qt::WaitCursor);
1495 QApplication::restoreOverrideCursor();
1500 void GuiView::resetCommandExecute()
1502 command_execute_ = false;
1507 double GuiView::pixelRatio() const
1509 #if QT_VERSION >= 0x050000
1510 return qt_scale_factor * devicePixelRatio();
1517 GuiWorkArea * GuiView::workArea(int index)
1519 if (TabWorkArea * twa = d.currentTabWorkArea())
1520 if (index < twa->count())
1521 return twa->workArea(index);
1526 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1528 if (currentWorkArea()
1529 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1530 return currentWorkArea();
1531 if (TabWorkArea * twa = d.currentTabWorkArea())
1532 return twa->workArea(buffer);
1537 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1539 // Automatically create a TabWorkArea if there are none yet.
1540 TabWorkArea * tab_widget = d.splitter_->count()
1541 ? d.currentTabWorkArea() : addTabWorkArea();
1542 return tab_widget->addWorkArea(buffer, *this);
1546 TabWorkArea * GuiView::addTabWorkArea()
1548 TabWorkArea * twa = new TabWorkArea;
1549 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1550 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1551 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1552 this, SLOT(on_lastWorkAreaRemoved()));
1554 d.splitter_->addWidget(twa);
1555 d.stack_widget_->setCurrentWidget(d.splitter_);
1560 GuiWorkArea const * GuiView::currentWorkArea() const
1562 return d.current_work_area_;
1566 GuiWorkArea * GuiView::currentWorkArea()
1568 return d.current_work_area_;
1572 GuiWorkArea const * GuiView::currentMainWorkArea() const
1574 if (!d.currentTabWorkArea())
1576 return d.currentTabWorkArea()->currentWorkArea();
1580 GuiWorkArea * GuiView::currentMainWorkArea()
1582 if (!d.currentTabWorkArea())
1584 return d.currentTabWorkArea()->currentWorkArea();
1588 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1590 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1592 d.current_work_area_ = nullptr;
1594 Q_EMIT bufferViewChanged();
1598 // FIXME: I've no clue why this is here and why it accesses
1599 // theGuiApp()->currentView, which might be 0 (bug 6464).
1600 // See also 27525 (vfr).
1601 if (theGuiApp()->currentView() == this
1602 && theGuiApp()->currentView()->currentWorkArea() == wa)
1605 if (currentBufferView())
1606 cap::saveSelection(currentBufferView()->cursor());
1608 theGuiApp()->setCurrentView(this);
1609 d.current_work_area_ = wa;
1611 // We need to reset this now, because it will need to be
1612 // right if the tabWorkArea gets reset in the for loop. We
1613 // will change it back if we aren't in that case.
1614 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1615 d.current_main_work_area_ = wa;
1617 for (int i = 0; i != d.splitter_->count(); ++i) {
1618 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1619 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1620 << ", Current main wa: " << currentMainWorkArea());
1625 d.current_main_work_area_ = old_cmwa;
1627 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1628 on_currentWorkAreaChanged(wa);
1629 BufferView & bv = wa->bufferView();
1630 bv.cursor().fixIfBroken();
1632 wa->setUpdatesEnabled(true);
1633 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1637 void GuiView::removeWorkArea(GuiWorkArea * wa)
1639 LASSERT(wa, return);
1640 if (wa == d.current_work_area_) {
1642 disconnectBufferView();
1643 d.current_work_area_ = nullptr;
1644 d.current_main_work_area_ = nullptr;
1647 bool found_twa = false;
1648 for (int i = 0; i != d.splitter_->count(); ++i) {
1649 TabWorkArea * twa = d.tabWorkArea(i);
1650 if (twa->removeWorkArea(wa)) {
1651 // Found in this tab group, and deleted the GuiWorkArea.
1653 if (twa->count() != 0) {
1654 if (d.current_work_area_ == nullptr)
1655 // This means that we are closing the current GuiWorkArea, so
1656 // switch to the next GuiWorkArea in the found TabWorkArea.
1657 setCurrentWorkArea(twa->currentWorkArea());
1659 // No more WorkAreas in this tab group, so delete it.
1666 // It is not a tabbed work area (i.e., the search work area), so it
1667 // should be deleted by other means.
1668 LASSERT(found_twa, return);
1670 if (d.current_work_area_ == nullptr) {
1671 if (d.splitter_->count() != 0) {
1672 TabWorkArea * twa = d.currentTabWorkArea();
1673 setCurrentWorkArea(twa->currentWorkArea());
1675 // No more work areas, switch to the background widget.
1676 setCurrentWorkArea(nullptr);
1682 LayoutBox * GuiView::getLayoutDialog() const
1688 void GuiView::updateLayoutList()
1691 d.layout_->updateContents(false);
1695 void GuiView::updateToolbars()
1697 if (d.current_work_area_) {
1699 if (d.current_work_area_->bufferView().cursor().inMathed()
1700 && !d.current_work_area_->bufferView().cursor().inRegexped())
1701 context |= Toolbars::MATH;
1702 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1703 context |= Toolbars::TABLE;
1704 if (currentBufferView()->buffer().areChangesPresent()
1705 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1706 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1707 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1708 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1709 context |= Toolbars::REVIEW;
1710 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1711 context |= Toolbars::MATHMACROTEMPLATE;
1712 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1713 context |= Toolbars::IPA;
1714 if (command_execute_)
1715 context |= Toolbars::MINIBUFFER;
1716 if (minibuffer_focus_) {
1717 context |= Toolbars::MINIBUFFER_FOCUS;
1718 minibuffer_focus_ = false;
1721 for (auto const & tb_p : d.toolbars_)
1722 tb_p.second->update(context);
1724 for (auto const & tb_p : d.toolbars_)
1725 tb_p.second->update();
1729 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1731 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1732 LASSERT(newBuffer, return);
1734 GuiWorkArea * wa = workArea(*newBuffer);
1735 if (wa == nullptr) {
1737 newBuffer->masterBuffer()->updateBuffer();
1739 wa = addWorkArea(*newBuffer);
1740 // scroll to the position when the BufferView was last closed
1741 if (lyxrc.use_lastfilepos) {
1742 LastFilePosSection::FilePos filepos =
1743 theSession().lastFilePos().load(newBuffer->fileName());
1744 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1747 //Disconnect the old buffer...there's no new one.
1750 connectBuffer(*newBuffer);
1751 connectBufferView(wa->bufferView());
1753 setCurrentWorkArea(wa);
1757 void GuiView::connectBuffer(Buffer & buf)
1759 buf.setGuiDelegate(this);
1763 void GuiView::disconnectBuffer()
1765 if (d.current_work_area_)
1766 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1770 void GuiView::connectBufferView(BufferView & bv)
1772 bv.setGuiDelegate(this);
1776 void GuiView::disconnectBufferView()
1778 if (d.current_work_area_)
1779 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1783 void GuiView::errors(string const & error_type, bool from_master)
1785 BufferView const * const bv = currentBufferView();
1789 ErrorList const & el = from_master ?
1790 bv->buffer().masterBuffer()->errorList(error_type) :
1791 bv->buffer().errorList(error_type);
1796 string err = error_type;
1798 err = "from_master|" + error_type;
1799 showDialog("errorlist", err);
1803 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1805 d.toc_models_.updateItem(toqstr(type), dit);
1809 void GuiView::structureChanged()
1811 // This is called from the Buffer, which has no way to ensure that cursors
1812 // in BufferView remain valid.
1813 if (documentBufferView())
1814 documentBufferView()->cursor().sanitize();
1815 // FIXME: This is slightly expensive, though less than the tocBackend update
1816 // (#9880). This also resets the view in the Toc Widget (#6675).
1817 d.toc_models_.reset(documentBufferView());
1818 // Navigator needs more than a simple update in this case. It needs to be
1820 updateDialog("toc", "");
1824 void GuiView::updateDialog(string const & name, string const & sdata)
1826 if (!isDialogVisible(name))
1829 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1830 if (it == d.dialogs_.end())
1833 Dialog * const dialog = it->second.get();
1834 if (dialog->isVisibleView())
1835 dialog->initialiseParams(sdata);
1839 BufferView * GuiView::documentBufferView()
1841 return currentMainWorkArea()
1842 ? ¤tMainWorkArea()->bufferView()
1847 BufferView const * GuiView::documentBufferView() const
1849 return currentMainWorkArea()
1850 ? ¤tMainWorkArea()->bufferView()
1855 BufferView * GuiView::currentBufferView()
1857 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1861 BufferView const * GuiView::currentBufferView() const
1863 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1867 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1868 Buffer const * orig, Buffer * clone)
1870 bool const success = clone->autoSave();
1872 busyBuffers.remove(orig);
1874 ? _("Automatic save done.")
1875 : _("Automatic save failed!");
1879 void GuiView::autoSave()
1881 LYXERR(Debug::INFO, "Running autoSave()");
1883 Buffer * buffer = documentBufferView()
1884 ? &documentBufferView()->buffer() : nullptr;
1886 resetAutosaveTimers();
1890 GuiViewPrivate::busyBuffers.insert(buffer);
1891 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1892 buffer, buffer->cloneBufferOnly());
1893 d.autosave_watcher_.setFuture(f);
1894 resetAutosaveTimers();
1898 void GuiView::resetAutosaveTimers()
1901 d.autosave_timeout_.restart();
1905 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1908 Buffer * buf = currentBufferView()
1909 ? ¤tBufferView()->buffer() : nullptr;
1910 Buffer * doc_buffer = documentBufferView()
1911 ? &(documentBufferView()->buffer()) : nullptr;
1914 /* In LyX/Mac, when a dialog is open, the menus of the
1915 application can still be accessed without giving focus to
1916 the main window. In this case, we want to disable the menu
1917 entries that are buffer-related.
1918 This code must not be used on Linux and Windows, since it
1919 would disable buffer-related entries when hovering over the
1920 menu (see bug #9574).
1922 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1928 // Check whether we need a buffer
1929 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1930 // no, exit directly
1931 flag.message(from_utf8(N_("Command not allowed with"
1932 "out any document open")));
1933 flag.setEnabled(false);
1937 if (cmd.origin() == FuncRequest::TOC) {
1938 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1939 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1940 flag.setEnabled(false);
1944 switch(cmd.action()) {
1945 case LFUN_BUFFER_IMPORT:
1948 case LFUN_MASTER_BUFFER_EXPORT:
1950 && (doc_buffer->parent() != nullptr
1951 || doc_buffer->hasChildren())
1952 && !d.processing_thread_watcher_.isRunning()
1953 // this launches a dialog, which would be in the wrong Buffer
1954 && !(::lyx::operator==(cmd.argument(), "custom"));
1957 case LFUN_MASTER_BUFFER_UPDATE:
1958 case LFUN_MASTER_BUFFER_VIEW:
1960 && (doc_buffer->parent() != nullptr
1961 || doc_buffer->hasChildren())
1962 && !d.processing_thread_watcher_.isRunning();
1965 case LFUN_BUFFER_UPDATE:
1966 case LFUN_BUFFER_VIEW: {
1967 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1971 string format = to_utf8(cmd.argument());
1972 if (cmd.argument().empty())
1973 format = doc_buffer->params().getDefaultOutputFormat();
1974 enable = doc_buffer->params().isExportable(format, true);
1978 case LFUN_BUFFER_RELOAD:
1979 enable = doc_buffer && !doc_buffer->isUnnamed()
1980 && doc_buffer->fileName().exists()
1981 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1984 case LFUN_BUFFER_RESET_EXPORT:
1985 enable = doc_buffer != nullptr;
1988 case LFUN_BUFFER_CHILD_OPEN:
1989 enable = doc_buffer != nullptr;
1992 case LFUN_MASTER_BUFFER_FORALL: {
1993 if (doc_buffer == nullptr) {
1994 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
1998 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
1999 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2000 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2005 for (Buffer * buf : doc_buffer->allRelatives()) {
2006 GuiWorkArea * wa = workArea(*buf);
2009 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2010 enable = flag.enabled();
2017 case LFUN_BUFFER_WRITE:
2018 enable = doc_buffer && (doc_buffer->isUnnamed()
2019 || (!doc_buffer->isClean()
2020 || cmd.argument() == "force"));
2023 //FIXME: This LFUN should be moved to GuiApplication.
2024 case LFUN_BUFFER_WRITE_ALL: {
2025 // We enable the command only if there are some modified buffers
2026 Buffer * first = theBufferList().first();
2031 // We cannot use a for loop as the buffer list is a cycle.
2033 if (!b->isClean()) {
2037 b = theBufferList().next(b);
2038 } while (b != first);
2042 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2043 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2046 case LFUN_BUFFER_EXPORT: {
2047 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2051 return doc_buffer->getStatus(cmd, flag);
2054 case LFUN_BUFFER_EXPORT_AS:
2055 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2060 case LFUN_BUFFER_WRITE_AS:
2061 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2062 enable = doc_buffer != nullptr;
2065 case LFUN_EXPORT_CANCEL:
2066 enable = d.processing_thread_watcher_.isRunning();
2069 case LFUN_BUFFER_CLOSE:
2070 case LFUN_VIEW_CLOSE:
2071 enable = doc_buffer != nullptr;
2074 case LFUN_BUFFER_CLOSE_ALL:
2075 enable = theBufferList().last() != theBufferList().first();
2078 case LFUN_BUFFER_CHKTEX: {
2079 // hide if we have no checktex command
2080 if (lyxrc.chktex_command.empty()) {
2081 flag.setUnknown(true);
2085 if (!doc_buffer || !doc_buffer->params().isLatex()
2086 || d.processing_thread_watcher_.isRunning()) {
2087 // grey out, don't hide
2095 case LFUN_VIEW_SPLIT:
2096 if (cmd.getArg(0) == "vertical")
2097 enable = doc_buffer && (d.splitter_->count() == 1 ||
2098 d.splitter_->orientation() == Qt::Vertical);
2100 enable = doc_buffer && (d.splitter_->count() == 1 ||
2101 d.splitter_->orientation() == Qt::Horizontal);
2104 case LFUN_TAB_GROUP_CLOSE:
2105 enable = d.tabWorkAreaCount() > 1;
2108 case LFUN_DEVEL_MODE_TOGGLE:
2109 flag.setOnOff(devel_mode_);
2112 case LFUN_TOOLBAR_TOGGLE: {
2113 string const name = cmd.getArg(0);
2114 if (GuiToolbar * t = toolbar(name))
2115 flag.setOnOff(t->isVisible());
2118 docstring const msg =
2119 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2125 case LFUN_TOOLBAR_MOVABLE: {
2126 string const name = cmd.getArg(0);
2127 // use negation since locked == !movable
2129 // toolbar name * locks all toolbars
2130 flag.setOnOff(!toolbarsMovable_);
2131 else if (GuiToolbar * t = toolbar(name))
2132 flag.setOnOff(!(t->isMovable()));
2135 docstring const msg =
2136 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2142 case LFUN_ICON_SIZE:
2143 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2146 case LFUN_DROP_LAYOUTS_CHOICE:
2147 enable = buf != nullptr;
2150 case LFUN_UI_TOGGLE:
2151 flag.setOnOff(isFullScreen());
2154 case LFUN_DIALOG_DISCONNECT_INSET:
2157 case LFUN_DIALOG_HIDE:
2158 // FIXME: should we check if the dialog is shown?
2161 case LFUN_DIALOG_TOGGLE:
2162 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2165 case LFUN_DIALOG_SHOW: {
2166 string const name = cmd.getArg(0);
2168 enable = name == "aboutlyx"
2169 || name == "file" //FIXME: should be removed.
2170 || name == "lyxfiles"
2172 || name == "texinfo"
2173 || name == "progress"
2174 || name == "compare";
2175 else if (name == "character" || name == "symbols"
2176 || name == "mathdelimiter" || name == "mathmatrix") {
2177 if (!buf || buf->isReadonly())
2180 Cursor const & cur = currentBufferView()->cursor();
2181 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2184 else if (name == "latexlog")
2185 enable = FileName(doc_buffer->logName()).isReadableFile();
2186 else if (name == "spellchecker")
2187 enable = theSpellChecker()
2188 && !doc_buffer->isReadonly()
2189 && !doc_buffer->text().empty();
2190 else if (name == "vclog")
2191 enable = doc_buffer->lyxvc().inUse();
2195 case LFUN_DIALOG_UPDATE: {
2196 string const name = cmd.getArg(0);
2198 enable = name == "prefs";
2202 case LFUN_COMMAND_EXECUTE:
2204 case LFUN_MENU_OPEN:
2205 // Nothing to check.
2208 case LFUN_COMPLETION_INLINE:
2209 if (!d.current_work_area_
2210 || !d.current_work_area_->completer().inlinePossible(
2211 currentBufferView()->cursor()))
2215 case LFUN_COMPLETION_POPUP:
2216 if (!d.current_work_area_
2217 || !d.current_work_area_->completer().popupPossible(
2218 currentBufferView()->cursor()))
2223 if (!d.current_work_area_
2224 || !d.current_work_area_->completer().inlinePossible(
2225 currentBufferView()->cursor()))
2229 case LFUN_COMPLETION_ACCEPT:
2230 if (!d.current_work_area_
2231 || (!d.current_work_area_->completer().popupVisible()
2232 && !d.current_work_area_->completer().inlineVisible()
2233 && !d.current_work_area_->completer().completionAvailable()))
2237 case LFUN_COMPLETION_CANCEL:
2238 if (!d.current_work_area_
2239 || (!d.current_work_area_->completer().popupVisible()
2240 && !d.current_work_area_->completer().inlineVisible()))
2244 case LFUN_BUFFER_ZOOM_OUT:
2245 case LFUN_BUFFER_ZOOM_IN: {
2246 // only diff between these two is that the default for ZOOM_OUT
2248 bool const neg_zoom =
2249 convert<int>(cmd.argument()) < 0 ||
2250 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2251 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2252 docstring const msg =
2253 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2257 enable = doc_buffer;
2261 case LFUN_BUFFER_ZOOM: {
2262 bool const less_than_min_zoom =
2263 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2264 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2265 docstring const msg =
2266 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2271 enable = doc_buffer;
2275 case LFUN_BUFFER_MOVE_NEXT:
2276 case LFUN_BUFFER_MOVE_PREVIOUS:
2277 // we do not cycle when moving
2278 case LFUN_BUFFER_NEXT:
2279 case LFUN_BUFFER_PREVIOUS:
2280 // because we cycle, it doesn't matter whether on first or last
2281 enable = (d.currentTabWorkArea()->count() > 1);
2283 case LFUN_BUFFER_SWITCH:
2284 // toggle on the current buffer, but do not toggle off
2285 // the other ones (is that a good idea?)
2287 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2288 flag.setOnOff(true);
2291 case LFUN_VC_REGISTER:
2292 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2294 case LFUN_VC_RENAME:
2295 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2298 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2300 case LFUN_VC_CHECK_IN:
2301 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2303 case LFUN_VC_CHECK_OUT:
2304 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2306 case LFUN_VC_LOCKING_TOGGLE:
2307 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2308 && doc_buffer->lyxvc().lockingToggleEnabled();
2309 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2311 case LFUN_VC_REVERT:
2312 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2313 && !doc_buffer->hasReadonlyFlag();
2315 case LFUN_VC_UNDO_LAST:
2316 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2318 case LFUN_VC_REPO_UPDATE:
2319 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2321 case LFUN_VC_COMMAND: {
2322 if (cmd.argument().empty())
2324 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2328 case LFUN_VC_COMPARE:
2329 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2332 case LFUN_SERVER_GOTO_FILE_ROW:
2333 case LFUN_LYX_ACTIVATE:
2334 case LFUN_WINDOW_RAISE:
2336 case LFUN_FORWARD_SEARCH:
2337 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2340 case LFUN_FILE_INSERT_PLAINTEXT:
2341 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2342 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2345 case LFUN_SPELLING_CONTINUOUSLY:
2346 flag.setOnOff(lyxrc.spellcheck_continuously);
2349 case LFUN_CITATION_OPEN:
2358 flag.setEnabled(false);
2364 static FileName selectTemplateFile()
2366 FileDialog dlg(qt_("Select template file"));
2367 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2368 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2370 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2371 QStringList(qt_("LyX Documents (*.lyx)")));
2373 if (result.first == FileDialog::Later)
2375 if (result.second.isEmpty())
2377 return FileName(fromqstr(result.second));
2381 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2385 Buffer * newBuffer = nullptr;
2387 newBuffer = checkAndLoadLyXFile(filename);
2388 } catch (ExceptionMessage const &) {
2395 message(_("Document not loaded."));
2399 setBuffer(newBuffer);
2400 newBuffer->errors("Parse");
2403 theSession().lastFiles().add(filename);
2404 theSession().writeFile();
2411 void GuiView::openDocument(string const & fname)
2413 string initpath = lyxrc.document_path;
2415 if (documentBufferView()) {
2416 string const trypath = documentBufferView()->buffer().filePath();
2417 // If directory is writeable, use this as default.
2418 if (FileName(trypath).isDirWritable())
2424 if (fname.empty()) {
2425 FileDialog dlg(qt_("Select document to open"));
2426 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2427 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2429 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2430 FileDialog::Result result =
2431 dlg.open(toqstr(initpath), filter);
2433 if (result.first == FileDialog::Later)
2436 filename = fromqstr(result.second);
2438 // check selected filename
2439 if (filename.empty()) {
2440 message(_("Canceled."));
2446 // get absolute path of file and add ".lyx" to the filename if
2448 FileName const fullname =
2449 fileSearch(string(), filename, "lyx", support::may_not_exist);
2450 if (!fullname.empty())
2451 filename = fullname.absFileName();
2453 if (!fullname.onlyPath().isDirectory()) {
2454 Alert::warning(_("Invalid filename"),
2455 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2456 from_utf8(fullname.absFileName())));
2460 // if the file doesn't exist and isn't already open (bug 6645),
2461 // let the user create one
2462 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2463 !LyXVC::file_not_found_hook(fullname)) {
2464 // the user specifically chose this name. Believe him.
2465 Buffer * const b = newFile(filename, string(), true);
2471 docstring const disp_fn = makeDisplayPath(filename);
2472 message(bformat(_("Opening document %1$s..."), disp_fn));
2475 Buffer * buf = loadDocument(fullname);
2477 str2 = bformat(_("Document %1$s opened."), disp_fn);
2478 if (buf->lyxvc().inUse())
2479 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2480 " " + _("Version control detected.");
2482 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2487 // FIXME: clean that
2488 static bool import(GuiView * lv, FileName const & filename,
2489 string const & format, ErrorList & errorList)
2491 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2493 string loader_format;
2494 vector<string> loaders = theConverters().loaders();
2495 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2496 vector<string>::const_iterator it = loaders.begin();
2497 vector<string>::const_iterator en = loaders.end();
2498 for (string const & loader : loaders) {
2499 if (!theConverters().isReachable(format, loader))
2502 string const tofile =
2503 support::changeExtension(filename.absFileName(),
2504 theFormats().extension(*it));
2505 if (theConverters().convert(nullptr, filename, FileName(tofile),
2506 filename, format, *it, errorList) != Converters::SUCCESS)
2508 loader_format = *it;
2511 if (loader_format.empty()) {
2512 frontend::Alert::error(_("Couldn't import file"),
2513 bformat(_("No information for importing the format %1$s."),
2514 translateIfPossible(theFormats().prettyName(format))));
2518 loader_format = format;
2520 if (loader_format == "lyx") {
2521 Buffer * buf = lv->loadDocument(lyxfile);
2525 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2529 bool as_paragraphs = loader_format == "textparagraph";
2530 string filename2 = (loader_format == format) ? filename.absFileName()
2531 : support::changeExtension(filename.absFileName(),
2532 theFormats().extension(loader_format));
2533 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2535 guiApp->setCurrentView(lv);
2536 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2543 void GuiView::importDocument(string const & argument)
2546 string filename = split(argument, format, ' ');
2548 LYXERR(Debug::INFO, format << " file: " << filename);
2550 // need user interaction
2551 if (filename.empty()) {
2552 string initpath = lyxrc.document_path;
2553 if (documentBufferView()) {
2554 string const trypath = documentBufferView()->buffer().filePath();
2555 // If directory is writeable, use this as default.
2556 if (FileName(trypath).isDirWritable())
2560 docstring const text = bformat(_("Select %1$s file to import"),
2561 translateIfPossible(theFormats().prettyName(format)));
2563 FileDialog dlg(toqstr(text));
2564 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2565 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2567 docstring filter = translateIfPossible(theFormats().prettyName(format));
2570 filter += from_utf8(theFormats().extensions(format));
2573 FileDialog::Result result =
2574 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2576 if (result.first == FileDialog::Later)
2579 filename = fromqstr(result.second);
2581 // check selected filename
2582 if (filename.empty())
2583 message(_("Canceled."));
2586 if (filename.empty())
2589 // get absolute path of file
2590 FileName const fullname(support::makeAbsPath(filename));
2592 // Can happen if the user entered a path into the dialog
2594 if (fullname.onlyFileName().empty()) {
2595 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2596 "Aborting import."),
2597 from_utf8(fullname.absFileName()));
2598 frontend::Alert::error(_("File name error"), msg);
2599 message(_("Canceled."));
2604 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2606 // Check if the document already is open
2607 Buffer * buf = theBufferList().getBuffer(lyxfile);
2610 if (!closeBuffer()) {
2611 message(_("Canceled."));
2616 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2618 // if the file exists already, and we didn't do
2619 // -i lyx thefile.lyx, warn
2620 if (lyxfile.exists() && fullname != lyxfile) {
2622 docstring text = bformat(_("The document %1$s already exists.\n\n"
2623 "Do you want to overwrite that document?"), displaypath);
2624 int const ret = Alert::prompt(_("Overwrite document?"),
2625 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2628 message(_("Canceled."));
2633 message(bformat(_("Importing %1$s..."), displaypath));
2634 ErrorList errorList;
2635 if (import(this, fullname, format, errorList))
2636 message(_("imported."));
2638 message(_("file not imported!"));
2640 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2644 void GuiView::newDocument(string const & filename, string templatefile,
2647 FileName initpath(lyxrc.document_path);
2648 if (documentBufferView()) {
2649 FileName const trypath(documentBufferView()->buffer().filePath());
2650 // If directory is writeable, use this as default.
2651 if (trypath.isDirWritable())
2655 if (from_template) {
2656 if (templatefile.empty())
2657 templatefile = selectTemplateFile().absFileName();
2658 if (templatefile.empty())
2663 if (filename.empty())
2664 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2666 b = newFile(filename, templatefile, true);
2671 // If no new document could be created, it is unsure
2672 // whether there is a valid BufferView.
2673 if (currentBufferView())
2674 // Ensure the cursor is correctly positioned on screen.
2675 currentBufferView()->showCursor();
2679 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2681 BufferView * bv = documentBufferView();
2686 FileName filename(to_utf8(fname));
2687 if (filename.empty()) {
2688 // Launch a file browser
2690 string initpath = lyxrc.document_path;
2691 string const trypath = bv->buffer().filePath();
2692 // If directory is writeable, use this as default.
2693 if (FileName(trypath).isDirWritable())
2697 FileDialog dlg(qt_("Select LyX document to insert"));
2698 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2699 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2701 FileDialog::Result result = dlg.open(toqstr(initpath),
2702 QStringList(qt_("LyX Documents (*.lyx)")));
2704 if (result.first == FileDialog::Later)
2708 filename.set(fromqstr(result.second));
2710 // check selected filename
2711 if (filename.empty()) {
2712 // emit message signal.
2713 message(_("Canceled."));
2718 bv->insertLyXFile(filename, ignorelang);
2719 bv->buffer().errors("Parse");
2723 string const GuiView::getTemplatesPath(Buffer & b)
2725 // We start off with the user's templates path
2726 string result = addPath(package().user_support().absFileName(), "templates");
2727 // Check for the document language
2728 string const langcode = b.params().language->code();
2729 string const shortcode = langcode.substr(0, 2);
2730 if (!langcode.empty() && shortcode != "en") {
2731 string subpath = addPath(result, shortcode);
2732 string subpath_long = addPath(result, langcode);
2733 // If we have a subdirectory for the language already,
2735 FileName sp = FileName(subpath);
2736 if (sp.isDirectory())
2738 else if (FileName(subpath_long).isDirectory())
2739 result = subpath_long;
2741 // Ask whether we should create such a subdirectory
2742 docstring const text =
2743 bformat(_("It is suggested to save the template in a subdirectory\n"
2744 "appropriate to the document language (%1$s).\n"
2745 "This subdirectory does not exists yet.\n"
2746 "Do you want to create it?"),
2747 _(b.params().language->display()));
2748 if (Alert::prompt(_("Create Language Directory?"),
2749 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2750 // If the user agreed, we try to create it and report if this failed.
2751 if (!sp.createDirectory(0777))
2752 Alert::error(_("Subdirectory creation failed!"),
2753 _("Could not create subdirectory.\n"
2754 "The template will be saved in the parent directory."));
2760 // Do we have a layout category?
2761 string const cat = b.params().baseClass() ?
2762 b.params().baseClass()->category()
2765 string subpath = addPath(result, cat);
2766 // If we have a subdirectory for the category already,
2768 FileName sp = FileName(subpath);
2769 if (sp.isDirectory())
2772 // Ask whether we should create such a subdirectory
2773 docstring const text =
2774 bformat(_("It is suggested to save the template in a subdirectory\n"
2775 "appropriate to the layout category (%1$s).\n"
2776 "This subdirectory does not exists yet.\n"
2777 "Do you want to create it?"),
2779 if (Alert::prompt(_("Create Category Directory?"),
2780 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2781 // If the user agreed, we try to create it and report if this failed.
2782 if (!sp.createDirectory(0777))
2783 Alert::error(_("Subdirectory creation failed!"),
2784 _("Could not create subdirectory.\n"
2785 "The template will be saved in the parent directory."));
2795 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2797 FileName fname = b.fileName();
2798 FileName const oldname = fname;
2799 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2801 if (!newname.empty()) {
2804 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2806 fname = support::makeAbsPath(to_utf8(newname),
2807 oldname.onlyPath().absFileName());
2809 // Switch to this Buffer.
2812 // No argument? Ask user through dialog.
2814 QString const title = as_template ? qt_("Choose a filename to save template as")
2815 : qt_("Choose a filename to save document as");
2816 FileDialog dlg(title);
2817 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2818 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2820 if (!isLyXFileName(fname.absFileName()))
2821 fname.changeExtension(".lyx");
2823 string const path = as_template ?
2825 : fname.onlyPath().absFileName();
2826 FileDialog::Result result =
2827 dlg.save(toqstr(path),
2828 QStringList(qt_("LyX Documents (*.lyx)")),
2829 toqstr(fname.onlyFileName()));
2831 if (result.first == FileDialog::Later)
2834 fname.set(fromqstr(result.second));
2839 if (!isLyXFileName(fname.absFileName()))
2840 fname.changeExtension(".lyx");
2843 // fname is now the new Buffer location.
2845 // if there is already a Buffer open with this name, we do not want
2846 // to have another one. (the second test makes sure we're not just
2847 // trying to overwrite ourselves, which is fine.)
2848 if (theBufferList().exists(fname) && fname != oldname
2849 && theBufferList().getBuffer(fname) != &b) {
2850 docstring const text =
2851 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2852 "Please close it before attempting to overwrite it.\n"
2853 "Do you want to choose a new filename?"),
2854 from_utf8(fname.absFileName()));
2855 int const ret = Alert::prompt(_("Chosen File Already Open"),
2856 text, 0, 1, _("&Rename"), _("&Cancel"));
2858 case 0: return renameBuffer(b, docstring(), kind);
2859 case 1: return false;
2864 bool const existsLocal = fname.exists();
2865 bool const existsInVC = LyXVC::fileInVC(fname);
2866 if (existsLocal || existsInVC) {
2867 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2868 if (kind != LV_WRITE_AS && existsInVC) {
2869 // renaming to a name that is already in VC
2871 docstring text = bformat(_("The document %1$s "
2872 "is already registered.\n\n"
2873 "Do you want to choose a new name?"),
2875 docstring const title = (kind == LV_VC_RENAME) ?
2876 _("Rename document?") : _("Copy document?");
2877 docstring const button = (kind == LV_VC_RENAME) ?
2878 _("&Rename") : _("&Copy");
2879 int const ret = Alert::prompt(title, text, 0, 1,
2880 button, _("&Cancel"));
2882 case 0: return renameBuffer(b, docstring(), kind);
2883 case 1: return false;
2888 docstring text = bformat(_("The document %1$s "
2889 "already exists.\n\n"
2890 "Do you want to overwrite that document?"),
2892 int const ret = Alert::prompt(_("Overwrite document?"),
2893 text, 0, 2, _("&Overwrite"),
2894 _("&Rename"), _("&Cancel"));
2897 case 1: return renameBuffer(b, docstring(), kind);
2898 case 2: return false;
2904 case LV_VC_RENAME: {
2905 string msg = b.lyxvc().rename(fname);
2908 message(from_utf8(msg));
2912 string msg = b.lyxvc().copy(fname);
2915 message(from_utf8(msg));
2919 case LV_WRITE_AS_TEMPLATE:
2922 // LyXVC created the file already in case of LV_VC_RENAME or
2923 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2924 // relative paths of included stuff right if we moved e.g. from
2925 // /a/b.lyx to /a/c/b.lyx.
2927 bool const saved = saveBuffer(b, fname);
2934 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2936 FileName fname = b.fileName();
2938 FileDialog dlg(qt_("Choose a filename to export the document as"));
2939 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2942 QString const anyformat = qt_("Guess from extension (*.*)");
2945 vector<Format const *> export_formats;
2946 for (Format const & f : theFormats())
2947 if (f.documentFormat())
2948 export_formats.push_back(&f);
2949 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2950 map<QString, string> fmap;
2953 for (Format const * f : export_formats) {
2954 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2955 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2957 from_ascii(f->extension())));
2958 types << loc_filter;
2959 fmap[loc_filter] = f->name();
2960 if (from_ascii(f->name()) == iformat) {
2961 filter = loc_filter;
2962 ext = f->extension();
2965 string ofname = fname.onlyFileName();
2967 ofname = support::changeExtension(ofname, ext);
2968 FileDialog::Result result =
2969 dlg.save(toqstr(fname.onlyPath().absFileName()),
2973 if (result.first != FileDialog::Chosen)
2977 fname.set(fromqstr(result.second));
2978 if (filter == anyformat)
2979 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2981 fmt_name = fmap[filter];
2982 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2983 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2985 if (fmt_name.empty() || fname.empty())
2988 // fname is now the new Buffer location.
2989 if (FileName(fname).exists()) {
2990 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2991 docstring text = bformat(_("The document %1$s already "
2992 "exists.\n\nDo you want to "
2993 "overwrite that document?"),
2995 int const ret = Alert::prompt(_("Overwrite document?"),
2996 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2999 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3000 case 2: return false;
3004 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3007 return dr.dispatched();
3011 bool GuiView::saveBuffer(Buffer & b)
3013 return saveBuffer(b, FileName());
3017 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3019 if (workArea(b) && workArea(b)->inDialogMode())
3022 if (fn.empty() && b.isUnnamed())
3023 return renameBuffer(b, docstring());
3025 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3027 theSession().lastFiles().add(b.fileName());
3028 theSession().writeFile();
3032 // Switch to this Buffer.
3035 // FIXME: we don't tell the user *WHY* the save failed !!
3036 docstring const file = makeDisplayPath(b.absFileName(), 30);
3037 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3038 "Do you want to rename the document and "
3039 "try again?"), file);
3040 int const ret = Alert::prompt(_("Rename and save?"),
3041 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3044 if (!renameBuffer(b, docstring()))
3053 return saveBuffer(b, fn);
3057 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3059 return closeWorkArea(wa, false);
3063 // We only want to close the buffer if it is not visible in other workareas
3064 // of the same view, nor in other views, and if this is not a child
3065 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3067 Buffer & buf = wa->bufferView().buffer();
3069 bool last_wa = d.countWorkAreasOf(buf) == 1
3070 && !inOtherView(buf) && !buf.parent();
3072 bool close_buffer = last_wa;
3075 if (lyxrc.close_buffer_with_last_view == "yes")
3077 else if (lyxrc.close_buffer_with_last_view == "no")
3078 close_buffer = false;
3081 if (buf.isUnnamed())
3082 file = from_utf8(buf.fileName().onlyFileName());
3084 file = buf.fileName().displayName(30);
3085 docstring const text = bformat(
3086 _("Last view on document %1$s is being closed.\n"
3087 "Would you like to close or hide the document?\n"
3089 "Hidden documents can be displayed back through\n"
3090 "the menu: View->Hidden->...\n"
3092 "To remove this question, set your preference in:\n"
3093 " Tools->Preferences->Look&Feel->UserInterface\n"
3095 int ret = Alert::prompt(_("Close or hide document?"),
3096 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3099 close_buffer = (ret == 0);
3103 return closeWorkArea(wa, close_buffer);
3107 bool GuiView::closeBuffer()
3109 GuiWorkArea * wa = currentMainWorkArea();
3110 // coverity complained about this
3111 // it seems unnecessary, but perhaps is worth the check
3112 LASSERT(wa, return false);
3114 setCurrentWorkArea(wa);
3115 Buffer & buf = wa->bufferView().buffer();
3116 return closeWorkArea(wa, !buf.parent());
3120 void GuiView::writeSession() const {
3121 GuiWorkArea const * active_wa = currentMainWorkArea();
3122 for (int i = 0; i < d.splitter_->count(); ++i) {
3123 TabWorkArea * twa = d.tabWorkArea(i);
3124 for (int j = 0; j < twa->count(); ++j) {
3125 GuiWorkArea * wa = twa->workArea(j);
3126 Buffer & buf = wa->bufferView().buffer();
3127 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3133 bool GuiView::closeBufferAll()
3136 for (auto & buf : theBufferList()) {
3137 if (!saveBufferIfNeeded(*buf, false)) {
3138 // Closing has been cancelled, so abort.
3143 // Close the workareas in all other views
3144 QList<int> const ids = guiApp->viewIds();
3145 for (int i = 0; i != ids.size(); ++i) {
3146 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3150 // Close our own workareas
3151 if (!closeWorkAreaAll())
3158 bool GuiView::closeWorkAreaAll()
3160 setCurrentWorkArea(currentMainWorkArea());
3162 // We might be in a situation that there is still a tabWorkArea, but
3163 // there are no tabs anymore. This can happen when we get here after a
3164 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3165 // many TabWorkArea's have no documents anymore.
3168 // We have to call count() each time, because it can happen that
3169 // more than one splitter will disappear in one iteration (bug 5998).
3170 while (d.splitter_->count() > empty_twa) {
3171 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3173 if (twa->count() == 0)
3176 setCurrentWorkArea(twa->currentWorkArea());
3177 if (!closeTabWorkArea(twa))
3185 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3190 Buffer & buf = wa->bufferView().buffer();
3192 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3193 Alert::warning(_("Close document"),
3194 _("Document could not be closed because it is being processed by LyX."));
3199 return closeBuffer(buf);
3201 if (!inMultiTabs(wa))
3202 if (!saveBufferIfNeeded(buf, true))
3210 bool GuiView::closeBuffer(Buffer & buf)
3212 bool success = true;
3213 for (Buffer * child_buf : buf.getChildren()) {
3214 if (theBufferList().isOthersChild(&buf, child_buf)) {
3215 child_buf->setParent(nullptr);
3219 // FIXME: should we look in other tabworkareas?
3220 // ANSWER: I don't think so. I've tested, and if the child is
3221 // open in some other window, it closes without a problem.
3222 GuiWorkArea * child_wa = workArea(*child_buf);
3225 // If we are in a close_event all children will be closed in some time,
3226 // so no need to do it here. This will ensure that the children end up
3227 // in the session file in the correct order. If we close the master
3228 // buffer, we can close or release the child buffers here too.
3230 success = closeWorkArea(child_wa, true);
3234 // In this case the child buffer is open but hidden.
3235 // Even in this case, children can be dirty (e.g.,
3236 // after a label change in the master, see #11405).
3237 // Therefore, check this
3238 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3239 // If we are in a close_event all children will be closed in some time,
3240 // so no need to do it here. This will ensure that the children end up
3241 // in the session file in the correct order. If we close the master
3242 // buffer, we can close or release the child buffers here too.
3245 // Save dirty buffers also if closing_!
3246 if (saveBufferIfNeeded(*child_buf, false)) {
3247 child_buf->removeAutosaveFile();
3248 theBufferList().release(child_buf);
3250 // Saving of dirty children has been cancelled.
3251 // Cancel the whole process.
3258 // goto bookmark to update bookmark pit.
3259 // FIXME: we should update only the bookmarks related to this buffer!
3260 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3261 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3262 guiApp->gotoBookmark(i + 1, false, false);
3264 if (saveBufferIfNeeded(buf, false)) {
3265 buf.removeAutosaveFile();
3266 theBufferList().release(&buf);
3270 // open all children again to avoid a crash because of dangling
3271 // pointers (bug 6603)
3277 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3279 while (twa == d.currentTabWorkArea()) {
3280 twa->setCurrentIndex(twa->count() - 1);
3282 GuiWorkArea * wa = twa->currentWorkArea();
3283 Buffer & b = wa->bufferView().buffer();
3285 // We only want to close the buffer if the same buffer is not visible
3286 // in another view, and if this is not a child and if we are closing
3287 // a view (not a tabgroup).
3288 bool const close_buffer =
3289 !inOtherView(b) && !b.parent() && closing_;
3291 if (!closeWorkArea(wa, close_buffer))
3298 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3300 if (buf.isClean() || buf.paragraphs().empty())
3303 // Switch to this Buffer.
3309 if (buf.isUnnamed()) {
3310 file = from_utf8(buf.fileName().onlyFileName());
3313 FileName filename = buf.fileName();
3315 file = filename.displayName(30);
3316 exists = filename.exists();
3319 // Bring this window to top before asking questions.
3324 if (hiding && buf.isUnnamed()) {
3325 docstring const text = bformat(_("The document %1$s has not been "
3326 "saved yet.\n\nDo you want to save "
3327 "the document?"), file);
3328 ret = Alert::prompt(_("Save new document?"),
3329 text, 0, 1, _("&Save"), _("&Cancel"));
3333 docstring const text = exists ?
3334 bformat(_("The document %1$s has unsaved changes."
3335 "\n\nDo you want to save the document or "
3336 "discard the changes?"), file) :
3337 bformat(_("The document %1$s has not been saved yet."
3338 "\n\nDo you want to save the document or "
3339 "discard it entirely?"), file);
3340 docstring const title = exists ?
3341 _("Save changed document?") : _("Save document?");
3342 ret = Alert::prompt(title, text, 0, 2,
3343 _("&Save"), _("&Discard"), _("&Cancel"));
3348 if (!saveBuffer(buf))
3352 // If we crash after this we could have no autosave file
3353 // but I guess this is really improbable (Jug).
3354 // Sometimes improbable things happen:
3355 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3356 // buf.removeAutosaveFile();
3358 // revert all changes
3369 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3371 Buffer & buf = wa->bufferView().buffer();
3373 for (int i = 0; i != d.splitter_->count(); ++i) {
3374 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3375 if (wa_ && wa_ != wa)
3378 return inOtherView(buf);
3382 bool GuiView::inOtherView(Buffer & buf)
3384 QList<int> const ids = guiApp->viewIds();
3386 for (int i = 0; i != ids.size(); ++i) {
3390 if (guiApp->view(ids[i]).workArea(buf))
3397 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3399 if (!documentBufferView())
3402 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3403 Buffer * const curbuf = &documentBufferView()->buffer();
3404 int nwa = twa->count();
3405 for (int i = 0; i < nwa; ++i) {
3406 if (&workArea(i)->bufferView().buffer() == curbuf) {
3408 if (np == NEXTBUFFER)
3409 next_index = (i == nwa - 1 ? 0 : i + 1);
3411 next_index = (i == 0 ? nwa - 1 : i - 1);
3413 twa->moveTab(i, next_index);
3415 setBuffer(&workArea(next_index)->bufferView().buffer());
3423 /// make sure the document is saved
3424 static bool ensureBufferClean(Buffer * buffer)
3426 LASSERT(buffer, return false);
3427 if (buffer->isClean() && !buffer->isUnnamed())
3430 docstring const file = buffer->fileName().displayName(30);
3433 if (!buffer->isUnnamed()) {
3434 text = bformat(_("The document %1$s has unsaved "
3435 "changes.\n\nDo you want to save "
3436 "the document?"), file);
3437 title = _("Save changed document?");
3440 text = bformat(_("The document %1$s has not been "
3441 "saved yet.\n\nDo you want to save "
3442 "the document?"), file);
3443 title = _("Save new document?");
3445 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3448 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3450 return buffer->isClean() && !buffer->isUnnamed();
3454 bool GuiView::reloadBuffer(Buffer & buf)
3456 currentBufferView()->cursor().reset();
3457 Buffer::ReadStatus status = buf.reload();
3458 return status == Buffer::ReadSuccess;
3462 void GuiView::checkExternallyModifiedBuffers()
3464 for (Buffer * buf : theBufferList()) {
3465 if (buf->fileName().exists() && buf->isChecksumModified()) {
3466 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3467 " Reload now? Any local changes will be lost."),
3468 from_utf8(buf->absFileName()));
3469 int const ret = Alert::prompt(_("Reload externally changed document?"),
3470 text, 0, 1, _("&Reload"), _("&Cancel"));
3478 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3480 Buffer * buffer = documentBufferView()
3481 ? &(documentBufferView()->buffer()) : nullptr;
3483 switch (cmd.action()) {
3484 case LFUN_VC_REGISTER:
3485 if (!buffer || !ensureBufferClean(buffer))
3487 if (!buffer->lyxvc().inUse()) {
3488 if (buffer->lyxvc().registrer()) {
3489 reloadBuffer(*buffer);
3490 dr.clearMessageUpdate();
3495 case LFUN_VC_RENAME:
3496 case LFUN_VC_COPY: {
3497 if (!buffer || !ensureBufferClean(buffer))
3499 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3500 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3501 // Some changes are not yet committed.
3502 // We test here and not in getStatus(), since
3503 // this test is expensive.
3505 LyXVC::CommandResult ret =
3506 buffer->lyxvc().checkIn(log);
3508 if (ret == LyXVC::ErrorCommand ||
3509 ret == LyXVC::VCSuccess)
3510 reloadBuffer(*buffer);
3511 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3512 frontend::Alert::error(
3513 _("Revision control error."),
3514 _("Document could not be checked in."));
3518 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3519 LV_VC_RENAME : LV_VC_COPY;
3520 renameBuffer(*buffer, cmd.argument(), kind);
3525 case LFUN_VC_CHECK_IN:
3526 if (!buffer || !ensureBufferClean(buffer))
3528 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3530 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3532 // Only skip reloading if the checkin was cancelled or
3533 // an error occurred before the real checkin VCS command
3534 // was executed, since the VCS might have changed the
3535 // file even if it could not checkin successfully.
3536 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3537 reloadBuffer(*buffer);
3541 case LFUN_VC_CHECK_OUT:
3542 if (!buffer || !ensureBufferClean(buffer))
3544 if (buffer->lyxvc().inUse()) {
3545 dr.setMessage(buffer->lyxvc().checkOut());
3546 reloadBuffer(*buffer);
3550 case LFUN_VC_LOCKING_TOGGLE:
3551 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3553 if (buffer->lyxvc().inUse()) {
3554 string res = buffer->lyxvc().lockingToggle();
3556 frontend::Alert::error(_("Revision control error."),
3557 _("Error when setting the locking property."));
3560 reloadBuffer(*buffer);
3565 case LFUN_VC_REVERT:
3568 if (buffer->lyxvc().revert()) {
3569 reloadBuffer(*buffer);
3570 dr.clearMessageUpdate();
3574 case LFUN_VC_UNDO_LAST:
3577 buffer->lyxvc().undoLast();
3578 reloadBuffer(*buffer);
3579 dr.clearMessageUpdate();
3582 case LFUN_VC_REPO_UPDATE:
3585 if (ensureBufferClean(buffer)) {
3586 dr.setMessage(buffer->lyxvc().repoUpdate());
3587 checkExternallyModifiedBuffers();
3591 case LFUN_VC_COMMAND: {
3592 string flag = cmd.getArg(0);
3593 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3596 if (contains(flag, 'M')) {
3597 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3600 string path = cmd.getArg(1);
3601 if (contains(path, "$$p") && buffer)
3602 path = subst(path, "$$p", buffer->filePath());
3603 LYXERR(Debug::LYXVC, "Directory: " << path);
3605 if (!pp.isReadableDirectory()) {
3606 lyxerr << _("Directory is not accessible.") << endl;
3609 support::PathChanger p(pp);
3611 string command = cmd.getArg(2);
3612 if (command.empty())
3615 command = subst(command, "$$i", buffer->absFileName());
3616 command = subst(command, "$$p", buffer->filePath());
3618 command = subst(command, "$$m", to_utf8(message));
3619 LYXERR(Debug::LYXVC, "Command: " << command);
3621 one.startscript(Systemcall::Wait, command);
3625 if (contains(flag, 'I'))
3626 buffer->markDirty();
3627 if (contains(flag, 'R'))
3628 reloadBuffer(*buffer);
3633 case LFUN_VC_COMPARE: {
3634 if (cmd.argument().empty()) {
3635 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3641 string rev1 = cmd.getArg(0);
3645 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3648 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3649 f2 = buffer->absFileName();
3651 string rev2 = cmd.getArg(1);
3655 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3659 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3660 f1 << "\n" << f2 << "\n" );
3661 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3662 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3672 void GuiView::openChildDocument(string const & fname)
3674 LASSERT(documentBufferView(), return);
3675 Buffer & buffer = documentBufferView()->buffer();
3676 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3677 documentBufferView()->saveBookmark(false);
3678 Buffer * child = nullptr;
3679 if (theBufferList().exists(filename)) {
3680 child = theBufferList().getBuffer(filename);
3683 message(bformat(_("Opening child document %1$s..."),
3684 makeDisplayPath(filename.absFileName())));
3685 child = loadDocument(filename, false);
3687 // Set the parent name of the child document.
3688 // This makes insertion of citations and references in the child work,
3689 // when the target is in the parent or another child document.
3691 child->setParent(&buffer);
3695 bool GuiView::goToFileRow(string const & argument)
3699 size_t i = argument.find_last_of(' ');
3700 if (i != string::npos) {
3701 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3702 istringstream is(argument.substr(i + 1));
3707 if (i == string::npos) {
3708 LYXERR0("Wrong argument: " << argument);
3711 Buffer * buf = nullptr;
3712 string const realtmp = package().temp_dir().realPath();
3713 // We have to use os::path_prefix_is() here, instead of
3714 // simply prefixIs(), because the file name comes from
3715 // an external application and may need case adjustment.
3716 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3717 buf = theBufferList().getBufferFromTmp(file_name, true);
3718 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3719 << (buf ? " success" : " failed"));
3721 // Must replace extension of the file to be .lyx
3722 // and get full path
3723 FileName const s = fileSearch(string(),
3724 support::changeExtension(file_name, ".lyx"), "lyx");
3725 // Either change buffer or load the file
3726 if (theBufferList().exists(s))
3727 buf = theBufferList().getBuffer(s);
3728 else if (s.exists()) {
3729 buf = loadDocument(s);
3734 _("File does not exist: %1$s"),
3735 makeDisplayPath(file_name)));
3741 _("No buffer for file: %1$s."),
3742 makeDisplayPath(file_name))
3747 bool success = documentBufferView()->setCursorFromRow(row);
3749 LYXERR(Debug::LATEX,
3750 "setCursorFromRow: invalid position for row " << row);
3751 frontend::Alert::error(_("Inverse Search Failed"),
3752 _("Invalid position requested by inverse search.\n"
3753 "You may need to update the viewed document."));
3759 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3761 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3762 menu->exec(QCursor::pos());
3767 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3768 Buffer const * orig, Buffer * clone, string const & format)
3770 Buffer::ExportStatus const status = func(format);
3772 // the cloning operation will have produced a clone of the entire set of
3773 // documents, starting from the master. so we must delete those.
3774 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3776 busyBuffers.remove(orig);
3781 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3782 Buffer const * orig, Buffer * clone, string const & format)
3784 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3786 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3790 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3791 Buffer const * orig, Buffer * clone, string const & format)
3793 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3795 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3799 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3800 Buffer const * orig, Buffer * clone, string const & format)
3802 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3804 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3808 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3809 string const & argument,
3810 Buffer const * used_buffer,
3811 docstring const & msg,
3812 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3813 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3814 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3820 string format = argument;
3822 format = used_buffer->params().getDefaultOutputFormat();
3823 processing_format = format;
3825 progress_->clearMessages();
3828 #if EXPORT_in_THREAD
3830 GuiViewPrivate::busyBuffers.insert(used_buffer);
3831 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3832 if (!cloned_buffer) {
3833 Alert::error(_("Export Error"),
3834 _("Error cloning the Buffer."));
3837 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3842 setPreviewFuture(f);
3843 last_export_format = used_buffer->params().bufferFormat();
3846 // We are asynchronous, so we don't know here anything about the success
3849 Buffer::ExportStatus status;
3851 status = (used_buffer->*syncFunc)(format, false);
3852 } else if (previewFunc) {
3853 status = (used_buffer->*previewFunc)(format);
3856 handleExportStatus(gv_, status, format);
3858 return (status == Buffer::ExportSuccess
3859 || status == Buffer::PreviewSuccess);
3863 Buffer::ExportStatus status;
3865 status = (used_buffer->*syncFunc)(format, true);
3866 } else if (previewFunc) {
3867 status = (used_buffer->*previewFunc)(format);
3870 handleExportStatus(gv_, status, format);
3872 return (status == Buffer::ExportSuccess
3873 || status == Buffer::PreviewSuccess);
3877 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3879 BufferView * bv = currentBufferView();
3880 LASSERT(bv, return);
3882 // Let the current BufferView dispatch its own actions.
3883 bv->dispatch(cmd, dr);
3884 if (dr.dispatched()) {
3885 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3886 updateDialog("document", "");
3890 // Try with the document BufferView dispatch if any.
3891 BufferView * doc_bv = documentBufferView();
3892 if (doc_bv && doc_bv != bv) {
3893 doc_bv->dispatch(cmd, dr);
3894 if (dr.dispatched()) {
3895 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3896 updateDialog("document", "");
3901 // Then let the current Cursor dispatch its own actions.
3902 bv->cursor().dispatch(cmd);
3904 // update completion. We do it here and not in
3905 // processKeySym to avoid another redraw just for a
3906 // changed inline completion
3907 if (cmd.origin() == FuncRequest::KEYBOARD) {
3908 if (cmd.action() == LFUN_SELF_INSERT
3909 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3910 updateCompletion(bv->cursor(), true, true);
3911 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3912 updateCompletion(bv->cursor(), false, true);
3914 updateCompletion(bv->cursor(), false, false);
3917 dr = bv->cursor().result();
3921 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3923 BufferView * bv = currentBufferView();
3924 // By default we won't need any update.
3925 dr.screenUpdate(Update::None);
3926 // assume cmd will be dispatched
3927 dr.dispatched(true);
3929 Buffer * doc_buffer = documentBufferView()
3930 ? &(documentBufferView()->buffer()) : nullptr;
3932 if (cmd.origin() == FuncRequest::TOC) {
3933 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3934 toc->doDispatch(bv->cursor(), cmd, dr);
3938 string const argument = to_utf8(cmd.argument());
3940 switch(cmd.action()) {
3941 case LFUN_BUFFER_CHILD_OPEN:
3942 openChildDocument(to_utf8(cmd.argument()));
3945 case LFUN_BUFFER_IMPORT:
3946 importDocument(to_utf8(cmd.argument()));
3949 case LFUN_MASTER_BUFFER_EXPORT:
3951 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3953 case LFUN_BUFFER_EXPORT: {
3956 // GCC only sees strfwd.h when building merged
3957 if (::lyx::operator==(cmd.argument(), "custom")) {
3958 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3959 // so the following test should not be needed.
3960 // In principle, we could try to switch to such a view...
3961 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3962 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3966 string const dest = cmd.getArg(1);
3967 FileName target_dir;
3968 if (!dest.empty() && FileName::isAbsolute(dest))
3969 target_dir = FileName(support::onlyPath(dest));
3971 target_dir = doc_buffer->fileName().onlyPath();
3973 string const format = (argument.empty() || argument == "default") ?
3974 doc_buffer->params().getDefaultOutputFormat() : argument;
3976 if ((dest.empty() && doc_buffer->isUnnamed())
3977 || !target_dir.isDirWritable()) {
3978 exportBufferAs(*doc_buffer, from_utf8(format));
3981 /* TODO/Review: Is it a problem to also export the children?
3982 See the update_unincluded flag */
3983 d.asyncBufferProcessing(format,
3986 &GuiViewPrivate::exportAndDestroy,
3988 nullptr, cmd.allowAsync());
3989 // TODO Inform user about success
3993 case LFUN_BUFFER_EXPORT_AS: {
3994 LASSERT(doc_buffer, break);
3995 docstring f = cmd.argument();
3997 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3998 exportBufferAs(*doc_buffer, f);
4002 case LFUN_BUFFER_UPDATE: {
4003 d.asyncBufferProcessing(argument,
4006 &GuiViewPrivate::compileAndDestroy,
4008 nullptr, cmd.allowAsync());
4011 case LFUN_BUFFER_VIEW: {
4012 d.asyncBufferProcessing(argument,
4014 _("Previewing ..."),
4015 &GuiViewPrivate::previewAndDestroy,
4017 &Buffer::preview, cmd.allowAsync());
4020 case LFUN_MASTER_BUFFER_UPDATE: {
4021 d.asyncBufferProcessing(argument,
4022 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4024 &GuiViewPrivate::compileAndDestroy,
4026 nullptr, cmd.allowAsync());
4029 case LFUN_MASTER_BUFFER_VIEW: {
4030 d.asyncBufferProcessing(argument,
4031 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4033 &GuiViewPrivate::previewAndDestroy,
4034 nullptr, &Buffer::preview, cmd.allowAsync());
4037 case LFUN_EXPORT_CANCEL: {
4038 Systemcall::killscript();
4041 case LFUN_BUFFER_SWITCH: {
4042 string const file_name = to_utf8(cmd.argument());
4043 if (!FileName::isAbsolute(file_name)) {
4045 dr.setMessage(_("Absolute filename expected."));
4049 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4052 dr.setMessage(_("Document not loaded"));
4056 // Do we open or switch to the buffer in this view ?
4057 if (workArea(*buffer)
4058 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4063 // Look for the buffer in other views
4064 QList<int> const ids = guiApp->viewIds();
4066 for (; i != ids.size(); ++i) {
4067 GuiView & gv = guiApp->view(ids[i]);
4068 if (gv.workArea(*buffer)) {
4070 gv.activateWindow();
4072 gv.setBuffer(buffer);
4077 // If necessary, open a new window as a last resort
4078 if (i == ids.size()) {
4079 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4085 case LFUN_BUFFER_NEXT:
4086 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4089 case LFUN_BUFFER_MOVE_NEXT:
4090 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4093 case LFUN_BUFFER_PREVIOUS:
4094 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4097 case LFUN_BUFFER_MOVE_PREVIOUS:
4098 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4101 case LFUN_BUFFER_CHKTEX:
4102 LASSERT(doc_buffer, break);
4103 doc_buffer->runChktex();
4106 case LFUN_COMMAND_EXECUTE: {
4107 command_execute_ = true;
4108 minibuffer_focus_ = true;
4111 case LFUN_DROP_LAYOUTS_CHOICE:
4112 d.layout_->showPopup();
4115 case LFUN_MENU_OPEN:
4116 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4117 menu->exec(QCursor::pos());
4120 case LFUN_FILE_INSERT: {
4121 if (cmd.getArg(1) == "ignorelang")
4122 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4124 insertLyXFile(cmd.argument());
4128 case LFUN_FILE_INSERT_PLAINTEXT:
4129 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4130 string const fname = to_utf8(cmd.argument());
4131 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4132 dr.setMessage(_("Absolute filename expected."));
4136 FileName filename(fname);
4137 if (fname.empty()) {
4138 FileDialog dlg(qt_("Select file to insert"));
4140 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4141 QStringList(qt_("All Files (*)")));
4143 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4144 dr.setMessage(_("Canceled."));
4148 filename.set(fromqstr(result.second));
4152 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4153 bv->dispatch(new_cmd, dr);
4158 case LFUN_BUFFER_RELOAD: {
4159 LASSERT(doc_buffer, break);
4162 bool drop = (cmd.argument() == "dump");
4165 if (!drop && !doc_buffer->isClean()) {
4166 docstring const file =
4167 makeDisplayPath(doc_buffer->absFileName(), 20);
4168 if (doc_buffer->notifiesExternalModification()) {
4169 docstring text = _("The current version will be lost. "
4170 "Are you sure you want to load the version on disk "
4171 "of the document %1$s?");
4172 ret = Alert::prompt(_("Reload saved document?"),
4173 bformat(text, file), 1, 1,
4174 _("&Reload"), _("&Cancel"));
4176 docstring text = _("Any changes will be lost. "
4177 "Are you sure you want to revert to the saved version "
4178 "of the document %1$s?");
4179 ret = Alert::prompt(_("Revert to saved document?"),
4180 bformat(text, file), 1, 1,
4181 _("&Revert"), _("&Cancel"));
4186 doc_buffer->markClean();
4187 reloadBuffer(*doc_buffer);
4188 dr.forceBufferUpdate();
4193 case LFUN_BUFFER_RESET_EXPORT:
4194 LASSERT(doc_buffer, break);
4195 doc_buffer->requireFreshStart(true);
4196 dr.setMessage(_("Buffer export reset."));
4199 case LFUN_BUFFER_WRITE:
4200 LASSERT(doc_buffer, break);
4201 saveBuffer(*doc_buffer);
4204 case LFUN_BUFFER_WRITE_AS:
4205 LASSERT(doc_buffer, break);
4206 renameBuffer(*doc_buffer, cmd.argument());
4209 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4210 LASSERT(doc_buffer, break);
4211 renameBuffer(*doc_buffer, cmd.argument(),
4212 LV_WRITE_AS_TEMPLATE);
4215 case LFUN_BUFFER_WRITE_ALL: {
4216 Buffer * first = theBufferList().first();
4219 message(_("Saving all documents..."));
4220 // We cannot use a for loop as the buffer list cycles.
4223 if (!b->isClean()) {
4225 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4227 b = theBufferList().next(b);
4228 } while (b != first);
4229 dr.setMessage(_("All documents saved."));
4233 case LFUN_MASTER_BUFFER_FORALL: {
4237 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4238 funcToRun.allowAsync(false);
4240 for (Buffer const * buf : doc_buffer->allRelatives()) {
4241 // Switch to other buffer view and resend cmd
4242 lyx::dispatch(FuncRequest(
4243 LFUN_BUFFER_SWITCH, buf->absFileName()));
4244 lyx::dispatch(funcToRun);
4247 lyx::dispatch(FuncRequest(
4248 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4252 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4253 LASSERT(doc_buffer, break);
4254 doc_buffer->clearExternalModification();
4257 case LFUN_BUFFER_CLOSE:
4261 case LFUN_BUFFER_CLOSE_ALL:
4265 case LFUN_DEVEL_MODE_TOGGLE:
4266 devel_mode_ = !devel_mode_;
4268 dr.setMessage(_("Developer mode is now enabled."));
4270 dr.setMessage(_("Developer mode is now disabled."));
4273 case LFUN_TOOLBAR_TOGGLE: {
4274 string const name = cmd.getArg(0);
4275 if (GuiToolbar * t = toolbar(name))
4280 case LFUN_TOOLBAR_MOVABLE: {
4281 string const name = cmd.getArg(0);
4283 // toggle (all) toolbars movablility
4284 toolbarsMovable_ = !toolbarsMovable_;
4285 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4286 GuiToolbar * tb = toolbar(ti.name);
4287 if (tb && tb->isMovable() != toolbarsMovable_)
4288 // toggle toolbar movablity if it does not fit lock
4289 // (all) toolbars positions state silent = true, since
4290 // status bar notifications are slow
4293 if (toolbarsMovable_)
4294 dr.setMessage(_("Toolbars unlocked."));
4296 dr.setMessage(_("Toolbars locked."));
4297 } else if (GuiToolbar * t = toolbar(name)) {
4298 // toggle current toolbar movablity
4300 // update lock (all) toolbars positions
4301 updateLockToolbars();
4306 case LFUN_ICON_SIZE: {
4307 QSize size = d.iconSize(cmd.argument());
4309 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4310 size.width(), size.height()));
4314 case LFUN_DIALOG_UPDATE: {
4315 string const name = to_utf8(cmd.argument());
4316 if (name == "prefs" || name == "document")
4317 updateDialog(name, string());
4318 else if (name == "paragraph")
4319 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4320 else if (currentBufferView()) {
4321 Inset * inset = currentBufferView()->editedInset(name);
4322 // Can only update a dialog connected to an existing inset
4324 // FIXME: get rid of this indirection; GuiView ask the inset
4325 // if he is kind enough to update itself...
4326 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4327 //FIXME: pass DispatchResult here?
4328 inset->dispatch(currentBufferView()->cursor(), fr);
4334 case LFUN_DIALOG_TOGGLE: {
4335 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4336 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4337 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4341 case LFUN_DIALOG_DISCONNECT_INSET:
4342 disconnectDialog(to_utf8(cmd.argument()));
4345 case LFUN_DIALOG_HIDE: {
4346 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4350 case LFUN_DIALOG_SHOW: {
4351 string const name = cmd.getArg(0);
4352 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4354 if (name == "latexlog") {
4355 // getStatus checks that
4356 LASSERT(doc_buffer, break);
4357 Buffer::LogType type;
4358 string const logfile = doc_buffer->logName(&type);
4360 case Buffer::latexlog:
4363 case Buffer::buildlog:
4364 sdata = "literate ";
4367 sdata += Lexer::quoteString(logfile);
4368 showDialog("log", sdata);
4369 } else if (name == "vclog") {
4370 // getStatus checks that
4371 LASSERT(doc_buffer, break);
4372 string const sdata2 = "vc " +
4373 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4374 showDialog("log", sdata2);
4375 } else if (name == "symbols") {
4376 sdata = bv->cursor().getEncoding()->name();
4378 showDialog("symbols", sdata);
4380 } else if (name == "prefs" && isFullScreen()) {
4381 lfunUiToggle("fullscreen");
4382 showDialog("prefs", sdata);
4384 showDialog(name, sdata);
4389 dr.setMessage(cmd.argument());
4392 case LFUN_UI_TOGGLE: {
4393 string arg = cmd.getArg(0);
4394 if (!lfunUiToggle(arg)) {
4395 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4396 dr.setMessage(bformat(msg, from_utf8(arg)));
4398 // Make sure the keyboard focus stays in the work area.
4403 case LFUN_VIEW_SPLIT: {
4404 LASSERT(doc_buffer, break);
4405 string const orientation = cmd.getArg(0);
4406 d.splitter_->setOrientation(orientation == "vertical"
4407 ? Qt::Vertical : Qt::Horizontal);
4408 TabWorkArea * twa = addTabWorkArea();
4409 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4410 setCurrentWorkArea(wa);
4413 case LFUN_TAB_GROUP_CLOSE:
4414 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4415 closeTabWorkArea(twa);
4416 d.current_work_area_ = nullptr;
4417 twa = d.currentTabWorkArea();
4418 // Switch to the next GuiWorkArea in the found TabWorkArea.
4420 // Make sure the work area is up to date.
4421 setCurrentWorkArea(twa->currentWorkArea());
4423 setCurrentWorkArea(nullptr);
4428 case LFUN_VIEW_CLOSE:
4429 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4430 closeWorkArea(twa->currentWorkArea());
4431 d.current_work_area_ = nullptr;
4432 twa = d.currentTabWorkArea();
4433 // Switch to the next GuiWorkArea in the found TabWorkArea.
4435 // Make sure the work area is up to date.
4436 setCurrentWorkArea(twa->currentWorkArea());
4438 setCurrentWorkArea(nullptr);
4443 case LFUN_COMPLETION_INLINE:
4444 if (d.current_work_area_)
4445 d.current_work_area_->completer().showInline();
4448 case LFUN_COMPLETION_POPUP:
4449 if (d.current_work_area_)
4450 d.current_work_area_->completer().showPopup();
4455 if (d.current_work_area_)
4456 d.current_work_area_->completer().tab();
4459 case LFUN_COMPLETION_CANCEL:
4460 if (d.current_work_area_) {
4461 if (d.current_work_area_->completer().popupVisible())
4462 d.current_work_area_->completer().hidePopup();
4464 d.current_work_area_->completer().hideInline();
4468 case LFUN_COMPLETION_ACCEPT:
4469 if (d.current_work_area_)
4470 d.current_work_area_->completer().activate();
4473 case LFUN_BUFFER_ZOOM_IN:
4474 case LFUN_BUFFER_ZOOM_OUT:
4475 case LFUN_BUFFER_ZOOM: {
4476 if (cmd.argument().empty()) {
4477 if (cmd.action() == LFUN_BUFFER_ZOOM)
4479 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4484 if (cmd.action() == LFUN_BUFFER_ZOOM)
4485 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4486 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4487 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4489 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4492 // Actual zoom value: default zoom + fractional extra value
4493 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4494 if (zoom < static_cast<int>(zoom_min_))
4497 lyxrc.currentZoom = zoom;
4499 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4500 lyxrc.currentZoom, lyxrc.defaultZoom));
4502 guiApp->fontLoader().update();
4503 dr.screenUpdate(Update::Force | Update::FitCursor);
4507 case LFUN_VC_REGISTER:
4508 case LFUN_VC_RENAME:
4510 case LFUN_VC_CHECK_IN:
4511 case LFUN_VC_CHECK_OUT:
4512 case LFUN_VC_REPO_UPDATE:
4513 case LFUN_VC_LOCKING_TOGGLE:
4514 case LFUN_VC_REVERT:
4515 case LFUN_VC_UNDO_LAST:
4516 case LFUN_VC_COMMAND:
4517 case LFUN_VC_COMPARE:
4518 dispatchVC(cmd, dr);
4521 case LFUN_SERVER_GOTO_FILE_ROW:
4522 if(goToFileRow(to_utf8(cmd.argument())))
4523 dr.screenUpdate(Update::Force | Update::FitCursor);
4526 case LFUN_LYX_ACTIVATE:
4530 case LFUN_WINDOW_RAISE:
4536 case LFUN_FORWARD_SEARCH: {
4537 // it seems safe to assume we have a document buffer, since
4538 // getStatus wants one.
4539 LASSERT(doc_buffer, break);
4540 Buffer const * doc_master = doc_buffer->masterBuffer();
4541 FileName const path(doc_master->temppath());
4542 string const texname = doc_master->isChild(doc_buffer)
4543 ? DocFileName(changeExtension(
4544 doc_buffer->absFileName(),
4545 "tex")).mangledFileName()
4546 : doc_buffer->latexName();
4547 string const fulltexname =
4548 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4549 string const mastername =
4550 removeExtension(doc_master->latexName());
4551 FileName const dviname(addName(path.absFileName(),
4552 addExtension(mastername, "dvi")));
4553 FileName const pdfname(addName(path.absFileName(),
4554 addExtension(mastername, "pdf")));
4555 bool const have_dvi = dviname.exists();
4556 bool const have_pdf = pdfname.exists();
4557 if (!have_dvi && !have_pdf) {
4558 dr.setMessage(_("Please, preview the document first."));
4561 string outname = dviname.onlyFileName();
4562 string command = lyxrc.forward_search_dvi;
4563 if (!have_dvi || (have_pdf &&
4564 pdfname.lastModified() > dviname.lastModified())) {
4565 outname = pdfname.onlyFileName();
4566 command = lyxrc.forward_search_pdf;
4569 DocIterator cur = bv->cursor();
4570 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4571 LYXERR(Debug::ACTION, "Forward search: row:" << row
4573 if (row == -1 || command.empty()) {
4574 dr.setMessage(_("Couldn't proceed."));
4577 string texrow = convert<string>(row);
4579 command = subst(command, "$$n", texrow);
4580 command = subst(command, "$$f", fulltexname);
4581 command = subst(command, "$$t", texname);
4582 command = subst(command, "$$o", outname);
4584 volatile PathChanger p(path);
4586 one.startscript(Systemcall::DontWait, command);
4590 case LFUN_SPELLING_CONTINUOUSLY:
4591 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4592 dr.screenUpdate(Update::Force);
4595 case LFUN_CITATION_OPEN: {
4597 if (theFormats().getFormat("pdf"))
4598 pdfv = theFormats().getFormat("pdf")->viewer();
4599 if (theFormats().getFormat("ps"))
4600 psv = theFormats().getFormat("ps")->viewer();
4601 frontend::showTarget(argument, pdfv, psv);
4606 // The LFUN must be for one of BufferView, Buffer or Cursor;
4608 dispatchToBufferView(cmd, dr);
4612 // Part of automatic menu appearance feature.
4613 if (isFullScreen()) {
4614 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4618 // Need to update bv because many LFUNs here might have destroyed it
4619 bv = currentBufferView();
4621 // Clear non-empty selections
4622 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4624 Cursor & cur = bv->cursor();
4625 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4626 cur.clearSelection();
4632 bool GuiView::lfunUiToggle(string const & ui_component)
4634 if (ui_component == "scrollbar") {
4635 // hide() is of no help
4636 if (d.current_work_area_->verticalScrollBarPolicy() ==
4637 Qt::ScrollBarAlwaysOff)
4639 d.current_work_area_->setVerticalScrollBarPolicy(
4640 Qt::ScrollBarAsNeeded);
4642 d.current_work_area_->setVerticalScrollBarPolicy(
4643 Qt::ScrollBarAlwaysOff);
4644 } else if (ui_component == "statusbar") {
4645 statusBar()->setVisible(!statusBar()->isVisible());
4646 } else if (ui_component == "menubar") {
4647 menuBar()->setVisible(!menuBar()->isVisible());
4649 if (ui_component == "frame") {
4650 int const l = contentsMargins().left();
4652 //are the frames in default state?
4653 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4655 setContentsMargins(-2, -2, -2, -2);
4657 setContentsMargins(0, 0, 0, 0);
4660 if (ui_component == "fullscreen") {
4668 void GuiView::toggleFullScreen()
4670 setWindowState(windowState() ^ Qt::WindowFullScreen);
4674 Buffer const * GuiView::updateInset(Inset const * inset)
4679 Buffer const * inset_buffer = &(inset->buffer());
4681 for (int i = 0; i != d.splitter_->count(); ++i) {
4682 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4685 Buffer const * buffer = &(wa->bufferView().buffer());
4686 if (inset_buffer == buffer)
4687 wa->scheduleRedraw(true);
4689 return inset_buffer;
4693 void GuiView::restartCaret()
4695 /* When we move around, or type, it's nice to be able to see
4696 * the caret immediately after the keypress.
4698 if (d.current_work_area_)
4699 d.current_work_area_->startBlinkingCaret();
4701 // Take this occasion to update the other GUI elements.
4707 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4709 if (d.current_work_area_)
4710 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4715 // This list should be kept in sync with the list of insets in
4716 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4717 // dialog should have the same name as the inset.
4718 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4719 // docs in LyXAction.cpp.
4721 char const * const dialognames[] = {
4723 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4724 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4725 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4726 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4727 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4728 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4729 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4730 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4732 char const * const * const end_dialognames =
4733 dialognames + (sizeof(dialognames) / sizeof(char *));
4737 cmpCStr(char const * name) : name_(name) {}
4738 bool operator()(char const * other) {
4739 return strcmp(other, name_) == 0;
4746 bool isValidName(string const & name)
4748 return find_if(dialognames, end_dialognames,
4749 cmpCStr(name.c_str())) != end_dialognames;
4755 void GuiView::resetDialogs()
4757 // Make sure that no LFUN uses any GuiView.
4758 guiApp->setCurrentView(nullptr);
4762 constructToolbars();
4763 guiApp->menus().fillMenuBar(menuBar(), this, false);
4764 d.layout_->updateContents(true);
4765 // Now update controls with current buffer.
4766 guiApp->setCurrentView(this);
4772 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4774 for (QObject * child: widget->children()) {
4775 if (child->inherits("QGroupBox")) {
4776 QGroupBox * box = (QGroupBox*) child;
4779 flatGroupBoxes(child, flag);
4785 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4787 if (!isValidName(name))
4790 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4792 if (it != d.dialogs_.end()) {
4794 it->second->hideView();
4795 return it->second.get();
4798 Dialog * dialog = build(name);
4799 d.dialogs_[name].reset(dialog);
4800 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4801 // Force a uniform style for group boxes
4802 // On Mac non-flat works better, on Linux flat is standard
4803 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4805 if (lyxrc.allow_geometry_session)
4806 dialog->restoreSession();
4813 void GuiView::showDialog(string const & name, string const & sdata,
4816 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4820 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4826 const string name = fromqstr(qname);
4827 const string sdata = fromqstr(qdata);
4831 Dialog * dialog = findOrBuild(name, false);
4833 bool const visible = dialog->isVisibleView();
4834 dialog->showData(sdata);
4835 if (currentBufferView())
4836 currentBufferView()->editInset(name, inset);
4837 // We only set the focus to the new dialog if it was not yet
4838 // visible in order not to change the existing previous behaviour
4840 // activateWindow is needed for floating dockviews
4841 dialog->asQWidget()->raise();
4842 dialog->asQWidget()->activateWindow();
4843 dialog->asQWidget()->setFocus();
4847 catch (ExceptionMessage const &) {
4855 bool GuiView::isDialogVisible(string const & name) const
4857 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4858 if (it == d.dialogs_.end())
4860 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4864 void GuiView::hideDialog(string const & name, Inset * inset)
4866 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4867 if (it == d.dialogs_.end())
4871 if (!currentBufferView())
4873 if (inset != currentBufferView()->editedInset(name))
4877 Dialog * const dialog = it->second.get();
4878 if (dialog->isVisibleView())
4880 if (currentBufferView())
4881 currentBufferView()->editInset(name, nullptr);
4885 void GuiView::disconnectDialog(string const & name)
4887 if (!isValidName(name))
4889 if (currentBufferView())
4890 currentBufferView()->editInset(name, nullptr);
4894 void GuiView::hideAll() const
4896 for(auto const & dlg_p : d.dialogs_)
4897 dlg_p.second->hideView();
4901 void GuiView::updateDialogs()
4903 for(auto const & dlg_p : d.dialogs_) {
4904 Dialog * dialog = dlg_p.second.get();
4906 if (dialog->needBufferOpen() && !documentBufferView())
4907 hideDialog(fromqstr(dialog->name()), nullptr);
4908 else if (dialog->isVisibleView())
4909 dialog->checkStatus();
4916 Dialog * createDialog(GuiView & lv, string const & name);
4918 // will be replaced by a proper factory...
4919 Dialog * createGuiAbout(GuiView & lv);
4920 Dialog * createGuiBibtex(GuiView & lv);
4921 Dialog * createGuiChanges(GuiView & lv);
4922 Dialog * createGuiCharacter(GuiView & lv);
4923 Dialog * createGuiCitation(GuiView & lv);
4924 Dialog * createGuiCompare(GuiView & lv);
4925 Dialog * createGuiCompareHistory(GuiView & lv);
4926 Dialog * createGuiDelimiter(GuiView & lv);
4927 Dialog * createGuiDocument(GuiView & lv);
4928 Dialog * createGuiErrorList(GuiView & lv);
4929 Dialog * createGuiExternal(GuiView & lv);
4930 Dialog * createGuiGraphics(GuiView & lv);
4931 Dialog * createGuiInclude(GuiView & lv);
4932 Dialog * createGuiIndex(GuiView & lv);
4933 Dialog * createGuiListings(GuiView & lv);
4934 Dialog * createGuiLog(GuiView & lv);
4935 Dialog * createGuiLyXFiles(GuiView & lv);
4936 Dialog * createGuiMathMatrix(GuiView & lv);
4937 Dialog * createGuiNote(GuiView & lv);
4938 Dialog * createGuiParagraph(GuiView & lv);
4939 Dialog * createGuiPhantom(GuiView & lv);
4940 Dialog * createGuiPreferences(GuiView & lv);
4941 Dialog * createGuiPrint(GuiView & lv);
4942 Dialog * createGuiPrintindex(GuiView & lv);
4943 Dialog * createGuiRef(GuiView & lv);
4944 Dialog * createGuiSearch(GuiView & lv);
4945 Dialog * createGuiSearchAdv(GuiView & lv);
4946 Dialog * createGuiSendTo(GuiView & lv);
4947 Dialog * createGuiShowFile(GuiView & lv);
4948 Dialog * createGuiSpellchecker(GuiView & lv);
4949 Dialog * createGuiSymbols(GuiView & lv);
4950 Dialog * createGuiTabularCreate(GuiView & lv);
4951 Dialog * createGuiTexInfo(GuiView & lv);
4952 Dialog * createGuiToc(GuiView & lv);
4953 Dialog * createGuiThesaurus(GuiView & lv);
4954 Dialog * createGuiViewSource(GuiView & lv);
4955 Dialog * createGuiWrap(GuiView & lv);
4956 Dialog * createGuiProgressView(GuiView & lv);
4960 Dialog * GuiView::build(string const & name)
4962 LASSERT(isValidName(name), return nullptr);
4964 Dialog * dialog = createDialog(*this, name);
4968 if (name == "aboutlyx")
4969 return createGuiAbout(*this);
4970 if (name == "bibtex")
4971 return createGuiBibtex(*this);
4972 if (name == "changes")
4973 return createGuiChanges(*this);
4974 if (name == "character")
4975 return createGuiCharacter(*this);
4976 if (name == "citation")
4977 return createGuiCitation(*this);
4978 if (name == "compare")
4979 return createGuiCompare(*this);
4980 if (name == "comparehistory")
4981 return createGuiCompareHistory(*this);
4982 if (name == "document")
4983 return createGuiDocument(*this);
4984 if (name == "errorlist")
4985 return createGuiErrorList(*this);
4986 if (name == "external")
4987 return createGuiExternal(*this);
4989 return createGuiShowFile(*this);
4990 if (name == "findreplace")
4991 return createGuiSearch(*this);
4992 if (name == "findreplaceadv")
4993 return createGuiSearchAdv(*this);
4994 if (name == "graphics")
4995 return createGuiGraphics(*this);
4996 if (name == "include")
4997 return createGuiInclude(*this);
4998 if (name == "index")
4999 return createGuiIndex(*this);
5000 if (name == "index_print")
5001 return createGuiPrintindex(*this);
5002 if (name == "listings")
5003 return createGuiListings(*this);
5005 return createGuiLog(*this);
5006 if (name == "lyxfiles")
5007 return createGuiLyXFiles(*this);
5008 if (name == "mathdelimiter")
5009 return createGuiDelimiter(*this);
5010 if (name == "mathmatrix")
5011 return createGuiMathMatrix(*this);
5013 return createGuiNote(*this);
5014 if (name == "paragraph")
5015 return createGuiParagraph(*this);
5016 if (name == "phantom")
5017 return createGuiPhantom(*this);
5018 if (name == "prefs")
5019 return createGuiPreferences(*this);
5021 return createGuiRef(*this);
5022 if (name == "sendto")
5023 return createGuiSendTo(*this);
5024 if (name == "spellchecker")
5025 return createGuiSpellchecker(*this);
5026 if (name == "symbols")
5027 return createGuiSymbols(*this);
5028 if (name == "tabularcreate")
5029 return createGuiTabularCreate(*this);
5030 if (name == "texinfo")
5031 return createGuiTexInfo(*this);
5032 if (name == "thesaurus")
5033 return createGuiThesaurus(*this);
5035 return createGuiToc(*this);
5036 if (name == "view-source")
5037 return createGuiViewSource(*this);
5039 return createGuiWrap(*this);
5040 if (name == "progress")
5041 return createGuiProgressView(*this);
5047 SEMenu::SEMenu(QWidget * parent)
5049 QAction * action = addAction(qt_("Disable Shell Escape"));
5050 connect(action, SIGNAL(triggered()),
5051 parent, SLOT(disableShellEscape()));
5054 } // namespace frontend
5057 #include "moc_GuiView.cpp"