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 #if QT_VERSION > 0x050903
1403 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1404 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1406 setContentsMargins(-2, -2, -2, -2);
1408 hideDialogs("prefs", nullptr);
1409 } else if (ofstate && !nfstate) {
1410 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1411 // switch back from full-screen state
1412 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1413 statusBar()->show();
1414 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1416 if (lyxrc.full_screen_toolbars) {
1417 for (auto const & tb_p : d.toolbars_)
1418 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1419 tb_p.second->show();
1422 for (int i = 0; i != d.splitter_->count(); ++i)
1423 d.tabWorkArea(i)->setFullScreen(false);
1424 #if QT_VERSION > 0x050903
1425 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1427 setContentsMargins(0, 0, 0, 0);
1431 case QEvent::WindowActivate: {
1432 GuiView * old_view = guiApp->currentView();
1433 if (this == old_view) {
1435 return QMainWindow::event(e);
1437 if (old_view && old_view->currentBufferView()) {
1438 // save current selection to the selection buffer to allow
1439 // middle-button paste in this window.
1440 cap::saveSelection(old_view->currentBufferView()->cursor());
1442 guiApp->setCurrentView(this);
1443 if (d.current_work_area_)
1444 on_currentWorkAreaChanged(d.current_work_area_);
1448 return QMainWindow::event(e);
1451 case QEvent::ShortcutOverride: {
1453 if (isFullScreen() && menuBar()->isHidden()) {
1454 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1455 // FIXME: we should also try to detect special LyX shortcut such as
1456 // Alt-P and Alt-M. Right now there is a hack in
1457 // GuiWorkArea::processKeySym() that hides again the menubar for
1459 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1461 return QMainWindow::event(e);
1464 return QMainWindow::event(e);
1468 return QMainWindow::event(e);
1472 void GuiView::resetWindowTitle()
1474 setWindowTitle(qt_("LyX"));
1477 bool GuiView::focusNextPrevChild(bool /*next*/)
1484 bool GuiView::busy() const
1490 void GuiView::setBusy(bool busy)
1492 bool const busy_before = busy_ > 0;
1493 busy ? ++busy_ : --busy_;
1494 if ((busy_ > 0) == busy_before)
1495 // busy state didn't change
1499 QApplication::setOverrideCursor(Qt::WaitCursor);
1502 QApplication::restoreOverrideCursor();
1507 void GuiView::resetCommandExecute()
1509 command_execute_ = false;
1514 double GuiView::pixelRatio() const
1516 #if QT_VERSION >= 0x050000
1517 return qt_scale_factor * devicePixelRatio();
1524 GuiWorkArea * GuiView::workArea(int index)
1526 if (TabWorkArea * twa = d.currentTabWorkArea())
1527 if (index < twa->count())
1528 return twa->workArea(index);
1533 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1535 if (currentWorkArea()
1536 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1537 return currentWorkArea();
1538 if (TabWorkArea * twa = d.currentTabWorkArea())
1539 return twa->workArea(buffer);
1544 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1546 // Automatically create a TabWorkArea if there are none yet.
1547 TabWorkArea * tab_widget = d.splitter_->count()
1548 ? d.currentTabWorkArea() : addTabWorkArea();
1549 return tab_widget->addWorkArea(buffer, *this);
1553 TabWorkArea * GuiView::addTabWorkArea()
1555 TabWorkArea * twa = new TabWorkArea;
1556 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1557 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1558 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1559 this, SLOT(on_lastWorkAreaRemoved()));
1561 d.splitter_->addWidget(twa);
1562 d.stack_widget_->setCurrentWidget(d.splitter_);
1567 GuiWorkArea const * GuiView::currentWorkArea() const
1569 return d.current_work_area_;
1573 GuiWorkArea * GuiView::currentWorkArea()
1575 return d.current_work_area_;
1579 GuiWorkArea const * GuiView::currentMainWorkArea() const
1581 if (!d.currentTabWorkArea())
1583 return d.currentTabWorkArea()->currentWorkArea();
1587 GuiWorkArea * GuiView::currentMainWorkArea()
1589 if (!d.currentTabWorkArea())
1591 return d.currentTabWorkArea()->currentWorkArea();
1595 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1597 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1599 d.current_work_area_ = nullptr;
1601 Q_EMIT bufferViewChanged();
1605 // FIXME: I've no clue why this is here and why it accesses
1606 // theGuiApp()->currentView, which might be 0 (bug 6464).
1607 // See also 27525 (vfr).
1608 if (theGuiApp()->currentView() == this
1609 && theGuiApp()->currentView()->currentWorkArea() == wa)
1612 if (currentBufferView())
1613 cap::saveSelection(currentBufferView()->cursor());
1615 theGuiApp()->setCurrentView(this);
1616 d.current_work_area_ = wa;
1618 // We need to reset this now, because it will need to be
1619 // right if the tabWorkArea gets reset in the for loop. We
1620 // will change it back if we aren't in that case.
1621 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1622 d.current_main_work_area_ = wa;
1624 for (int i = 0; i != d.splitter_->count(); ++i) {
1625 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1626 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1627 << ", Current main wa: " << currentMainWorkArea());
1632 d.current_main_work_area_ = old_cmwa;
1634 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1635 on_currentWorkAreaChanged(wa);
1636 BufferView & bv = wa->bufferView();
1637 bv.cursor().fixIfBroken();
1639 wa->setUpdatesEnabled(true);
1640 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1644 void GuiView::removeWorkArea(GuiWorkArea * wa)
1646 LASSERT(wa, return);
1647 if (wa == d.current_work_area_) {
1649 disconnectBufferView();
1650 d.current_work_area_ = nullptr;
1651 d.current_main_work_area_ = nullptr;
1654 bool found_twa = false;
1655 for (int i = 0; i != d.splitter_->count(); ++i) {
1656 TabWorkArea * twa = d.tabWorkArea(i);
1657 if (twa->removeWorkArea(wa)) {
1658 // Found in this tab group, and deleted the GuiWorkArea.
1660 if (twa->count() != 0) {
1661 if (d.current_work_area_ == nullptr)
1662 // This means that we are closing the current GuiWorkArea, so
1663 // switch to the next GuiWorkArea in the found TabWorkArea.
1664 setCurrentWorkArea(twa->currentWorkArea());
1666 // No more WorkAreas in this tab group, so delete it.
1673 // It is not a tabbed work area (i.e., the search work area), so it
1674 // should be deleted by other means.
1675 LASSERT(found_twa, return);
1677 if (d.current_work_area_ == nullptr) {
1678 if (d.splitter_->count() != 0) {
1679 TabWorkArea * twa = d.currentTabWorkArea();
1680 setCurrentWorkArea(twa->currentWorkArea());
1682 // No more work areas, switch to the background widget.
1683 setCurrentWorkArea(nullptr);
1689 LayoutBox * GuiView::getLayoutDialog() const
1695 void GuiView::updateLayoutList()
1698 d.layout_->updateContents(false);
1702 void GuiView::updateToolbars()
1704 if (d.current_work_area_) {
1706 if (d.current_work_area_->bufferView().cursor().inMathed()
1707 && !d.current_work_area_->bufferView().cursor().inRegexped())
1708 context |= Toolbars::MATH;
1709 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1710 context |= Toolbars::TABLE;
1711 if (currentBufferView()->buffer().areChangesPresent()
1712 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1713 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1714 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1715 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1716 context |= Toolbars::REVIEW;
1717 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1718 context |= Toolbars::MATHMACROTEMPLATE;
1719 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1720 context |= Toolbars::IPA;
1721 if (command_execute_)
1722 context |= Toolbars::MINIBUFFER;
1723 if (minibuffer_focus_) {
1724 context |= Toolbars::MINIBUFFER_FOCUS;
1725 minibuffer_focus_ = false;
1728 for (auto const & tb_p : d.toolbars_)
1729 tb_p.second->update(context);
1731 for (auto const & tb_p : d.toolbars_)
1732 tb_p.second->update();
1736 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1738 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1739 LASSERT(newBuffer, return);
1741 GuiWorkArea * wa = workArea(*newBuffer);
1742 if (wa == nullptr) {
1744 newBuffer->masterBuffer()->updateBuffer();
1746 wa = addWorkArea(*newBuffer);
1747 // scroll to the position when the BufferView was last closed
1748 if (lyxrc.use_lastfilepos) {
1749 LastFilePosSection::FilePos filepos =
1750 theSession().lastFilePos().load(newBuffer->fileName());
1751 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1754 //Disconnect the old buffer...there's no new one.
1757 connectBuffer(*newBuffer);
1758 connectBufferView(wa->bufferView());
1760 setCurrentWorkArea(wa);
1764 void GuiView::connectBuffer(Buffer & buf)
1766 buf.setGuiDelegate(this);
1770 void GuiView::disconnectBuffer()
1772 if (d.current_work_area_)
1773 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1777 void GuiView::connectBufferView(BufferView & bv)
1779 bv.setGuiDelegate(this);
1783 void GuiView::disconnectBufferView()
1785 if (d.current_work_area_)
1786 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1790 void GuiView::errors(string const & error_type, bool from_master)
1792 BufferView const * const bv = currentBufferView();
1796 ErrorList const & el = from_master ?
1797 bv->buffer().masterBuffer()->errorList(error_type) :
1798 bv->buffer().errorList(error_type);
1803 string err = error_type;
1805 err = "from_master|" + error_type;
1806 showDialog("errorlist", err);
1810 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1812 d.toc_models_.updateItem(toqstr(type), dit);
1816 void GuiView::structureChanged()
1818 // This is called from the Buffer, which has no way to ensure that cursors
1819 // in BufferView remain valid.
1820 if (documentBufferView())
1821 documentBufferView()->cursor().sanitize();
1822 // FIXME: This is slightly expensive, though less than the tocBackend update
1823 // (#9880). This also resets the view in the Toc Widget (#6675).
1824 d.toc_models_.reset(documentBufferView());
1825 // Navigator needs more than a simple update in this case. It needs to be
1827 updateDialog("toc", "");
1831 void GuiView::updateDialog(string const & name, string const & sdata)
1833 if (!isDialogVisible(name))
1836 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1837 if (it == d.dialogs_.end())
1840 Dialog * const dialog = it->second.get();
1841 if (dialog->isVisibleView())
1842 dialog->initialiseParams(sdata);
1846 BufferView * GuiView::documentBufferView()
1848 return currentMainWorkArea()
1849 ? ¤tMainWorkArea()->bufferView()
1854 BufferView const * GuiView::documentBufferView() const
1856 return currentMainWorkArea()
1857 ? ¤tMainWorkArea()->bufferView()
1862 BufferView * GuiView::currentBufferView()
1864 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1868 BufferView const * GuiView::currentBufferView() const
1870 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1874 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1875 Buffer const * orig, Buffer * clone)
1877 bool const success = clone->autoSave();
1879 busyBuffers.remove(orig);
1881 ? _("Automatic save done.")
1882 : _("Automatic save failed!");
1886 void GuiView::autoSave()
1888 LYXERR(Debug::INFO, "Running autoSave()");
1890 Buffer * buffer = documentBufferView()
1891 ? &documentBufferView()->buffer() : nullptr;
1893 resetAutosaveTimers();
1897 GuiViewPrivate::busyBuffers.insert(buffer);
1898 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1899 buffer, buffer->cloneBufferOnly());
1900 d.autosave_watcher_.setFuture(f);
1901 resetAutosaveTimers();
1905 void GuiView::resetAutosaveTimers()
1908 d.autosave_timeout_.restart();
1912 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1915 Buffer * buf = currentBufferView()
1916 ? ¤tBufferView()->buffer() : nullptr;
1917 Buffer * doc_buffer = documentBufferView()
1918 ? &(documentBufferView()->buffer()) : nullptr;
1921 /* In LyX/Mac, when a dialog is open, the menus of the
1922 application can still be accessed without giving focus to
1923 the main window. In this case, we want to disable the menu
1924 entries that are buffer-related.
1925 This code must not be used on Linux and Windows, since it
1926 would disable buffer-related entries when hovering over the
1927 menu (see bug #9574).
1929 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1935 // Check whether we need a buffer
1936 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1937 // no, exit directly
1938 flag.message(from_utf8(N_("Command not allowed with"
1939 "out any document open")));
1940 flag.setEnabled(false);
1944 if (cmd.origin() == FuncRequest::TOC) {
1945 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1946 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1947 flag.setEnabled(false);
1951 switch(cmd.action()) {
1952 case LFUN_BUFFER_IMPORT:
1955 case LFUN_MASTER_BUFFER_EXPORT:
1957 && (doc_buffer->parent() != nullptr
1958 || doc_buffer->hasChildren())
1959 && !d.processing_thread_watcher_.isRunning()
1960 // this launches a dialog, which would be in the wrong Buffer
1961 && !(::lyx::operator==(cmd.argument(), "custom"));
1964 case LFUN_MASTER_BUFFER_UPDATE:
1965 case LFUN_MASTER_BUFFER_VIEW:
1967 && (doc_buffer->parent() != nullptr
1968 || doc_buffer->hasChildren())
1969 && !d.processing_thread_watcher_.isRunning();
1972 case LFUN_BUFFER_UPDATE:
1973 case LFUN_BUFFER_VIEW: {
1974 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1978 string format = to_utf8(cmd.argument());
1979 if (cmd.argument().empty())
1980 format = doc_buffer->params().getDefaultOutputFormat();
1981 enable = doc_buffer->params().isExportable(format, true);
1985 case LFUN_BUFFER_RELOAD:
1986 enable = doc_buffer && !doc_buffer->isUnnamed()
1987 && doc_buffer->fileName().exists()
1988 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1991 case LFUN_BUFFER_RESET_EXPORT:
1992 enable = doc_buffer != nullptr;
1995 case LFUN_BUFFER_CHILD_OPEN:
1996 enable = doc_buffer != nullptr;
1999 case LFUN_MASTER_BUFFER_FORALL: {
2000 if (doc_buffer == nullptr) {
2001 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2005 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2006 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2007 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2012 for (Buffer * buf : doc_buffer->allRelatives()) {
2013 GuiWorkArea * wa = workArea(*buf);
2016 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2017 enable = flag.enabled();
2024 case LFUN_BUFFER_WRITE:
2025 enable = doc_buffer && (doc_buffer->isUnnamed()
2026 || (!doc_buffer->isClean()
2027 || cmd.argument() == "force"));
2030 //FIXME: This LFUN should be moved to GuiApplication.
2031 case LFUN_BUFFER_WRITE_ALL: {
2032 // We enable the command only if there are some modified buffers
2033 Buffer * first = theBufferList().first();
2038 // We cannot use a for loop as the buffer list is a cycle.
2040 if (!b->isClean()) {
2044 b = theBufferList().next(b);
2045 } while (b != first);
2049 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2050 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2053 case LFUN_BUFFER_EXPORT: {
2054 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2058 return doc_buffer->getStatus(cmd, flag);
2061 case LFUN_BUFFER_EXPORT_AS:
2062 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2067 case LFUN_BUFFER_WRITE_AS:
2068 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2069 enable = doc_buffer != nullptr;
2072 case LFUN_EXPORT_CANCEL:
2073 enable = d.processing_thread_watcher_.isRunning();
2076 case LFUN_BUFFER_CLOSE:
2077 case LFUN_VIEW_CLOSE:
2078 enable = doc_buffer != nullptr;
2081 case LFUN_BUFFER_CLOSE_ALL:
2082 enable = theBufferList().last() != theBufferList().first();
2085 case LFUN_BUFFER_CHKTEX: {
2086 // hide if we have no checktex command
2087 if (lyxrc.chktex_command.empty()) {
2088 flag.setUnknown(true);
2092 if (!doc_buffer || !doc_buffer->params().isLatex()
2093 || d.processing_thread_watcher_.isRunning()) {
2094 // grey out, don't hide
2102 case LFUN_VIEW_SPLIT:
2103 if (cmd.getArg(0) == "vertical")
2104 enable = doc_buffer && (d.splitter_->count() == 1 ||
2105 d.splitter_->orientation() == Qt::Vertical);
2107 enable = doc_buffer && (d.splitter_->count() == 1 ||
2108 d.splitter_->orientation() == Qt::Horizontal);
2111 case LFUN_TAB_GROUP_CLOSE:
2112 enable = d.tabWorkAreaCount() > 1;
2115 case LFUN_DEVEL_MODE_TOGGLE:
2116 flag.setOnOff(devel_mode_);
2119 case LFUN_TOOLBAR_TOGGLE: {
2120 string const name = cmd.getArg(0);
2121 if (GuiToolbar * t = toolbar(name))
2122 flag.setOnOff(t->isVisible());
2125 docstring const msg =
2126 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2132 case LFUN_TOOLBAR_MOVABLE: {
2133 string const name = cmd.getArg(0);
2134 // use negation since locked == !movable
2136 // toolbar name * locks all toolbars
2137 flag.setOnOff(!toolbarsMovable_);
2138 else if (GuiToolbar * t = toolbar(name))
2139 flag.setOnOff(!(t->isMovable()));
2142 docstring const msg =
2143 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2149 case LFUN_ICON_SIZE:
2150 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2153 case LFUN_DROP_LAYOUTS_CHOICE:
2154 enable = buf != nullptr;
2157 case LFUN_UI_TOGGLE:
2158 flag.setOnOff(isFullScreen());
2161 case LFUN_DIALOG_DISCONNECT_INSET:
2164 case LFUN_DIALOG_HIDE:
2165 // FIXME: should we check if the dialog is shown?
2168 case LFUN_DIALOG_TOGGLE:
2169 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2172 case LFUN_DIALOG_SHOW: {
2173 string const name = cmd.getArg(0);
2175 enable = name == "aboutlyx"
2176 || name == "file" //FIXME: should be removed.
2177 || name == "lyxfiles"
2179 || name == "texinfo"
2180 || name == "progress"
2181 || name == "compare";
2182 else if (name == "character" || name == "symbols"
2183 || name == "mathdelimiter" || name == "mathmatrix") {
2184 if (!buf || buf->isReadonly())
2187 Cursor const & cur = currentBufferView()->cursor();
2188 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2191 else if (name == "latexlog")
2192 enable = FileName(doc_buffer->logName()).isReadableFile();
2193 else if (name == "spellchecker")
2194 enable = theSpellChecker()
2195 && !doc_buffer->isReadonly()
2196 && !doc_buffer->text().empty();
2197 else if (name == "vclog")
2198 enable = doc_buffer->lyxvc().inUse();
2202 case LFUN_DIALOG_UPDATE: {
2203 string const name = cmd.getArg(0);
2205 enable = name == "prefs";
2209 case LFUN_COMMAND_EXECUTE:
2211 case LFUN_MENU_OPEN:
2212 // Nothing to check.
2215 case LFUN_COMPLETION_INLINE:
2216 if (!d.current_work_area_
2217 || !d.current_work_area_->completer().inlinePossible(
2218 currentBufferView()->cursor()))
2222 case LFUN_COMPLETION_POPUP:
2223 if (!d.current_work_area_
2224 || !d.current_work_area_->completer().popupPossible(
2225 currentBufferView()->cursor()))
2230 if (!d.current_work_area_
2231 || !d.current_work_area_->completer().inlinePossible(
2232 currentBufferView()->cursor()))
2236 case LFUN_COMPLETION_ACCEPT:
2237 if (!d.current_work_area_
2238 || (!d.current_work_area_->completer().popupVisible()
2239 && !d.current_work_area_->completer().inlineVisible()
2240 && !d.current_work_area_->completer().completionAvailable()))
2244 case LFUN_COMPLETION_CANCEL:
2245 if (!d.current_work_area_
2246 || (!d.current_work_area_->completer().popupVisible()
2247 && !d.current_work_area_->completer().inlineVisible()))
2251 case LFUN_BUFFER_ZOOM_OUT:
2252 case LFUN_BUFFER_ZOOM_IN: {
2253 // only diff between these two is that the default for ZOOM_OUT
2255 bool const neg_zoom =
2256 convert<int>(cmd.argument()) < 0 ||
2257 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2258 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2259 docstring const msg =
2260 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2264 enable = doc_buffer;
2268 case LFUN_BUFFER_ZOOM: {
2269 bool const less_than_min_zoom =
2270 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2271 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2272 docstring const msg =
2273 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2278 enable = doc_buffer;
2282 case LFUN_BUFFER_MOVE_NEXT:
2283 case LFUN_BUFFER_MOVE_PREVIOUS:
2284 // we do not cycle when moving
2285 case LFUN_BUFFER_NEXT:
2286 case LFUN_BUFFER_PREVIOUS:
2287 // because we cycle, it doesn't matter whether on first or last
2288 enable = (d.currentTabWorkArea()->count() > 1);
2290 case LFUN_BUFFER_SWITCH:
2291 // toggle on the current buffer, but do not toggle off
2292 // the other ones (is that a good idea?)
2294 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2295 flag.setOnOff(true);
2298 case LFUN_VC_REGISTER:
2299 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2301 case LFUN_VC_RENAME:
2302 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2305 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2307 case LFUN_VC_CHECK_IN:
2308 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2310 case LFUN_VC_CHECK_OUT:
2311 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2313 case LFUN_VC_LOCKING_TOGGLE:
2314 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2315 && doc_buffer->lyxvc().lockingToggleEnabled();
2316 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2318 case LFUN_VC_REVERT:
2319 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2320 && !doc_buffer->hasReadonlyFlag();
2322 case LFUN_VC_UNDO_LAST:
2323 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2325 case LFUN_VC_REPO_UPDATE:
2326 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2328 case LFUN_VC_COMMAND: {
2329 if (cmd.argument().empty())
2331 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2335 case LFUN_VC_COMPARE:
2336 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2339 case LFUN_SERVER_GOTO_FILE_ROW:
2340 case LFUN_LYX_ACTIVATE:
2341 case LFUN_WINDOW_RAISE:
2343 case LFUN_FORWARD_SEARCH:
2344 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2347 case LFUN_FILE_INSERT_PLAINTEXT:
2348 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2349 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2352 case LFUN_SPELLING_CONTINUOUSLY:
2353 flag.setOnOff(lyxrc.spellcheck_continuously);
2356 case LFUN_CITATION_OPEN:
2365 flag.setEnabled(false);
2371 static FileName selectTemplateFile()
2373 FileDialog dlg(qt_("Select template file"));
2374 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2375 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2377 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2378 QStringList(qt_("LyX Documents (*.lyx)")));
2380 if (result.first == FileDialog::Later)
2382 if (result.second.isEmpty())
2384 return FileName(fromqstr(result.second));
2388 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2392 Buffer * newBuffer = nullptr;
2394 newBuffer = checkAndLoadLyXFile(filename);
2395 } catch (ExceptionMessage const &) {
2402 message(_("Document not loaded."));
2406 setBuffer(newBuffer);
2407 newBuffer->errors("Parse");
2410 theSession().lastFiles().add(filename);
2411 theSession().writeFile();
2418 void GuiView::openDocument(string const & fname)
2420 string initpath = lyxrc.document_path;
2422 if (documentBufferView()) {
2423 string const trypath = documentBufferView()->buffer().filePath();
2424 // If directory is writeable, use this as default.
2425 if (FileName(trypath).isDirWritable())
2431 if (fname.empty()) {
2432 FileDialog dlg(qt_("Select document to open"));
2433 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2434 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2436 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2437 FileDialog::Result result =
2438 dlg.open(toqstr(initpath), filter);
2440 if (result.first == FileDialog::Later)
2443 filename = fromqstr(result.second);
2445 // check selected filename
2446 if (filename.empty()) {
2447 message(_("Canceled."));
2453 // get absolute path of file and add ".lyx" to the filename if
2455 FileName const fullname =
2456 fileSearch(string(), filename, "lyx", support::may_not_exist);
2457 if (!fullname.empty())
2458 filename = fullname.absFileName();
2460 if (!fullname.onlyPath().isDirectory()) {
2461 Alert::warning(_("Invalid filename"),
2462 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2463 from_utf8(fullname.absFileName())));
2467 // if the file doesn't exist and isn't already open (bug 6645),
2468 // let the user create one
2469 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2470 !LyXVC::file_not_found_hook(fullname)) {
2471 // the user specifically chose this name. Believe him.
2472 Buffer * const b = newFile(filename, string(), true);
2478 docstring const disp_fn = makeDisplayPath(filename);
2479 message(bformat(_("Opening document %1$s..."), disp_fn));
2482 Buffer * buf = loadDocument(fullname);
2484 str2 = bformat(_("Document %1$s opened."), disp_fn);
2485 if (buf->lyxvc().inUse())
2486 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2487 " " + _("Version control detected.");
2489 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2494 // FIXME: clean that
2495 static bool import(GuiView * lv, FileName const & filename,
2496 string const & format, ErrorList & errorList)
2498 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2500 string loader_format;
2501 vector<string> loaders = theConverters().loaders();
2502 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2503 for (string const & loader : loaders) {
2504 if (!theConverters().isReachable(format, loader))
2507 string const tofile =
2508 support::changeExtension(filename.absFileName(),
2509 theFormats().extension(loader));
2510 if (theConverters().convert(nullptr, filename, FileName(tofile),
2511 filename, format, loader, errorList) != Converters::SUCCESS)
2513 loader_format = loader;
2516 if (loader_format.empty()) {
2517 frontend::Alert::error(_("Couldn't import file"),
2518 bformat(_("No information for importing the format %1$s."),
2519 translateIfPossible(theFormats().prettyName(format))));
2523 loader_format = format;
2525 if (loader_format == "lyx") {
2526 Buffer * buf = lv->loadDocument(lyxfile);
2530 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2534 bool as_paragraphs = loader_format == "textparagraph";
2535 string filename2 = (loader_format == format) ? filename.absFileName()
2536 : support::changeExtension(filename.absFileName(),
2537 theFormats().extension(loader_format));
2538 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2540 guiApp->setCurrentView(lv);
2541 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2548 void GuiView::importDocument(string const & argument)
2551 string filename = split(argument, format, ' ');
2553 LYXERR(Debug::INFO, format << " file: " << filename);
2555 // need user interaction
2556 if (filename.empty()) {
2557 string initpath = lyxrc.document_path;
2558 if (documentBufferView()) {
2559 string const trypath = documentBufferView()->buffer().filePath();
2560 // If directory is writeable, use this as default.
2561 if (FileName(trypath).isDirWritable())
2565 docstring const text = bformat(_("Select %1$s file to import"),
2566 translateIfPossible(theFormats().prettyName(format)));
2568 FileDialog dlg(toqstr(text));
2569 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2570 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2572 docstring filter = translateIfPossible(theFormats().prettyName(format));
2575 filter += from_utf8(theFormats().extensions(format));
2578 FileDialog::Result result =
2579 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2581 if (result.first == FileDialog::Later)
2584 filename = fromqstr(result.second);
2586 // check selected filename
2587 if (filename.empty())
2588 message(_("Canceled."));
2591 if (filename.empty())
2594 // get absolute path of file
2595 FileName const fullname(support::makeAbsPath(filename));
2597 // Can happen if the user entered a path into the dialog
2599 if (fullname.onlyFileName().empty()) {
2600 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2601 "Aborting import."),
2602 from_utf8(fullname.absFileName()));
2603 frontend::Alert::error(_("File name error"), msg);
2604 message(_("Canceled."));
2609 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2611 // Check if the document already is open
2612 Buffer * buf = theBufferList().getBuffer(lyxfile);
2615 if (!closeBuffer()) {
2616 message(_("Canceled."));
2621 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2623 // if the file exists already, and we didn't do
2624 // -i lyx thefile.lyx, warn
2625 if (lyxfile.exists() && fullname != lyxfile) {
2627 docstring text = bformat(_("The document %1$s already exists.\n\n"
2628 "Do you want to overwrite that document?"), displaypath);
2629 int const ret = Alert::prompt(_("Overwrite document?"),
2630 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2633 message(_("Canceled."));
2638 message(bformat(_("Importing %1$s..."), displaypath));
2639 ErrorList errorList;
2640 if (import(this, fullname, format, errorList))
2641 message(_("imported."));
2643 message(_("file not imported!"));
2645 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2649 void GuiView::newDocument(string const & filename, string templatefile,
2652 FileName initpath(lyxrc.document_path);
2653 if (documentBufferView()) {
2654 FileName const trypath(documentBufferView()->buffer().filePath());
2655 // If directory is writeable, use this as default.
2656 if (trypath.isDirWritable())
2660 if (from_template) {
2661 if (templatefile.empty())
2662 templatefile = selectTemplateFile().absFileName();
2663 if (templatefile.empty())
2668 if (filename.empty())
2669 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2671 b = newFile(filename, templatefile, true);
2676 // If no new document could be created, it is unsure
2677 // whether there is a valid BufferView.
2678 if (currentBufferView())
2679 // Ensure the cursor is correctly positioned on screen.
2680 currentBufferView()->showCursor();
2684 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2686 BufferView * bv = documentBufferView();
2691 FileName filename(to_utf8(fname));
2692 if (filename.empty()) {
2693 // Launch a file browser
2695 string initpath = lyxrc.document_path;
2696 string const trypath = bv->buffer().filePath();
2697 // If directory is writeable, use this as default.
2698 if (FileName(trypath).isDirWritable())
2702 FileDialog dlg(qt_("Select LyX document to insert"));
2703 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2704 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2706 FileDialog::Result result = dlg.open(toqstr(initpath),
2707 QStringList(qt_("LyX Documents (*.lyx)")));
2709 if (result.first == FileDialog::Later)
2713 filename.set(fromqstr(result.second));
2715 // check selected filename
2716 if (filename.empty()) {
2717 // emit message signal.
2718 message(_("Canceled."));
2723 bv->insertLyXFile(filename, ignorelang);
2724 bv->buffer().errors("Parse");
2728 string const GuiView::getTemplatesPath(Buffer & b)
2730 // We start off with the user's templates path
2731 string result = addPath(package().user_support().absFileName(), "templates");
2732 // Check for the document language
2733 string const langcode = b.params().language->code();
2734 string const shortcode = langcode.substr(0, 2);
2735 if (!langcode.empty() && shortcode != "en") {
2736 string subpath = addPath(result, shortcode);
2737 string subpath_long = addPath(result, langcode);
2738 // If we have a subdirectory for the language already,
2740 FileName sp = FileName(subpath);
2741 if (sp.isDirectory())
2743 else if (FileName(subpath_long).isDirectory())
2744 result = subpath_long;
2746 // Ask whether we should create such a subdirectory
2747 docstring const text =
2748 bformat(_("It is suggested to save the template in a subdirectory\n"
2749 "appropriate to the document language (%1$s).\n"
2750 "This subdirectory does not exists yet.\n"
2751 "Do you want to create it?"),
2752 _(b.params().language->display()));
2753 if (Alert::prompt(_("Create Language Directory?"),
2754 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2755 // If the user agreed, we try to create it and report if this failed.
2756 if (!sp.createDirectory(0777))
2757 Alert::error(_("Subdirectory creation failed!"),
2758 _("Could not create subdirectory.\n"
2759 "The template will be saved in the parent directory."));
2765 // Do we have a layout category?
2766 string const cat = b.params().baseClass() ?
2767 b.params().baseClass()->category()
2770 string subpath = addPath(result, cat);
2771 // If we have a subdirectory for the category already,
2773 FileName sp = FileName(subpath);
2774 if (sp.isDirectory())
2777 // Ask whether we should create such a subdirectory
2778 docstring const text =
2779 bformat(_("It is suggested to save the template in a subdirectory\n"
2780 "appropriate to the layout category (%1$s).\n"
2781 "This subdirectory does not exists yet.\n"
2782 "Do you want to create it?"),
2784 if (Alert::prompt(_("Create Category Directory?"),
2785 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2786 // If the user agreed, we try to create it and report if this failed.
2787 if (!sp.createDirectory(0777))
2788 Alert::error(_("Subdirectory creation failed!"),
2789 _("Could not create subdirectory.\n"
2790 "The template will be saved in the parent directory."));
2800 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2802 FileName fname = b.fileName();
2803 FileName const oldname = fname;
2804 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2806 if (!newname.empty()) {
2809 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2811 fname = support::makeAbsPath(to_utf8(newname),
2812 oldname.onlyPath().absFileName());
2814 // Switch to this Buffer.
2817 // No argument? Ask user through dialog.
2819 QString const title = as_template ? qt_("Choose a filename to save template as")
2820 : qt_("Choose a filename to save document as");
2821 FileDialog dlg(title);
2822 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2823 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2825 if (!isLyXFileName(fname.absFileName()))
2826 fname.changeExtension(".lyx");
2828 string const path = as_template ?
2830 : fname.onlyPath().absFileName();
2831 FileDialog::Result result =
2832 dlg.save(toqstr(path),
2833 QStringList(qt_("LyX Documents (*.lyx)")),
2834 toqstr(fname.onlyFileName()));
2836 if (result.first == FileDialog::Later)
2839 fname.set(fromqstr(result.second));
2844 if (!isLyXFileName(fname.absFileName()))
2845 fname.changeExtension(".lyx");
2848 // fname is now the new Buffer location.
2850 // if there is already a Buffer open with this name, we do not want
2851 // to have another one. (the second test makes sure we're not just
2852 // trying to overwrite ourselves, which is fine.)
2853 if (theBufferList().exists(fname) && fname != oldname
2854 && theBufferList().getBuffer(fname) != &b) {
2855 docstring const text =
2856 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2857 "Please close it before attempting to overwrite it.\n"
2858 "Do you want to choose a new filename?"),
2859 from_utf8(fname.absFileName()));
2860 int const ret = Alert::prompt(_("Chosen File Already Open"),
2861 text, 0, 1, _("&Rename"), _("&Cancel"));
2863 case 0: return renameBuffer(b, docstring(), kind);
2864 case 1: return false;
2869 bool const existsLocal = fname.exists();
2870 bool const existsInVC = LyXVC::fileInVC(fname);
2871 if (existsLocal || existsInVC) {
2872 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2873 if (kind != LV_WRITE_AS && existsInVC) {
2874 // renaming to a name that is already in VC
2876 docstring text = bformat(_("The document %1$s "
2877 "is already registered.\n\n"
2878 "Do you want to choose a new name?"),
2880 docstring const title = (kind == LV_VC_RENAME) ?
2881 _("Rename document?") : _("Copy document?");
2882 docstring const button = (kind == LV_VC_RENAME) ?
2883 _("&Rename") : _("&Copy");
2884 int const ret = Alert::prompt(title, text, 0, 1,
2885 button, _("&Cancel"));
2887 case 0: return renameBuffer(b, docstring(), kind);
2888 case 1: return false;
2893 docstring text = bformat(_("The document %1$s "
2894 "already exists.\n\n"
2895 "Do you want to overwrite that document?"),
2897 int const ret = Alert::prompt(_("Overwrite document?"),
2898 text, 0, 2, _("&Overwrite"),
2899 _("&Rename"), _("&Cancel"));
2902 case 1: return renameBuffer(b, docstring(), kind);
2903 case 2: return false;
2909 case LV_VC_RENAME: {
2910 string msg = b.lyxvc().rename(fname);
2913 message(from_utf8(msg));
2917 string msg = b.lyxvc().copy(fname);
2920 message(from_utf8(msg));
2924 case LV_WRITE_AS_TEMPLATE:
2927 // LyXVC created the file already in case of LV_VC_RENAME or
2928 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2929 // relative paths of included stuff right if we moved e.g. from
2930 // /a/b.lyx to /a/c/b.lyx.
2932 bool const saved = saveBuffer(b, fname);
2939 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2941 FileName fname = b.fileName();
2943 FileDialog dlg(qt_("Choose a filename to export the document as"));
2944 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2947 QString const anyformat = qt_("Guess from extension (*.*)");
2950 vector<Format const *> export_formats;
2951 for (Format const & f : theFormats())
2952 if (f.documentFormat())
2953 export_formats.push_back(&f);
2954 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2955 map<QString, string> fmap;
2958 for (Format const * f : export_formats) {
2959 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2960 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2962 from_ascii(f->extension())));
2963 types << loc_filter;
2964 fmap[loc_filter] = f->name();
2965 if (from_ascii(f->name()) == iformat) {
2966 filter = loc_filter;
2967 ext = f->extension();
2970 string ofname = fname.onlyFileName();
2972 ofname = support::changeExtension(ofname, ext);
2973 FileDialog::Result result =
2974 dlg.save(toqstr(fname.onlyPath().absFileName()),
2978 if (result.first != FileDialog::Chosen)
2982 fname.set(fromqstr(result.second));
2983 if (filter == anyformat)
2984 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2986 fmt_name = fmap[filter];
2987 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2988 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2990 if (fmt_name.empty() || fname.empty())
2993 // fname is now the new Buffer location.
2994 if (FileName(fname).exists()) {
2995 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2996 docstring text = bformat(_("The document %1$s already "
2997 "exists.\n\nDo you want to "
2998 "overwrite that document?"),
3000 int const ret = Alert::prompt(_("Overwrite document?"),
3001 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3004 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3005 case 2: return false;
3009 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3012 return dr.dispatched();
3016 bool GuiView::saveBuffer(Buffer & b)
3018 return saveBuffer(b, FileName());
3022 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3024 if (workArea(b) && workArea(b)->inDialogMode())
3027 if (fn.empty() && b.isUnnamed())
3028 return renameBuffer(b, docstring());
3030 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3032 theSession().lastFiles().add(b.fileName());
3033 theSession().writeFile();
3037 // Switch to this Buffer.
3040 // FIXME: we don't tell the user *WHY* the save failed !!
3041 docstring const file = makeDisplayPath(b.absFileName(), 30);
3042 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3043 "Do you want to rename the document and "
3044 "try again?"), file);
3045 int const ret = Alert::prompt(_("Rename and save?"),
3046 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3049 if (!renameBuffer(b, docstring()))
3058 return saveBuffer(b, fn);
3062 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3064 return closeWorkArea(wa, false);
3068 // We only want to close the buffer if it is not visible in other workareas
3069 // of the same view, nor in other views, and if this is not a child
3070 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3072 Buffer & buf = wa->bufferView().buffer();
3074 bool last_wa = d.countWorkAreasOf(buf) == 1
3075 && !inOtherView(buf) && !buf.parent();
3077 bool close_buffer = last_wa;
3080 if (lyxrc.close_buffer_with_last_view == "yes")
3082 else if (lyxrc.close_buffer_with_last_view == "no")
3083 close_buffer = false;
3086 if (buf.isUnnamed())
3087 file = from_utf8(buf.fileName().onlyFileName());
3089 file = buf.fileName().displayName(30);
3090 docstring const text = bformat(
3091 _("Last view on document %1$s is being closed.\n"
3092 "Would you like to close or hide the document?\n"
3094 "Hidden documents can be displayed back through\n"
3095 "the menu: View->Hidden->...\n"
3097 "To remove this question, set your preference in:\n"
3098 " Tools->Preferences->Look&Feel->UserInterface\n"
3100 int ret = Alert::prompt(_("Close or hide document?"),
3101 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3104 close_buffer = (ret == 0);
3108 return closeWorkArea(wa, close_buffer);
3112 bool GuiView::closeBuffer()
3114 GuiWorkArea * wa = currentMainWorkArea();
3115 // coverity complained about this
3116 // it seems unnecessary, but perhaps is worth the check
3117 LASSERT(wa, return false);
3119 setCurrentWorkArea(wa);
3120 Buffer & buf = wa->bufferView().buffer();
3121 return closeWorkArea(wa, !buf.parent());
3125 void GuiView::writeSession() const {
3126 GuiWorkArea const * active_wa = currentMainWorkArea();
3127 for (int i = 0; i < d.splitter_->count(); ++i) {
3128 TabWorkArea * twa = d.tabWorkArea(i);
3129 for (int j = 0; j < twa->count(); ++j) {
3130 GuiWorkArea * wa = twa->workArea(j);
3131 Buffer & buf = wa->bufferView().buffer();
3132 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3138 bool GuiView::closeBufferAll()
3141 for (auto & buf : theBufferList()) {
3142 if (!saveBufferIfNeeded(*buf, false)) {
3143 // Closing has been cancelled, so abort.
3148 // Close the workareas in all other views
3149 QList<int> const ids = guiApp->viewIds();
3150 for (int i = 0; i != ids.size(); ++i) {
3151 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3155 // Close our own workareas
3156 if (!closeWorkAreaAll())
3163 bool GuiView::closeWorkAreaAll()
3165 setCurrentWorkArea(currentMainWorkArea());
3167 // We might be in a situation that there is still a tabWorkArea, but
3168 // there are no tabs anymore. This can happen when we get here after a
3169 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3170 // many TabWorkArea's have no documents anymore.
3173 // We have to call count() each time, because it can happen that
3174 // more than one splitter will disappear in one iteration (bug 5998).
3175 while (d.splitter_->count() > empty_twa) {
3176 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3178 if (twa->count() == 0)
3181 setCurrentWorkArea(twa->currentWorkArea());
3182 if (!closeTabWorkArea(twa))
3190 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3195 Buffer & buf = wa->bufferView().buffer();
3197 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3198 Alert::warning(_("Close document"),
3199 _("Document could not be closed because it is being processed by LyX."));
3204 return closeBuffer(buf);
3206 if (!inMultiTabs(wa))
3207 if (!saveBufferIfNeeded(buf, true))
3215 bool GuiView::closeBuffer(Buffer & buf)
3217 bool success = true;
3218 for (Buffer * child_buf : buf.getChildren()) {
3219 if (theBufferList().isOthersChild(&buf, child_buf)) {
3220 child_buf->setParent(nullptr);
3224 // FIXME: should we look in other tabworkareas?
3225 // ANSWER: I don't think so. I've tested, and if the child is
3226 // open in some other window, it closes without a problem.
3227 GuiWorkArea * child_wa = workArea(*child_buf);
3230 // If we are in a close_event all children will be closed in some time,
3231 // so no need to do it here. This will ensure that the children end up
3232 // in the session file in the correct order. If we close the master
3233 // buffer, we can close or release the child buffers here too.
3235 success = closeWorkArea(child_wa, true);
3239 // In this case the child buffer is open but hidden.
3240 // Even in this case, children can be dirty (e.g.,
3241 // after a label change in the master, see #11405).
3242 // Therefore, check this
3243 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3244 // If we are in a close_event all children will be closed in some time,
3245 // so no need to do it here. This will ensure that the children end up
3246 // in the session file in the correct order. If we close the master
3247 // buffer, we can close or release the child buffers here too.
3250 // Save dirty buffers also if closing_!
3251 if (saveBufferIfNeeded(*child_buf, false)) {
3252 child_buf->removeAutosaveFile();
3253 theBufferList().release(child_buf);
3255 // Saving of dirty children has been cancelled.
3256 // Cancel the whole process.
3263 // goto bookmark to update bookmark pit.
3264 // FIXME: we should update only the bookmarks related to this buffer!
3265 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3266 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3267 guiApp->gotoBookmark(i + 1, false, false);
3269 if (saveBufferIfNeeded(buf, false)) {
3270 buf.removeAutosaveFile();
3271 theBufferList().release(&buf);
3275 // open all children again to avoid a crash because of dangling
3276 // pointers (bug 6603)
3282 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3284 while (twa == d.currentTabWorkArea()) {
3285 twa->setCurrentIndex(twa->count() - 1);
3287 GuiWorkArea * wa = twa->currentWorkArea();
3288 Buffer & b = wa->bufferView().buffer();
3290 // We only want to close the buffer if the same buffer is not visible
3291 // in another view, and if this is not a child and if we are closing
3292 // a view (not a tabgroup).
3293 bool const close_buffer =
3294 !inOtherView(b) && !b.parent() && closing_;
3296 if (!closeWorkArea(wa, close_buffer))
3303 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3305 if (buf.isClean() || buf.paragraphs().empty())
3308 // Switch to this Buffer.
3314 if (buf.isUnnamed()) {
3315 file = from_utf8(buf.fileName().onlyFileName());
3318 FileName filename = buf.fileName();
3320 file = filename.displayName(30);
3321 exists = filename.exists();
3324 // Bring this window to top before asking questions.
3329 if (hiding && buf.isUnnamed()) {
3330 docstring const text = bformat(_("The document %1$s has not been "
3331 "saved yet.\n\nDo you want to save "
3332 "the document?"), file);
3333 ret = Alert::prompt(_("Save new document?"),
3334 text, 0, 1, _("&Save"), _("&Cancel"));
3338 docstring const text = exists ?
3339 bformat(_("The document %1$s has unsaved changes."
3340 "\n\nDo you want to save the document or "
3341 "discard the changes?"), file) :
3342 bformat(_("The document %1$s has not been saved yet."
3343 "\n\nDo you want to save the document or "
3344 "discard it entirely?"), file);
3345 docstring const title = exists ?
3346 _("Save changed document?") : _("Save document?");
3347 ret = Alert::prompt(title, text, 0, 2,
3348 _("&Save"), _("&Discard"), _("&Cancel"));
3353 if (!saveBuffer(buf))
3357 // If we crash after this we could have no autosave file
3358 // but I guess this is really improbable (Jug).
3359 // Sometimes improbable things happen:
3360 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3361 // buf.removeAutosaveFile();
3363 // revert all changes
3374 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3376 Buffer & buf = wa->bufferView().buffer();
3378 for (int i = 0; i != d.splitter_->count(); ++i) {
3379 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3380 if (wa_ && wa_ != wa)
3383 return inOtherView(buf);
3387 bool GuiView::inOtherView(Buffer & buf)
3389 QList<int> const ids = guiApp->viewIds();
3391 for (int i = 0; i != ids.size(); ++i) {
3395 if (guiApp->view(ids[i]).workArea(buf))
3402 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3404 if (!documentBufferView())
3407 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3408 Buffer * const curbuf = &documentBufferView()->buffer();
3409 int nwa = twa->count();
3410 for (int i = 0; i < nwa; ++i) {
3411 if (&workArea(i)->bufferView().buffer() == curbuf) {
3413 if (np == NEXTBUFFER)
3414 next_index = (i == nwa - 1 ? 0 : i + 1);
3416 next_index = (i == 0 ? nwa - 1 : i - 1);
3418 twa->moveTab(i, next_index);
3420 setBuffer(&workArea(next_index)->bufferView().buffer());
3428 /// make sure the document is saved
3429 static bool ensureBufferClean(Buffer * buffer)
3431 LASSERT(buffer, return false);
3432 if (buffer->isClean() && !buffer->isUnnamed())
3435 docstring const file = buffer->fileName().displayName(30);
3438 if (!buffer->isUnnamed()) {
3439 text = bformat(_("The document %1$s has unsaved "
3440 "changes.\n\nDo you want to save "
3441 "the document?"), file);
3442 title = _("Save changed document?");
3445 text = bformat(_("The document %1$s has not been "
3446 "saved yet.\n\nDo you want to save "
3447 "the document?"), file);
3448 title = _("Save new document?");
3450 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3453 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3455 return buffer->isClean() && !buffer->isUnnamed();
3459 bool GuiView::reloadBuffer(Buffer & buf)
3461 currentBufferView()->cursor().reset();
3462 Buffer::ReadStatus status = buf.reload();
3463 return status == Buffer::ReadSuccess;
3467 void GuiView::checkExternallyModifiedBuffers()
3469 for (Buffer * buf : theBufferList()) {
3470 if (buf->fileName().exists() && buf->isChecksumModified()) {
3471 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3472 " Reload now? Any local changes will be lost."),
3473 from_utf8(buf->absFileName()));
3474 int const ret = Alert::prompt(_("Reload externally changed document?"),
3475 text, 0, 1, _("&Reload"), _("&Cancel"));
3483 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3485 Buffer * buffer = documentBufferView()
3486 ? &(documentBufferView()->buffer()) : nullptr;
3488 switch (cmd.action()) {
3489 case LFUN_VC_REGISTER:
3490 if (!buffer || !ensureBufferClean(buffer))
3492 if (!buffer->lyxvc().inUse()) {
3493 if (buffer->lyxvc().registrer()) {
3494 reloadBuffer(*buffer);
3495 dr.clearMessageUpdate();
3500 case LFUN_VC_RENAME:
3501 case LFUN_VC_COPY: {
3502 if (!buffer || !ensureBufferClean(buffer))
3504 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3505 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3506 // Some changes are not yet committed.
3507 // We test here and not in getStatus(), since
3508 // this test is expensive.
3510 LyXVC::CommandResult ret =
3511 buffer->lyxvc().checkIn(log);
3513 if (ret == LyXVC::ErrorCommand ||
3514 ret == LyXVC::VCSuccess)
3515 reloadBuffer(*buffer);
3516 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3517 frontend::Alert::error(
3518 _("Revision control error."),
3519 _("Document could not be checked in."));
3523 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3524 LV_VC_RENAME : LV_VC_COPY;
3525 renameBuffer(*buffer, cmd.argument(), kind);
3530 case LFUN_VC_CHECK_IN:
3531 if (!buffer || !ensureBufferClean(buffer))
3533 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3535 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3537 // Only skip reloading if the checkin was cancelled or
3538 // an error occurred before the real checkin VCS command
3539 // was executed, since the VCS might have changed the
3540 // file even if it could not checkin successfully.
3541 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3542 reloadBuffer(*buffer);
3546 case LFUN_VC_CHECK_OUT:
3547 if (!buffer || !ensureBufferClean(buffer))
3549 if (buffer->lyxvc().inUse()) {
3550 dr.setMessage(buffer->lyxvc().checkOut());
3551 reloadBuffer(*buffer);
3555 case LFUN_VC_LOCKING_TOGGLE:
3556 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3558 if (buffer->lyxvc().inUse()) {
3559 string res = buffer->lyxvc().lockingToggle();
3561 frontend::Alert::error(_("Revision control error."),
3562 _("Error when setting the locking property."));
3565 reloadBuffer(*buffer);
3570 case LFUN_VC_REVERT:
3573 if (buffer->lyxvc().revert()) {
3574 reloadBuffer(*buffer);
3575 dr.clearMessageUpdate();
3579 case LFUN_VC_UNDO_LAST:
3582 buffer->lyxvc().undoLast();
3583 reloadBuffer(*buffer);
3584 dr.clearMessageUpdate();
3587 case LFUN_VC_REPO_UPDATE:
3590 if (ensureBufferClean(buffer)) {
3591 dr.setMessage(buffer->lyxvc().repoUpdate());
3592 checkExternallyModifiedBuffers();
3596 case LFUN_VC_COMMAND: {
3597 string flag = cmd.getArg(0);
3598 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3601 if (contains(flag, 'M')) {
3602 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3605 string path = cmd.getArg(1);
3606 if (contains(path, "$$p") && buffer)
3607 path = subst(path, "$$p", buffer->filePath());
3608 LYXERR(Debug::LYXVC, "Directory: " << path);
3610 if (!pp.isReadableDirectory()) {
3611 lyxerr << _("Directory is not accessible.") << endl;
3614 support::PathChanger p(pp);
3616 string command = cmd.getArg(2);
3617 if (command.empty())
3620 command = subst(command, "$$i", buffer->absFileName());
3621 command = subst(command, "$$p", buffer->filePath());
3623 command = subst(command, "$$m", to_utf8(message));
3624 LYXERR(Debug::LYXVC, "Command: " << command);
3626 one.startscript(Systemcall::Wait, command);
3630 if (contains(flag, 'I'))
3631 buffer->markDirty();
3632 if (contains(flag, 'R'))
3633 reloadBuffer(*buffer);
3638 case LFUN_VC_COMPARE: {
3639 if (cmd.argument().empty()) {
3640 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3646 string rev1 = cmd.getArg(0);
3650 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3653 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3654 f2 = buffer->absFileName();
3656 string rev2 = cmd.getArg(1);
3660 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3664 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3665 f1 << "\n" << f2 << "\n" );
3666 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3667 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3677 void GuiView::openChildDocument(string const & fname)
3679 LASSERT(documentBufferView(), return);
3680 Buffer & buffer = documentBufferView()->buffer();
3681 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3682 documentBufferView()->saveBookmark(false);
3683 Buffer * child = nullptr;
3684 if (theBufferList().exists(filename)) {
3685 child = theBufferList().getBuffer(filename);
3688 message(bformat(_("Opening child document %1$s..."),
3689 makeDisplayPath(filename.absFileName())));
3690 child = loadDocument(filename, false);
3692 // Set the parent name of the child document.
3693 // This makes insertion of citations and references in the child work,
3694 // when the target is in the parent or another child document.
3696 child->setParent(&buffer);
3700 bool GuiView::goToFileRow(string const & argument)
3704 size_t i = argument.find_last_of(' ');
3705 if (i != string::npos) {
3706 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3707 istringstream is(argument.substr(i + 1));
3712 if (i == string::npos) {
3713 LYXERR0("Wrong argument: " << argument);
3716 Buffer * buf = nullptr;
3717 string const realtmp = package().temp_dir().realPath();
3718 // We have to use os::path_prefix_is() here, instead of
3719 // simply prefixIs(), because the file name comes from
3720 // an external application and may need case adjustment.
3721 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3722 buf = theBufferList().getBufferFromTmp(file_name, true);
3723 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3724 << (buf ? " success" : " failed"));
3726 // Must replace extension of the file to be .lyx
3727 // and get full path
3728 FileName const s = fileSearch(string(),
3729 support::changeExtension(file_name, ".lyx"), "lyx");
3730 // Either change buffer or load the file
3731 if (theBufferList().exists(s))
3732 buf = theBufferList().getBuffer(s);
3733 else if (s.exists()) {
3734 buf = loadDocument(s);
3739 _("File does not exist: %1$s"),
3740 makeDisplayPath(file_name)));
3746 _("No buffer for file: %1$s."),
3747 makeDisplayPath(file_name))
3752 bool success = documentBufferView()->setCursorFromRow(row);
3754 LYXERR(Debug::LATEX,
3755 "setCursorFromRow: invalid position for row " << row);
3756 frontend::Alert::error(_("Inverse Search Failed"),
3757 _("Invalid position requested by inverse search.\n"
3758 "You may need to update the viewed document."));
3764 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3766 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3767 menu->exec(QCursor::pos());
3772 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3773 Buffer const * orig, Buffer * clone, string const & format)
3775 Buffer::ExportStatus const status = func(format);
3777 // the cloning operation will have produced a clone of the entire set of
3778 // documents, starting from the master. so we must delete those.
3779 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3781 busyBuffers.remove(orig);
3786 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3787 Buffer const * orig, Buffer * clone, string const & format)
3789 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3791 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3795 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3796 Buffer const * orig, Buffer * clone, string const & format)
3798 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3800 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3804 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3805 Buffer const * orig, Buffer * clone, string const & format)
3807 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3809 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3813 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3814 string const & argument,
3815 Buffer const * used_buffer,
3816 docstring const & msg,
3817 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3818 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3819 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3825 string format = argument;
3827 format = used_buffer->params().getDefaultOutputFormat();
3828 processing_format = format;
3830 progress_->clearMessages();
3833 #if EXPORT_in_THREAD
3835 GuiViewPrivate::busyBuffers.insert(used_buffer);
3836 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3837 if (!cloned_buffer) {
3838 Alert::error(_("Export Error"),
3839 _("Error cloning the Buffer."));
3842 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3847 setPreviewFuture(f);
3848 last_export_format = used_buffer->params().bufferFormat();
3851 // We are asynchronous, so we don't know here anything about the success
3854 Buffer::ExportStatus status;
3856 status = (used_buffer->*syncFunc)(format, false);
3857 } else if (previewFunc) {
3858 status = (used_buffer->*previewFunc)(format);
3861 handleExportStatus(gv_, status, format);
3863 return (status == Buffer::ExportSuccess
3864 || status == Buffer::PreviewSuccess);
3868 Buffer::ExportStatus status;
3870 status = (used_buffer->*syncFunc)(format, true);
3871 } else if (previewFunc) {
3872 status = (used_buffer->*previewFunc)(format);
3875 handleExportStatus(gv_, status, format);
3877 return (status == Buffer::ExportSuccess
3878 || status == Buffer::PreviewSuccess);
3882 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3884 BufferView * bv = currentBufferView();
3885 LASSERT(bv, return);
3887 // Let the current BufferView dispatch its own actions.
3888 bv->dispatch(cmd, dr);
3889 if (dr.dispatched()) {
3890 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3891 updateDialog("document", "");
3895 // Try with the document BufferView dispatch if any.
3896 BufferView * doc_bv = documentBufferView();
3897 if (doc_bv && doc_bv != bv) {
3898 doc_bv->dispatch(cmd, dr);
3899 if (dr.dispatched()) {
3900 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3901 updateDialog("document", "");
3906 // Then let the current Cursor dispatch its own actions.
3907 bv->cursor().dispatch(cmd);
3909 // update completion. We do it here and not in
3910 // processKeySym to avoid another redraw just for a
3911 // changed inline completion
3912 if (cmd.origin() == FuncRequest::KEYBOARD) {
3913 if (cmd.action() == LFUN_SELF_INSERT
3914 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3915 updateCompletion(bv->cursor(), true, true);
3916 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3917 updateCompletion(bv->cursor(), false, true);
3919 updateCompletion(bv->cursor(), false, false);
3922 dr = bv->cursor().result();
3926 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3928 BufferView * bv = currentBufferView();
3929 // By default we won't need any update.
3930 dr.screenUpdate(Update::None);
3931 // assume cmd will be dispatched
3932 dr.dispatched(true);
3934 Buffer * doc_buffer = documentBufferView()
3935 ? &(documentBufferView()->buffer()) : nullptr;
3937 if (cmd.origin() == FuncRequest::TOC) {
3938 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3939 toc->doDispatch(bv->cursor(), cmd, dr);
3943 string const argument = to_utf8(cmd.argument());
3945 switch(cmd.action()) {
3946 case LFUN_BUFFER_CHILD_OPEN:
3947 openChildDocument(to_utf8(cmd.argument()));
3950 case LFUN_BUFFER_IMPORT:
3951 importDocument(to_utf8(cmd.argument()));
3954 case LFUN_MASTER_BUFFER_EXPORT:
3956 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3958 case LFUN_BUFFER_EXPORT: {
3961 // GCC only sees strfwd.h when building merged
3962 if (::lyx::operator==(cmd.argument(), "custom")) {
3963 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3964 // so the following test should not be needed.
3965 // In principle, we could try to switch to such a view...
3966 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3967 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3971 string const dest = cmd.getArg(1);
3972 FileName target_dir;
3973 if (!dest.empty() && FileName::isAbsolute(dest))
3974 target_dir = FileName(support::onlyPath(dest));
3976 target_dir = doc_buffer->fileName().onlyPath();
3978 string const format = (argument.empty() || argument == "default") ?
3979 doc_buffer->params().getDefaultOutputFormat() : argument;
3981 if ((dest.empty() && doc_buffer->isUnnamed())
3982 || !target_dir.isDirWritable()) {
3983 exportBufferAs(*doc_buffer, from_utf8(format));
3986 /* TODO/Review: Is it a problem to also export the children?
3987 See the update_unincluded flag */
3988 d.asyncBufferProcessing(format,
3991 &GuiViewPrivate::exportAndDestroy,
3993 nullptr, cmd.allowAsync());
3994 // TODO Inform user about success
3998 case LFUN_BUFFER_EXPORT_AS: {
3999 LASSERT(doc_buffer, break);
4000 docstring f = cmd.argument();
4002 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4003 exportBufferAs(*doc_buffer, f);
4007 case LFUN_BUFFER_UPDATE: {
4008 d.asyncBufferProcessing(argument,
4011 &GuiViewPrivate::compileAndDestroy,
4013 nullptr, cmd.allowAsync());
4016 case LFUN_BUFFER_VIEW: {
4017 d.asyncBufferProcessing(argument,
4019 _("Previewing ..."),
4020 &GuiViewPrivate::previewAndDestroy,
4022 &Buffer::preview, cmd.allowAsync());
4025 case LFUN_MASTER_BUFFER_UPDATE: {
4026 d.asyncBufferProcessing(argument,
4027 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4029 &GuiViewPrivate::compileAndDestroy,
4031 nullptr, cmd.allowAsync());
4034 case LFUN_MASTER_BUFFER_VIEW: {
4035 d.asyncBufferProcessing(argument,
4036 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4038 &GuiViewPrivate::previewAndDestroy,
4039 nullptr, &Buffer::preview, cmd.allowAsync());
4042 case LFUN_EXPORT_CANCEL: {
4043 Systemcall::killscript();
4046 case LFUN_BUFFER_SWITCH: {
4047 string const file_name = to_utf8(cmd.argument());
4048 if (!FileName::isAbsolute(file_name)) {
4050 dr.setMessage(_("Absolute filename expected."));
4054 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4057 dr.setMessage(_("Document not loaded"));
4061 // Do we open or switch to the buffer in this view ?
4062 if (workArea(*buffer)
4063 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4068 // Look for the buffer in other views
4069 QList<int> const ids = guiApp->viewIds();
4071 for (; i != ids.size(); ++i) {
4072 GuiView & gv = guiApp->view(ids[i]);
4073 if (gv.workArea(*buffer)) {
4075 gv.activateWindow();
4077 gv.setBuffer(buffer);
4082 // If necessary, open a new window as a last resort
4083 if (i == ids.size()) {
4084 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4090 case LFUN_BUFFER_NEXT:
4091 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4094 case LFUN_BUFFER_MOVE_NEXT:
4095 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4098 case LFUN_BUFFER_PREVIOUS:
4099 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4102 case LFUN_BUFFER_MOVE_PREVIOUS:
4103 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4106 case LFUN_BUFFER_CHKTEX:
4107 LASSERT(doc_buffer, break);
4108 doc_buffer->runChktex();
4111 case LFUN_COMMAND_EXECUTE: {
4112 command_execute_ = true;
4113 minibuffer_focus_ = true;
4116 case LFUN_DROP_LAYOUTS_CHOICE:
4117 d.layout_->showPopup();
4120 case LFUN_MENU_OPEN:
4121 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4122 menu->exec(QCursor::pos());
4125 case LFUN_FILE_INSERT: {
4126 if (cmd.getArg(1) == "ignorelang")
4127 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4129 insertLyXFile(cmd.argument());
4133 case LFUN_FILE_INSERT_PLAINTEXT:
4134 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4135 string const fname = to_utf8(cmd.argument());
4136 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4137 dr.setMessage(_("Absolute filename expected."));
4141 FileName filename(fname);
4142 if (fname.empty()) {
4143 FileDialog dlg(qt_("Select file to insert"));
4145 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4146 QStringList(qt_("All Files (*)")));
4148 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4149 dr.setMessage(_("Canceled."));
4153 filename.set(fromqstr(result.second));
4157 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4158 bv->dispatch(new_cmd, dr);
4163 case LFUN_BUFFER_RELOAD: {
4164 LASSERT(doc_buffer, break);
4167 bool drop = (cmd.argument() == "dump");
4170 if (!drop && !doc_buffer->isClean()) {
4171 docstring const file =
4172 makeDisplayPath(doc_buffer->absFileName(), 20);
4173 if (doc_buffer->notifiesExternalModification()) {
4174 docstring text = _("The current version will be lost. "
4175 "Are you sure you want to load the version on disk "
4176 "of the document %1$s?");
4177 ret = Alert::prompt(_("Reload saved document?"),
4178 bformat(text, file), 1, 1,
4179 _("&Reload"), _("&Cancel"));
4181 docstring text = _("Any changes will be lost. "
4182 "Are you sure you want to revert to the saved version "
4183 "of the document %1$s?");
4184 ret = Alert::prompt(_("Revert to saved document?"),
4185 bformat(text, file), 1, 1,
4186 _("&Revert"), _("&Cancel"));
4191 doc_buffer->markClean();
4192 reloadBuffer(*doc_buffer);
4193 dr.forceBufferUpdate();
4198 case LFUN_BUFFER_RESET_EXPORT:
4199 LASSERT(doc_buffer, break);
4200 doc_buffer->requireFreshStart(true);
4201 dr.setMessage(_("Buffer export reset."));
4204 case LFUN_BUFFER_WRITE:
4205 LASSERT(doc_buffer, break);
4206 saveBuffer(*doc_buffer);
4209 case LFUN_BUFFER_WRITE_AS:
4210 LASSERT(doc_buffer, break);
4211 renameBuffer(*doc_buffer, cmd.argument());
4214 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4215 LASSERT(doc_buffer, break);
4216 renameBuffer(*doc_buffer, cmd.argument(),
4217 LV_WRITE_AS_TEMPLATE);
4220 case LFUN_BUFFER_WRITE_ALL: {
4221 Buffer * first = theBufferList().first();
4224 message(_("Saving all documents..."));
4225 // We cannot use a for loop as the buffer list cycles.
4228 if (!b->isClean()) {
4230 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4232 b = theBufferList().next(b);
4233 } while (b != first);
4234 dr.setMessage(_("All documents saved."));
4238 case LFUN_MASTER_BUFFER_FORALL: {
4242 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4243 funcToRun.allowAsync(false);
4245 for (Buffer const * buf : doc_buffer->allRelatives()) {
4246 // Switch to other buffer view and resend cmd
4247 lyx::dispatch(FuncRequest(
4248 LFUN_BUFFER_SWITCH, buf->absFileName()));
4249 lyx::dispatch(funcToRun);
4252 lyx::dispatch(FuncRequest(
4253 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4257 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4258 LASSERT(doc_buffer, break);
4259 doc_buffer->clearExternalModification();
4262 case LFUN_BUFFER_CLOSE:
4266 case LFUN_BUFFER_CLOSE_ALL:
4270 case LFUN_DEVEL_MODE_TOGGLE:
4271 devel_mode_ = !devel_mode_;
4273 dr.setMessage(_("Developer mode is now enabled."));
4275 dr.setMessage(_("Developer mode is now disabled."));
4278 case LFUN_TOOLBAR_TOGGLE: {
4279 string const name = cmd.getArg(0);
4280 if (GuiToolbar * t = toolbar(name))
4285 case LFUN_TOOLBAR_MOVABLE: {
4286 string const name = cmd.getArg(0);
4288 // toggle (all) toolbars movablility
4289 toolbarsMovable_ = !toolbarsMovable_;
4290 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4291 GuiToolbar * tb = toolbar(ti.name);
4292 if (tb && tb->isMovable() != toolbarsMovable_)
4293 // toggle toolbar movablity if it does not fit lock
4294 // (all) toolbars positions state silent = true, since
4295 // status bar notifications are slow
4298 if (toolbarsMovable_)
4299 dr.setMessage(_("Toolbars unlocked."));
4301 dr.setMessage(_("Toolbars locked."));
4302 } else if (GuiToolbar * t = toolbar(name)) {
4303 // toggle current toolbar movablity
4305 // update lock (all) toolbars positions
4306 updateLockToolbars();
4311 case LFUN_ICON_SIZE: {
4312 QSize size = d.iconSize(cmd.argument());
4314 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4315 size.width(), size.height()));
4319 case LFUN_DIALOG_UPDATE: {
4320 string const name = to_utf8(cmd.argument());
4321 if (name == "prefs" || name == "document")
4322 updateDialog(name, string());
4323 else if (name == "paragraph")
4324 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4325 else if (currentBufferView()) {
4326 Inset * inset = currentBufferView()->editedInset(name);
4327 // Can only update a dialog connected to an existing inset
4329 // FIXME: get rid of this indirection; GuiView ask the inset
4330 // if he is kind enough to update itself...
4331 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4332 //FIXME: pass DispatchResult here?
4333 inset->dispatch(currentBufferView()->cursor(), fr);
4339 case LFUN_DIALOG_TOGGLE: {
4340 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4341 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4342 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4346 case LFUN_DIALOG_DISCONNECT_INSET:
4347 disconnectDialog(to_utf8(cmd.argument()));
4350 case LFUN_DIALOG_HIDE: {
4351 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4355 case LFUN_DIALOG_SHOW: {
4356 string const name = cmd.getArg(0);
4357 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4359 if (name == "latexlog") {
4360 // getStatus checks that
4361 LASSERT(doc_buffer, break);
4362 Buffer::LogType type;
4363 string const logfile = doc_buffer->logName(&type);
4365 case Buffer::latexlog:
4368 case Buffer::buildlog:
4369 sdata = "literate ";
4372 sdata += Lexer::quoteString(logfile);
4373 showDialog("log", sdata);
4374 } else if (name == "vclog") {
4375 // getStatus checks that
4376 LASSERT(doc_buffer, break);
4377 string const sdata2 = "vc " +
4378 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4379 showDialog("log", sdata2);
4380 } else if (name == "symbols") {
4381 sdata = bv->cursor().getEncoding()->name();
4383 showDialog("symbols", sdata);
4385 } else if (name == "prefs" && isFullScreen()) {
4386 lfunUiToggle("fullscreen");
4387 showDialog("prefs", sdata);
4389 showDialog(name, sdata);
4394 dr.setMessage(cmd.argument());
4397 case LFUN_UI_TOGGLE: {
4398 string arg = cmd.getArg(0);
4399 if (!lfunUiToggle(arg)) {
4400 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4401 dr.setMessage(bformat(msg, from_utf8(arg)));
4403 // Make sure the keyboard focus stays in the work area.
4408 case LFUN_VIEW_SPLIT: {
4409 LASSERT(doc_buffer, break);
4410 string const orientation = cmd.getArg(0);
4411 d.splitter_->setOrientation(orientation == "vertical"
4412 ? Qt::Vertical : Qt::Horizontal);
4413 TabWorkArea * twa = addTabWorkArea();
4414 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4415 setCurrentWorkArea(wa);
4418 case LFUN_TAB_GROUP_CLOSE:
4419 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4420 closeTabWorkArea(twa);
4421 d.current_work_area_ = nullptr;
4422 twa = d.currentTabWorkArea();
4423 // Switch to the next GuiWorkArea in the found TabWorkArea.
4425 // Make sure the work area is up to date.
4426 setCurrentWorkArea(twa->currentWorkArea());
4428 setCurrentWorkArea(nullptr);
4433 case LFUN_VIEW_CLOSE:
4434 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4435 closeWorkArea(twa->currentWorkArea());
4436 d.current_work_area_ = nullptr;
4437 twa = d.currentTabWorkArea();
4438 // Switch to the next GuiWorkArea in the found TabWorkArea.
4440 // Make sure the work area is up to date.
4441 setCurrentWorkArea(twa->currentWorkArea());
4443 setCurrentWorkArea(nullptr);
4448 case LFUN_COMPLETION_INLINE:
4449 if (d.current_work_area_)
4450 d.current_work_area_->completer().showInline();
4453 case LFUN_COMPLETION_POPUP:
4454 if (d.current_work_area_)
4455 d.current_work_area_->completer().showPopup();
4460 if (d.current_work_area_)
4461 d.current_work_area_->completer().tab();
4464 case LFUN_COMPLETION_CANCEL:
4465 if (d.current_work_area_) {
4466 if (d.current_work_area_->completer().popupVisible())
4467 d.current_work_area_->completer().hidePopup();
4469 d.current_work_area_->completer().hideInline();
4473 case LFUN_COMPLETION_ACCEPT:
4474 if (d.current_work_area_)
4475 d.current_work_area_->completer().activate();
4478 case LFUN_BUFFER_ZOOM_IN:
4479 case LFUN_BUFFER_ZOOM_OUT:
4480 case LFUN_BUFFER_ZOOM: {
4481 if (cmd.argument().empty()) {
4482 if (cmd.action() == LFUN_BUFFER_ZOOM)
4484 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4489 if (cmd.action() == LFUN_BUFFER_ZOOM)
4490 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4491 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4492 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4494 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4497 // Actual zoom value: default zoom + fractional extra value
4498 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4499 if (zoom < static_cast<int>(zoom_min_))
4502 lyxrc.currentZoom = zoom;
4504 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4505 lyxrc.currentZoom, lyxrc.defaultZoom));
4507 guiApp->fontLoader().update();
4508 dr.screenUpdate(Update::Force | Update::FitCursor);
4512 case LFUN_VC_REGISTER:
4513 case LFUN_VC_RENAME:
4515 case LFUN_VC_CHECK_IN:
4516 case LFUN_VC_CHECK_OUT:
4517 case LFUN_VC_REPO_UPDATE:
4518 case LFUN_VC_LOCKING_TOGGLE:
4519 case LFUN_VC_REVERT:
4520 case LFUN_VC_UNDO_LAST:
4521 case LFUN_VC_COMMAND:
4522 case LFUN_VC_COMPARE:
4523 dispatchVC(cmd, dr);
4526 case LFUN_SERVER_GOTO_FILE_ROW:
4527 if(goToFileRow(to_utf8(cmd.argument())))
4528 dr.screenUpdate(Update::Force | Update::FitCursor);
4531 case LFUN_LYX_ACTIVATE:
4535 case LFUN_WINDOW_RAISE:
4541 case LFUN_FORWARD_SEARCH: {
4542 // it seems safe to assume we have a document buffer, since
4543 // getStatus wants one.
4544 LASSERT(doc_buffer, break);
4545 Buffer const * doc_master = doc_buffer->masterBuffer();
4546 FileName const path(doc_master->temppath());
4547 string const texname = doc_master->isChild(doc_buffer)
4548 ? DocFileName(changeExtension(
4549 doc_buffer->absFileName(),
4550 "tex")).mangledFileName()
4551 : doc_buffer->latexName();
4552 string const fulltexname =
4553 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4554 string const mastername =
4555 removeExtension(doc_master->latexName());
4556 FileName const dviname(addName(path.absFileName(),
4557 addExtension(mastername, "dvi")));
4558 FileName const pdfname(addName(path.absFileName(),
4559 addExtension(mastername, "pdf")));
4560 bool const have_dvi = dviname.exists();
4561 bool const have_pdf = pdfname.exists();
4562 if (!have_dvi && !have_pdf) {
4563 dr.setMessage(_("Please, preview the document first."));
4566 string outname = dviname.onlyFileName();
4567 string command = lyxrc.forward_search_dvi;
4568 if (!have_dvi || (have_pdf &&
4569 pdfname.lastModified() > dviname.lastModified())) {
4570 outname = pdfname.onlyFileName();
4571 command = lyxrc.forward_search_pdf;
4574 DocIterator cur = bv->cursor();
4575 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4576 LYXERR(Debug::ACTION, "Forward search: row:" << row
4578 if (row == -1 || command.empty()) {
4579 dr.setMessage(_("Couldn't proceed."));
4582 string texrow = convert<string>(row);
4584 command = subst(command, "$$n", texrow);
4585 command = subst(command, "$$f", fulltexname);
4586 command = subst(command, "$$t", texname);
4587 command = subst(command, "$$o", outname);
4589 volatile PathChanger p(path);
4591 one.startscript(Systemcall::DontWait, command);
4595 case LFUN_SPELLING_CONTINUOUSLY:
4596 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4597 dr.screenUpdate(Update::Force);
4600 case LFUN_CITATION_OPEN: {
4602 if (theFormats().getFormat("pdf"))
4603 pdfv = theFormats().getFormat("pdf")->viewer();
4604 if (theFormats().getFormat("ps"))
4605 psv = theFormats().getFormat("ps")->viewer();
4606 frontend::showTarget(argument, pdfv, psv);
4611 // The LFUN must be for one of BufferView, Buffer or Cursor;
4613 dispatchToBufferView(cmd, dr);
4617 // Part of automatic menu appearance feature.
4618 if (isFullScreen()) {
4619 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4623 // Need to update bv because many LFUNs here might have destroyed it
4624 bv = currentBufferView();
4626 // Clear non-empty selections
4627 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4629 Cursor & cur = bv->cursor();
4630 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4631 cur.clearSelection();
4637 bool GuiView::lfunUiToggle(string const & ui_component)
4639 if (ui_component == "scrollbar") {
4640 // hide() is of no help
4641 if (d.current_work_area_->verticalScrollBarPolicy() ==
4642 Qt::ScrollBarAlwaysOff)
4644 d.current_work_area_->setVerticalScrollBarPolicy(
4645 Qt::ScrollBarAsNeeded);
4647 d.current_work_area_->setVerticalScrollBarPolicy(
4648 Qt::ScrollBarAlwaysOff);
4649 } else if (ui_component == "statusbar") {
4650 statusBar()->setVisible(!statusBar()->isVisible());
4651 } else if (ui_component == "menubar") {
4652 menuBar()->setVisible(!menuBar()->isVisible());
4654 if (ui_component == "frame") {
4655 int const l = contentsMargins().left();
4657 //are the frames in default state?
4658 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4660 #if QT_VERSION > 0x050903
4661 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4663 setContentsMargins(-2, -2, -2, -2);
4665 #if QT_VERSION > 0x050903
4666 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4668 setContentsMargins(0, 0, 0, 0);
4671 if (ui_component == "fullscreen") {
4679 void GuiView::toggleFullScreen()
4681 setWindowState(windowState() ^ Qt::WindowFullScreen);
4685 Buffer const * GuiView::updateInset(Inset const * inset)
4690 Buffer const * inset_buffer = &(inset->buffer());
4692 for (int i = 0; i != d.splitter_->count(); ++i) {
4693 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4696 Buffer const * buffer = &(wa->bufferView().buffer());
4697 if (inset_buffer == buffer)
4698 wa->scheduleRedraw(true);
4700 return inset_buffer;
4704 void GuiView::restartCaret()
4706 /* When we move around, or type, it's nice to be able to see
4707 * the caret immediately after the keypress.
4709 if (d.current_work_area_)
4710 d.current_work_area_->startBlinkingCaret();
4712 // Take this occasion to update the other GUI elements.
4718 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4720 if (d.current_work_area_)
4721 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4726 // This list should be kept in sync with the list of insets in
4727 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4728 // dialog should have the same name as the inset.
4729 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4730 // docs in LyXAction.cpp.
4732 char const * const dialognames[] = {
4734 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4735 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4736 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4737 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4738 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4739 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4740 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4741 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4743 char const * const * const end_dialognames =
4744 dialognames + (sizeof(dialognames) / sizeof(char *));
4748 cmpCStr(char const * name) : name_(name) {}
4749 bool operator()(char const * other) {
4750 return strcmp(other, name_) == 0;
4757 bool isValidName(string const & name)
4759 return find_if(dialognames, end_dialognames,
4760 cmpCStr(name.c_str())) != end_dialognames;
4766 void GuiView::resetDialogs()
4768 // Make sure that no LFUN uses any GuiView.
4769 guiApp->setCurrentView(nullptr);
4773 constructToolbars();
4774 guiApp->menus().fillMenuBar(menuBar(), this, false);
4775 d.layout_->updateContents(true);
4776 // Now update controls with current buffer.
4777 guiApp->setCurrentView(this);
4783 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4785 for (QObject * child: widget->children()) {
4786 if (child->inherits("QGroupBox")) {
4787 QGroupBox * box = (QGroupBox*) child;
4790 flatGroupBoxes(child, flag);
4796 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4798 if (!isValidName(name))
4801 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4803 if (it != d.dialogs_.end()) {
4805 it->second->hideView();
4806 return it->second.get();
4809 Dialog * dialog = build(name);
4810 d.dialogs_[name].reset(dialog);
4811 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4812 // Force a uniform style for group boxes
4813 // On Mac non-flat works better, on Linux flat is standard
4814 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4816 if (lyxrc.allow_geometry_session)
4817 dialog->restoreSession();
4824 void GuiView::showDialog(string const & name, string const & sdata,
4827 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4831 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4837 const string name = fromqstr(qname);
4838 const string sdata = fromqstr(qdata);
4842 Dialog * dialog = findOrBuild(name, false);
4844 bool const visible = dialog->isVisibleView();
4845 dialog->showData(sdata);
4846 if (currentBufferView())
4847 currentBufferView()->editInset(name, inset);
4848 // We only set the focus to the new dialog if it was not yet
4849 // visible in order not to change the existing previous behaviour
4851 // activateWindow is needed for floating dockviews
4852 dialog->asQWidget()->raise();
4853 dialog->asQWidget()->activateWindow();
4854 dialog->asQWidget()->setFocus();
4858 catch (ExceptionMessage const &) {
4866 bool GuiView::isDialogVisible(string const & name) const
4868 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4869 if (it == d.dialogs_.end())
4871 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4875 void GuiView::hideDialog(string const & name, Inset * inset)
4877 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4878 if (it == d.dialogs_.end())
4882 if (!currentBufferView())
4884 if (inset != currentBufferView()->editedInset(name))
4888 Dialog * const dialog = it->second.get();
4889 if (dialog->isVisibleView())
4891 if (currentBufferView())
4892 currentBufferView()->editInset(name, nullptr);
4896 void GuiView::disconnectDialog(string const & name)
4898 if (!isValidName(name))
4900 if (currentBufferView())
4901 currentBufferView()->editInset(name, nullptr);
4905 void GuiView::hideAll() const
4907 for(auto const & dlg_p : d.dialogs_)
4908 dlg_p.second->hideView();
4912 void GuiView::updateDialogs()
4914 for(auto const & dlg_p : d.dialogs_) {
4915 Dialog * dialog = dlg_p.second.get();
4917 if (dialog->needBufferOpen() && !documentBufferView())
4918 hideDialog(fromqstr(dialog->name()), nullptr);
4919 else if (dialog->isVisibleView())
4920 dialog->checkStatus();
4927 Dialog * createDialog(GuiView & lv, string const & name);
4929 // will be replaced by a proper factory...
4930 Dialog * createGuiAbout(GuiView & lv);
4931 Dialog * createGuiBibtex(GuiView & lv);
4932 Dialog * createGuiChanges(GuiView & lv);
4933 Dialog * createGuiCharacter(GuiView & lv);
4934 Dialog * createGuiCitation(GuiView & lv);
4935 Dialog * createGuiCompare(GuiView & lv);
4936 Dialog * createGuiCompareHistory(GuiView & lv);
4937 Dialog * createGuiDelimiter(GuiView & lv);
4938 Dialog * createGuiDocument(GuiView & lv);
4939 Dialog * createGuiErrorList(GuiView & lv);
4940 Dialog * createGuiExternal(GuiView & lv);
4941 Dialog * createGuiGraphics(GuiView & lv);
4942 Dialog * createGuiInclude(GuiView & lv);
4943 Dialog * createGuiIndex(GuiView & lv);
4944 Dialog * createGuiListings(GuiView & lv);
4945 Dialog * createGuiLog(GuiView & lv);
4946 Dialog * createGuiLyXFiles(GuiView & lv);
4947 Dialog * createGuiMathMatrix(GuiView & lv);
4948 Dialog * createGuiNote(GuiView & lv);
4949 Dialog * createGuiParagraph(GuiView & lv);
4950 Dialog * createGuiPhantom(GuiView & lv);
4951 Dialog * createGuiPreferences(GuiView & lv);
4952 Dialog * createGuiPrint(GuiView & lv);
4953 Dialog * createGuiPrintindex(GuiView & lv);
4954 Dialog * createGuiRef(GuiView & lv);
4955 Dialog * createGuiSearch(GuiView & lv);
4956 Dialog * createGuiSearchAdv(GuiView & lv);
4957 Dialog * createGuiSendTo(GuiView & lv);
4958 Dialog * createGuiShowFile(GuiView & lv);
4959 Dialog * createGuiSpellchecker(GuiView & lv);
4960 Dialog * createGuiSymbols(GuiView & lv);
4961 Dialog * createGuiTabularCreate(GuiView & lv);
4962 Dialog * createGuiTexInfo(GuiView & lv);
4963 Dialog * createGuiToc(GuiView & lv);
4964 Dialog * createGuiThesaurus(GuiView & lv);
4965 Dialog * createGuiViewSource(GuiView & lv);
4966 Dialog * createGuiWrap(GuiView & lv);
4967 Dialog * createGuiProgressView(GuiView & lv);
4971 Dialog * GuiView::build(string const & name)
4973 LASSERT(isValidName(name), return nullptr);
4975 Dialog * dialog = createDialog(*this, name);
4979 if (name == "aboutlyx")
4980 return createGuiAbout(*this);
4981 if (name == "bibtex")
4982 return createGuiBibtex(*this);
4983 if (name == "changes")
4984 return createGuiChanges(*this);
4985 if (name == "character")
4986 return createGuiCharacter(*this);
4987 if (name == "citation")
4988 return createGuiCitation(*this);
4989 if (name == "compare")
4990 return createGuiCompare(*this);
4991 if (name == "comparehistory")
4992 return createGuiCompareHistory(*this);
4993 if (name == "document")
4994 return createGuiDocument(*this);
4995 if (name == "errorlist")
4996 return createGuiErrorList(*this);
4997 if (name == "external")
4998 return createGuiExternal(*this);
5000 return createGuiShowFile(*this);
5001 if (name == "findreplace")
5002 return createGuiSearch(*this);
5003 if (name == "findreplaceadv")
5004 return createGuiSearchAdv(*this);
5005 if (name == "graphics")
5006 return createGuiGraphics(*this);
5007 if (name == "include")
5008 return createGuiInclude(*this);
5009 if (name == "index")
5010 return createGuiIndex(*this);
5011 if (name == "index_print")
5012 return createGuiPrintindex(*this);
5013 if (name == "listings")
5014 return createGuiListings(*this);
5016 return createGuiLog(*this);
5017 if (name == "lyxfiles")
5018 return createGuiLyXFiles(*this);
5019 if (name == "mathdelimiter")
5020 return createGuiDelimiter(*this);
5021 if (name == "mathmatrix")
5022 return createGuiMathMatrix(*this);
5024 return createGuiNote(*this);
5025 if (name == "paragraph")
5026 return createGuiParagraph(*this);
5027 if (name == "phantom")
5028 return createGuiPhantom(*this);
5029 if (name == "prefs")
5030 return createGuiPreferences(*this);
5032 return createGuiRef(*this);
5033 if (name == "sendto")
5034 return createGuiSendTo(*this);
5035 if (name == "spellchecker")
5036 return createGuiSpellchecker(*this);
5037 if (name == "symbols")
5038 return createGuiSymbols(*this);
5039 if (name == "tabularcreate")
5040 return createGuiTabularCreate(*this);
5041 if (name == "texinfo")
5042 return createGuiTexInfo(*this);
5043 if (name == "thesaurus")
5044 return createGuiThesaurus(*this);
5046 return createGuiToc(*this);
5047 if (name == "view-source")
5048 return createGuiViewSource(*this);
5050 return createGuiWrap(*this);
5051 if (name == "progress")
5052 return createGuiProgressView(*this);
5058 SEMenu::SEMenu(QWidget * parent)
5060 QAction * action = addAction(qt_("Disable Shell Escape"));
5061 connect(action, SIGNAL(triggered()),
5062 parent, SLOT(disableShellEscape()));
5065 } // namespace frontend
5068 #include "moc_GuiView.cpp"