3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
104 #include <QShowEvent>
106 #include <QStackedWidget>
107 #include <QStatusBar>
108 #include <QSvgRenderer>
109 #include <QtConcurrentRun>
112 #include <QWindowStateChangeEvent>
115 // sync with GuiAlert.cpp
116 #define EXPORT_in_THREAD 1
119 #include "support/bind.h"
123 #ifdef HAVE_SYS_TIME_H
124 # include <sys/time.h>
132 using namespace lyx::support;
136 using support::addExtension;
137 using support::changeExtension;
138 using support::removeExtension;
144 class BackgroundWidget : public QWidget
147 BackgroundWidget(int width, int height)
148 : width_(width), height_(height)
150 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
151 if (!lyxrc.show_banner)
153 /// The text to be written on top of the pixmap
154 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
155 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
156 /// The text to be written on top of the pixmap
157 QString const text = lyx_version ?
158 qt_("version ") + lyx_version : qt_("unknown version");
159 #if QT_VERSION >= 0x050000
160 QString imagedir = "images/";
161 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
162 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
163 if (svgRenderer.isValid()) {
164 splash_ = QPixmap(splashSize());
165 QPainter painter(&splash_);
166 svgRenderer.render(&painter);
167 splash_.setDevicePixelRatio(pixelRatio());
169 splash_ = getPixmap("images/", "banner", "png");
172 splash_ = getPixmap("images/", "banner", "svgz,png");
175 QPainter pain(&splash_);
176 pain.setPen(QColor(0, 0, 0));
177 qreal const fsize = fontSize();
180 qreal locscale = htextsize.toFloat(&ok);
183 QPointF const position = textPosition(false);
184 QPointF const hposition = textPosition(true);
185 QRectF const hrect(hposition, splashSize());
187 "widget pixel ratio: " << pixelRatio() <<
188 " splash pixel ratio: " << splashPixelRatio() <<
189 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
191 // The font used to display the version info
192 font.setStyleHint(QFont::SansSerif);
193 font.setWeight(QFont::Bold);
194 font.setPointSizeF(fsize);
196 pain.drawText(position, text);
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Normal);
200 font.setPointSizeF(hfsize);
201 // Check how long the logo gets with the current font
202 // and adapt if the font is running wider than what
204 GuiFontMetrics fm(font);
205 // Split the title into lines to measure the longest line
206 // in the current l7n.
207 QStringList titlesegs = htext.split('\n');
209 int hline = fm.maxHeight();
210 QStringList::const_iterator sit;
211 for (QString const & seg : titlesegs) {
212 if (fm.width(seg) > wline)
213 wline = fm.width(seg);
215 // The longest line in the reference font (for English)
216 // is 180. Calculate scale factor from that.
217 double const wscale = wline > 0 ? (180.0 / wline) : 1;
218 // Now do the same for the height (necessary for condensed fonts)
219 double const hscale = (34.0 / hline);
220 // take the lower of the two scale factors.
221 double const scale = min(wscale, hscale);
222 // Now rescale. Also consider l7n's offset factor.
223 font.setPointSizeF(hfsize * scale * locscale);
226 pain.drawText(hrect, Qt::AlignLeft, htext);
227 setFocusPolicy(Qt::StrongFocus);
230 void paintEvent(QPaintEvent *) override
232 int const w = width_;
233 int const h = height_;
234 int const x = (width() - w) / 2;
235 int const y = (height() - h) / 2;
237 "widget pixel ratio: " << pixelRatio() <<
238 " splash pixel ratio: " << splashPixelRatio() <<
239 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
241 pain.drawPixmap(x, y, w, h, splash_);
244 void keyPressEvent(QKeyEvent * ev) override
247 setKeySymbol(&sym, ev);
249 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
261 /// Current ratio between physical pixels and device-independent pixels
262 double pixelRatio() const {
263 #if QT_VERSION >= 0x050000
264 return qt_scale_factor * devicePixelRatio();
270 qreal fontSize() const {
271 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
274 QPointF textPosition(bool const heading) const {
275 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
276 : QPointF(width_/2 - 18, height_/2 + 45);
279 QSize splashSize() const {
281 static_cast<unsigned int>(width_ * pixelRatio()),
282 static_cast<unsigned int>(height_ * pixelRatio()));
285 /// Ratio between physical pixels and device-independent pixels of splash image
286 double splashPixelRatio() const {
287 #if QT_VERSION >= 0x050000
288 return splash_.devicePixelRatio();
296 /// Toolbar store providing access to individual toolbars by name.
297 typedef map<string, GuiToolbar *> ToolbarMap;
299 typedef shared_ptr<Dialog> DialogPtr;
304 class GuiView::GuiViewPrivate
307 GuiViewPrivate(GuiViewPrivate const &);
308 void operator=(GuiViewPrivate const &);
310 GuiViewPrivate(GuiView * gv)
311 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
312 layout_(nullptr), autosave_timeout_(5000),
315 // hardcode here the platform specific icon size
316 smallIconSize = 16; // scaling problems
317 normalIconSize = 20; // ok, default if iconsize.png is missing
318 bigIconSize = 26; // better for some math icons
319 hugeIconSize = 32; // better for hires displays
322 // if it exists, use width of iconsize.png as normal size
323 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
324 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
326 QImage image(toqstr(fn.absFileName()));
327 if (image.width() < int(smallIconSize))
328 normalIconSize = smallIconSize;
329 else if (image.width() > int(giantIconSize))
330 normalIconSize = giantIconSize;
332 normalIconSize = image.width();
335 splitter_ = new QSplitter;
336 bg_widget_ = new BackgroundWidget(400, 250);
337 stack_widget_ = new QStackedWidget;
338 stack_widget_->addWidget(bg_widget_);
339 stack_widget_->addWidget(splitter_);
342 // TODO cleanup, remove the singleton, handle multiple Windows?
343 progress_ = ProgressInterface::instance();
344 if (!dynamic_cast<GuiProgress*>(progress_)) {
345 progress_ = new GuiProgress; // TODO who deletes it
346 ProgressInterface::setInstance(progress_);
349 dynamic_cast<GuiProgress*>(progress_),
350 SIGNAL(updateStatusBarMessage(QString const&)),
351 gv, SLOT(updateStatusBarMessage(QString const&)));
353 dynamic_cast<GuiProgress*>(progress_),
354 SIGNAL(clearMessageText()),
355 gv, SLOT(clearMessageText()));
362 delete stack_widget_;
367 stack_widget_->setCurrentWidget(bg_widget_);
368 bg_widget_->setUpdatesEnabled(true);
369 bg_widget_->setFocus();
372 int tabWorkAreaCount()
374 return splitter_->count();
377 TabWorkArea * tabWorkArea(int i)
379 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
382 TabWorkArea * currentTabWorkArea()
384 int areas = tabWorkAreaCount();
386 // The first TabWorkArea is always the first one, if any.
387 return tabWorkArea(0);
389 for (int i = 0; i != areas; ++i) {
390 TabWorkArea * twa = tabWorkArea(i);
391 if (current_main_work_area_ == twa->currentWorkArea())
395 // None has the focus so we just take the first one.
396 return tabWorkArea(0);
399 int countWorkAreasOf(Buffer & buf)
401 int areas = tabWorkAreaCount();
403 for (int i = 0; i != areas; ++i) {
404 TabWorkArea * twa = tabWorkArea(i);
405 if (twa->workArea(buf))
411 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
413 if (processing_thread_watcher_.isRunning()) {
414 // we prefer to cancel this preview in order to keep a snappy
418 processing_thread_watcher_.setFuture(f);
421 QSize iconSize(docstring const & icon_size)
424 if (icon_size == "small")
425 size = smallIconSize;
426 else if (icon_size == "normal")
427 size = normalIconSize;
428 else if (icon_size == "big")
430 else if (icon_size == "huge")
432 else if (icon_size == "giant")
433 size = giantIconSize;
435 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
437 if (size < smallIconSize)
438 size = smallIconSize;
440 return QSize(size, size);
443 QSize iconSize(QString const & icon_size)
445 return iconSize(qstring_to_ucs4(icon_size));
448 string & iconSize(QSize const & qsize)
450 LATTEST(qsize.width() == qsize.height());
452 static string icon_size;
454 unsigned int size = qsize.width();
456 if (size < smallIconSize)
457 size = smallIconSize;
459 if (size == smallIconSize)
461 else if (size == normalIconSize)
462 icon_size = "normal";
463 else if (size == bigIconSize)
465 else if (size == hugeIconSize)
467 else if (size == giantIconSize)
470 icon_size = convert<string>(size);
475 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
476 Buffer * buffer, string const & format);
477 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
484 static Buffer::ExportStatus runAndDestroy(const T& func,
485 Buffer const * orig, Buffer * buffer, string const & format);
487 // TODO syncFunc/previewFunc: use bind
488 bool asyncBufferProcessing(string const & argument,
489 Buffer const * used_buffer,
490 docstring const & msg,
491 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
492 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
493 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
494 bool allow_async, bool use_tmpdir = false);
496 QVector<GuiWorkArea*> guiWorkAreas();
500 GuiWorkArea * current_work_area_;
501 GuiWorkArea * current_main_work_area_;
502 QSplitter * splitter_;
503 QStackedWidget * stack_widget_;
504 BackgroundWidget * bg_widget_;
506 ToolbarMap toolbars_;
507 ProgressInterface* progress_;
508 /// The main layout box.
510 * \warning Don't Delete! The layout box is actually owned by
511 * whichever toolbar contains it. All the GuiView class needs is a
512 * means of accessing it.
514 * FIXME: replace that with a proper model so that we are not limited
515 * to only one dialog.
520 map<string, DialogPtr> dialogs_;
523 QTimer statusbar_timer_;
524 /// auto-saving of buffers
525 Timeout autosave_timeout_;
528 TocModels toc_models_;
531 QFutureWatcher<docstring> autosave_watcher_;
532 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
534 string last_export_format;
535 string processing_format;
537 static QSet<Buffer const *> busyBuffers;
539 unsigned int smallIconSize;
540 unsigned int normalIconSize;
541 unsigned int bigIconSize;
542 unsigned int hugeIconSize;
543 unsigned int giantIconSize;
545 /// flag against a race condition due to multiclicks, see bug #1119
549 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
552 GuiView::GuiView(int id)
553 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
554 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
557 connect(this, SIGNAL(bufferViewChanged()),
558 this, SLOT(onBufferViewChanged()));
560 // GuiToolbars *must* be initialised before the menu bar.
561 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
564 // set ourself as the current view. This is needed for the menu bar
565 // filling, at least for the static special menu item on Mac. Otherwise
566 // they are greyed out.
567 guiApp->setCurrentView(this);
569 // Fill up the menu bar.
570 guiApp->menus().fillMenuBar(menuBar(), this, true);
572 setCentralWidget(d.stack_widget_);
574 // Start autosave timer
575 if (lyxrc.autosave) {
576 // The connection is closed when this is destroyed.
577 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
578 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
579 d.autosave_timeout_.start();
581 connect(&d.statusbar_timer_, SIGNAL(timeout()),
582 this, SLOT(clearMessage()));
584 // We don't want to keep the window in memory if it is closed.
585 setAttribute(Qt::WA_DeleteOnClose, true);
587 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
588 // QIcon::fromTheme was introduced in Qt 4.6
589 #if (QT_VERSION >= 0x040600)
590 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
591 // since the icon is provided in the application bundle. We use a themed
592 // version when available and use the bundled one as fallback.
593 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
595 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
601 // use tabbed dock area for multiple docks
602 // (such as "source" and "messages")
603 setDockOptions(QMainWindow::ForceTabbedDocks);
606 // use document mode tabs on docks
607 setDocumentMode(true);
611 setAcceptDrops(true);
613 // add busy indicator to statusbar
614 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
615 statusBar()->addPermanentWidget(busylabel);
616 search_mode mode = theGuiApp()->imageSearchMode();
617 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
618 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
619 busylabel->setMovie(busyanim);
623 connect(&d.processing_thread_watcher_, SIGNAL(started()),
624 busylabel, SLOT(show()));
625 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
626 busylabel, SLOT(hide()));
627 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
629 QFontMetrics const fm(statusBar()->fontMetrics());
630 int const iconheight = max(int(d.normalIconSize), fm.height());
631 QSize const iconsize(iconheight, iconheight);
633 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
634 shell_escape_ = new QLabel(statusBar());
635 shell_escape_->setPixmap(shellescape);
636 shell_escape_->setScaledContents(true);
637 shell_escape_->setAlignment(Qt::AlignCenter);
638 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
639 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
640 "external commands for this document. "
641 "Right click to change."));
642 SEMenu * menu = new SEMenu(this);
643 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
644 menu, SLOT(showMenu(QPoint)));
645 shell_escape_->hide();
646 statusBar()->addPermanentWidget(shell_escape_);
648 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
649 read_only_ = new QLabel(statusBar());
650 read_only_->setPixmap(readonly);
651 read_only_->setScaledContents(true);
652 read_only_->setAlignment(Qt::AlignCenter);
654 statusBar()->addPermanentWidget(read_only_);
656 version_control_ = new QLabel(statusBar());
657 version_control_->setAlignment(Qt::AlignCenter);
658 version_control_->setFrameStyle(QFrame::StyledPanel);
659 version_control_->hide();
660 statusBar()->addPermanentWidget(version_control_);
662 statusBar()->setSizeGripEnabled(true);
665 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
666 SLOT(autoSaveThreadFinished()));
668 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
669 SLOT(processingThreadStarted()));
670 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
671 SLOT(processingThreadFinished()));
673 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
674 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
676 // set custom application bars context menu, e.g. tool bar and menu bar
677 setContextMenuPolicy(Qt::CustomContextMenu);
678 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
679 SLOT(toolBarPopup(const QPoint &)));
681 // Forbid too small unresizable window because it can happen
682 // with some window manager under X11.
683 setMinimumSize(300, 200);
685 if (lyxrc.allow_geometry_session) {
686 // Now take care of session management.
691 // no session handling, default to a sane size.
692 setGeometry(50, 50, 690, 510);
695 // clear session data if any.
697 settings.remove("views");
707 void GuiView::disableShellEscape()
709 BufferView * bv = documentBufferView();
712 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
713 bv->buffer().params().shell_escape = false;
714 bv->processUpdateFlags(Update::Force);
718 void GuiView::checkCancelBackground()
720 docstring const ttl = _("Cancel Export?");
721 docstring const msg = _("Do you want to cancel the background export process?");
723 Alert::prompt(ttl, msg, 1, 1,
724 _("&Cancel export"), _("Co&ntinue"));
726 Systemcall::killscript();
730 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
732 QVector<GuiWorkArea*> areas;
733 for (int i = 0; i < tabWorkAreaCount(); i++) {
734 TabWorkArea* ta = tabWorkArea(i);
735 for (int u = 0; u < ta->count(); u++) {
736 areas << ta->workArea(u);
742 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
743 string const & format)
745 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
748 case Buffer::ExportSuccess:
749 msg = bformat(_("Successful export to format: %1$s"), fmt);
751 case Buffer::ExportCancel:
752 msg = _("Document export cancelled.");
754 case Buffer::ExportError:
755 case Buffer::ExportNoPathToFormat:
756 case Buffer::ExportTexPathHasSpaces:
757 case Buffer::ExportConverterError:
758 msg = bformat(_("Error while exporting format: %1$s"), fmt);
760 case Buffer::PreviewSuccess:
761 msg = bformat(_("Successful preview of format: %1$s"), fmt);
763 case Buffer::PreviewError:
764 msg = bformat(_("Error while previewing format: %1$s"), fmt);
766 case Buffer::ExportKilled:
767 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
774 void GuiView::processingThreadStarted()
779 void GuiView::processingThreadFinished()
781 QFutureWatcher<Buffer::ExportStatus> const * watcher =
782 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
784 Buffer::ExportStatus const status = watcher->result();
785 handleExportStatus(this, status, d.processing_format);
788 BufferView const * const bv = currentBufferView();
789 if (bv && !bv->buffer().errorList("Export").empty()) {
794 bool const error = (status != Buffer::ExportSuccess &&
795 status != Buffer::PreviewSuccess &&
796 status != Buffer::ExportCancel);
798 ErrorList & el = bv->buffer().errorList(d.last_export_format);
799 // at this point, we do not know if buffer-view or
800 // master-buffer-view was called. If there was an export error,
801 // and the current buffer's error log is empty, we guess that
802 // it must be master-buffer-view that was called so we set
804 errors(d.last_export_format, el.empty());
809 void GuiView::autoSaveThreadFinished()
811 QFutureWatcher<docstring> const * watcher =
812 static_cast<QFutureWatcher<docstring> const *>(sender());
813 message(watcher->result());
818 void GuiView::saveLayout() const
821 settings.setValue("zoom_ratio", zoom_ratio_);
822 settings.setValue("devel_mode", devel_mode_);
823 settings.beginGroup("views");
824 settings.beginGroup(QString::number(id_));
825 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
826 settings.setValue("pos", pos());
827 settings.setValue("size", size());
829 settings.setValue("geometry", saveGeometry());
830 settings.setValue("layout", saveState(0));
831 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
835 void GuiView::saveUISettings() const
839 // Save the toolbar private states
840 for (auto const & tb_p : d.toolbars_)
841 tb_p.second->saveSession(settings);
842 // Now take care of all other dialogs
843 for (auto const & dlg_p : d.dialogs_)
844 dlg_p.second->saveSession(settings);
848 bool GuiView::restoreLayout()
851 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
852 // Actual zoom value: default zoom + fractional offset
853 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
854 if (zoom < static_cast<int>(zoom_min_))
856 lyxrc.currentZoom = zoom;
857 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
858 settings.beginGroup("views");
859 settings.beginGroup(QString::number(id_));
860 QString const icon_key = "icon_size";
861 if (!settings.contains(icon_key))
864 //code below is skipped when when ~/.config/LyX is (re)created
865 setIconSize(d.iconSize(settings.value(icon_key).toString()));
867 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
868 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
869 QSize size = settings.value("size", QSize(690, 510)).toSize();
873 // Work-around for bug #6034: the window ends up in an undetermined
874 // state when trying to restore a maximized window when it is
875 // already maximized.
876 if (!(windowState() & Qt::WindowMaximized))
877 if (!restoreGeometry(settings.value("geometry").toByteArray()))
878 setGeometry(50, 50, 690, 510);
881 // Make sure layout is correctly oriented.
882 setLayoutDirection(qApp->layoutDirection());
884 // Allow the toc and view-source dock widget to be restored if needed.
886 if ((dialog = findOrBuild("toc", true)))
887 // see bug 5082. At least setup title and enabled state.
888 // Visibility will be adjusted by restoreState below.
889 dialog->prepareView();
890 if ((dialog = findOrBuild("view-source", true)))
891 dialog->prepareView();
892 if ((dialog = findOrBuild("progress", true)))
893 dialog->prepareView();
895 if (!restoreState(settings.value("layout").toByteArray(), 0))
898 // init the toolbars that have not been restored
899 for (auto const & tb_p : guiApp->toolbars()) {
900 GuiToolbar * tb = toolbar(tb_p.name);
901 if (tb && !tb->isRestored())
902 initToolbar(tb_p.name);
905 // update lock (all) toolbars positions
906 updateLockToolbars();
913 GuiToolbar * GuiView::toolbar(string const & name)
915 ToolbarMap::iterator it = d.toolbars_.find(name);
916 if (it != d.toolbars_.end())
919 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
924 void GuiView::updateLockToolbars()
926 toolbarsMovable_ = false;
927 for (ToolbarInfo const & info : guiApp->toolbars()) {
928 GuiToolbar * tb = toolbar(info.name);
929 if (tb && tb->isMovable())
930 toolbarsMovable_ = true;
935 void GuiView::constructToolbars()
937 for (auto const & tb_p : d.toolbars_)
941 // I don't like doing this here, but the standard toolbar
942 // destroys this object when it's destroyed itself (vfr)
943 d.layout_ = new LayoutBox(*this);
944 d.stack_widget_->addWidget(d.layout_);
945 d.layout_->move(0,0);
947 // extracts the toolbars from the backend
948 for (ToolbarInfo const & inf : guiApp->toolbars())
949 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
953 void GuiView::initToolbars()
955 // extracts the toolbars from the backend
956 for (ToolbarInfo const & inf : guiApp->toolbars())
957 initToolbar(inf.name);
961 void GuiView::initToolbar(string const & name)
963 GuiToolbar * tb = toolbar(name);
966 int const visibility = guiApp->toolbars().defaultVisibility(name);
967 bool newline = !(visibility & Toolbars::SAMEROW);
968 tb->setVisible(false);
969 tb->setVisibility(visibility);
971 if (visibility & Toolbars::TOP) {
973 addToolBarBreak(Qt::TopToolBarArea);
974 addToolBar(Qt::TopToolBarArea, tb);
977 if (visibility & Toolbars::BOTTOM) {
979 addToolBarBreak(Qt::BottomToolBarArea);
980 addToolBar(Qt::BottomToolBarArea, tb);
983 if (visibility & Toolbars::LEFT) {
985 addToolBarBreak(Qt::LeftToolBarArea);
986 addToolBar(Qt::LeftToolBarArea, tb);
989 if (visibility & Toolbars::RIGHT) {
991 addToolBarBreak(Qt::RightToolBarArea);
992 addToolBar(Qt::RightToolBarArea, tb);
995 if (visibility & Toolbars::ON)
996 tb->setVisible(true);
998 tb->setMovable(true);
1002 TocModels & GuiView::tocModels()
1004 return d.toc_models_;
1008 void GuiView::setFocus()
1010 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1011 QMainWindow::setFocus();
1015 bool GuiView::hasFocus() const
1017 if (currentWorkArea())
1018 return currentWorkArea()->hasFocus();
1019 if (currentMainWorkArea())
1020 return currentMainWorkArea()->hasFocus();
1021 return d.bg_widget_->hasFocus();
1025 void GuiView::focusInEvent(QFocusEvent * e)
1027 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1028 QMainWindow::focusInEvent(e);
1029 // Make sure guiApp points to the correct view.
1030 guiApp->setCurrentView(this);
1031 if (currentWorkArea())
1032 currentWorkArea()->setFocus();
1033 else if (currentMainWorkArea())
1034 currentMainWorkArea()->setFocus();
1036 d.bg_widget_->setFocus();
1040 void GuiView::showEvent(QShowEvent * e)
1042 LYXERR(Debug::GUI, "Passed Geometry "
1043 << size().height() << "x" << size().width()
1044 << "+" << pos().x() << "+" << pos().y());
1046 if (d.splitter_->count() == 0)
1047 // No work area, switch to the background widget.
1051 QMainWindow::showEvent(e);
1055 bool GuiView::closeScheduled()
1062 bool GuiView::prepareAllBuffersForLogout()
1064 Buffer * first = theBufferList().first();
1068 // First, iterate over all buffers and ask the users if unsaved
1069 // changes should be saved.
1070 // We cannot use a for loop as the buffer list cycles.
1073 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1075 b = theBufferList().next(b);
1076 } while (b != first);
1078 // Next, save session state
1079 // When a view/window was closed before without quitting LyX, there
1080 // are already entries in the lastOpened list.
1081 theSession().lastOpened().clear();
1088 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1089 ** is responsibility of the container (e.g., dialog)
1091 void GuiView::closeEvent(QCloseEvent * close_event)
1093 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1095 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1096 Alert::warning(_("Exit LyX"),
1097 _("LyX could not be closed because documents are being processed by LyX."));
1098 close_event->setAccepted(false);
1102 // If the user pressed the x (so we didn't call closeView
1103 // programmatically), we want to clear all existing entries.
1105 theSession().lastOpened().clear();
1110 // it can happen that this event arrives without selecting the view,
1111 // e.g. when clicking the close button on a background window.
1113 if (!closeWorkAreaAll()) {
1115 close_event->ignore();
1119 // Make sure that nothing will use this to be closed View.
1120 guiApp->unregisterView(this);
1122 if (isFullScreen()) {
1123 // Switch off fullscreen before closing.
1128 // Make sure the timer time out will not trigger a statusbar update.
1129 d.statusbar_timer_.stop();
1131 // Saving fullscreen requires additional tweaks in the toolbar code.
1132 // It wouldn't also work under linux natively.
1133 if (lyxrc.allow_geometry_session) {
1138 close_event->accept();
1142 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1144 if (event->mimeData()->hasUrls())
1146 /// \todo Ask lyx-devel is this is enough:
1147 /// if (event->mimeData()->hasFormat("text/plain"))
1148 /// event->acceptProposedAction();
1152 void GuiView::dropEvent(QDropEvent * event)
1154 QList<QUrl> files = event->mimeData()->urls();
1155 if (files.isEmpty())
1158 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1159 for (int i = 0; i != files.size(); ++i) {
1160 string const file = os::internal_path(fromqstr(
1161 files.at(i).toLocalFile()));
1165 string const ext = support::getExtension(file);
1166 vector<const Format *> found_formats;
1168 // Find all formats that have the correct extension.
1169 for (const Format * fmt : theConverters().importableFormats())
1170 if (fmt->hasExtension(ext))
1171 found_formats.push_back(fmt);
1174 if (!found_formats.empty()) {
1175 if (found_formats.size() > 1) {
1176 //FIXME: show a dialog to choose the correct importable format
1177 LYXERR(Debug::FILES,
1178 "Multiple importable formats found, selecting first");
1180 string const arg = found_formats[0]->name() + " " + file;
1181 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1184 //FIXME: do we have to explicitly check whether it's a lyx file?
1185 LYXERR(Debug::FILES,
1186 "No formats found, trying to open it as a lyx file");
1187 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1189 // add the functions to the queue
1190 guiApp->addToFuncRequestQueue(cmd);
1193 // now process the collected functions. We perform the events
1194 // asynchronously. This prevents potential problems in case the
1195 // BufferView is closed within an event.
1196 guiApp->processFuncRequestQueueAsync();
1200 void GuiView::message(docstring const & str)
1202 if (ForkedProcess::iAmAChild())
1205 // call is moved to GUI-thread by GuiProgress
1206 d.progress_->appendMessage(toqstr(str));
1210 void GuiView::clearMessageText()
1212 message(docstring());
1216 void GuiView::updateStatusBarMessage(QString const & str)
1218 statusBar()->showMessage(str);
1219 d.statusbar_timer_.stop();
1220 d.statusbar_timer_.start(3000);
1224 void GuiView::clearMessage()
1226 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1227 // the hasFocus function mostly returns false, even if the focus is on
1228 // a workarea in this view.
1232 d.statusbar_timer_.stop();
1236 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1238 if (wa != d.current_work_area_
1239 || wa->bufferView().buffer().isInternal())
1241 Buffer const & buf = wa->bufferView().buffer();
1242 // Set the windows title
1243 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1244 if (buf.notifiesExternalModification()) {
1245 title = bformat(_("%1$s (modified externally)"), title);
1246 // If the external modification status has changed, then maybe the status of
1247 // buffer-save has changed too.
1251 title += from_ascii(" - LyX");
1253 setWindowTitle(toqstr(title));
1254 // Sets the path for the window: this is used by OSX to
1255 // allow a context click on the title bar showing a menu
1256 // with the path up to the file
1257 setWindowFilePath(toqstr(buf.absFileName()));
1258 // Tell Qt whether the current document is changed
1259 setWindowModified(!buf.isClean());
1261 if (buf.params().shell_escape)
1262 shell_escape_->show();
1264 shell_escape_->hide();
1266 if (buf.hasReadonlyFlag())
1271 if (buf.lyxvc().inUse()) {
1272 version_control_->show();
1273 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1275 version_control_->hide();
1279 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1281 if (d.current_work_area_)
1282 // disconnect the current work area from all slots
1283 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1285 disconnectBufferView();
1286 connectBufferView(wa->bufferView());
1287 connectBuffer(wa->bufferView().buffer());
1288 d.current_work_area_ = wa;
1289 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1290 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1291 QObject::connect(wa, SIGNAL(busy(bool)),
1292 this, SLOT(setBusy(bool)));
1293 // connection of a signal to a signal
1294 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1295 this, SIGNAL(bufferViewChanged()));
1296 Q_EMIT updateWindowTitle(wa);
1297 Q_EMIT bufferViewChanged();
1301 void GuiView::onBufferViewChanged()
1304 // Buffer-dependent dialogs must be updated. This is done here because
1305 // some dialogs require buffer()->text.
1310 void GuiView::on_lastWorkAreaRemoved()
1313 // We already are in a close event. Nothing more to do.
1316 if (d.splitter_->count() > 1)
1317 // We have a splitter so don't close anything.
1320 // Reset and updates the dialogs.
1321 Q_EMIT bufferViewChanged();
1326 if (lyxrc.open_buffers_in_tabs)
1327 // Nothing more to do, the window should stay open.
1330 if (guiApp->viewIds().size() > 1) {
1336 // On Mac we also close the last window because the application stay
1337 // resident in memory. On other platforms we don't close the last
1338 // window because this would quit the application.
1344 void GuiView::updateStatusBar()
1346 // let the user see the explicit message
1347 if (d.statusbar_timer_.isActive())
1354 void GuiView::showMessage()
1358 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1359 if (msg.isEmpty()) {
1360 BufferView const * bv = currentBufferView();
1362 msg = toqstr(bv->cursor().currentState(devel_mode_));
1364 msg = qt_("Welcome to LyX!");
1366 statusBar()->showMessage(msg);
1370 bool GuiView::event(QEvent * e)
1374 // Useful debug code:
1375 //case QEvent::ActivationChange:
1376 //case QEvent::WindowDeactivate:
1377 //case QEvent::Paint:
1378 //case QEvent::Enter:
1379 //case QEvent::Leave:
1380 //case QEvent::HoverEnter:
1381 //case QEvent::HoverLeave:
1382 //case QEvent::HoverMove:
1383 //case QEvent::StatusTip:
1384 //case QEvent::DragEnter:
1385 //case QEvent::DragLeave:
1386 //case QEvent::Drop:
1389 case QEvent::WindowStateChange: {
1390 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1391 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1392 bool result = QMainWindow::event(e);
1393 bool nfstate = (windowState() & Qt::WindowFullScreen);
1394 if (!ofstate && nfstate) {
1395 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1396 // switch to full-screen state
1397 if (lyxrc.full_screen_statusbar)
1398 statusBar()->hide();
1399 if (lyxrc.full_screen_menubar)
1401 if (lyxrc.full_screen_toolbars) {
1402 for (auto const & tb_p : d.toolbars_)
1403 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1404 tb_p.second->hide();
1406 for (int i = 0; i != d.splitter_->count(); ++i)
1407 d.tabWorkArea(i)->setFullScreen(true);
1408 #if QT_VERSION > 0x050903
1409 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1410 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1412 setContentsMargins(-2, -2, -2, -2);
1414 hideDialogs("prefs", nullptr);
1415 } else if (ofstate && !nfstate) {
1416 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1417 // switch back from full-screen state
1418 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1419 statusBar()->show();
1420 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1422 if (lyxrc.full_screen_toolbars) {
1423 for (auto const & tb_p : d.toolbars_)
1424 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1425 tb_p.second->show();
1428 for (int i = 0; i != d.splitter_->count(); ++i)
1429 d.tabWorkArea(i)->setFullScreen(false);
1430 #if QT_VERSION > 0x050903
1431 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1433 setContentsMargins(0, 0, 0, 0);
1437 case QEvent::WindowActivate: {
1438 GuiView * old_view = guiApp->currentView();
1439 if (this == old_view) {
1441 return QMainWindow::event(e);
1443 if (old_view && old_view->currentBufferView()) {
1444 // save current selection to the selection buffer to allow
1445 // middle-button paste in this window.
1446 cap::saveSelection(old_view->currentBufferView()->cursor());
1448 guiApp->setCurrentView(this);
1449 if (d.current_work_area_)
1450 on_currentWorkAreaChanged(d.current_work_area_);
1454 return QMainWindow::event(e);
1457 case QEvent::ShortcutOverride: {
1459 if (isFullScreen() && menuBar()->isHidden()) {
1460 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1461 // FIXME: we should also try to detect special LyX shortcut such as
1462 // Alt-P and Alt-M. Right now there is a hack in
1463 // GuiWorkArea::processKeySym() that hides again the menubar for
1465 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1467 return QMainWindow::event(e);
1470 return QMainWindow::event(e);
1474 return QMainWindow::event(e);
1478 void GuiView::resetWindowTitle()
1480 setWindowTitle(qt_("LyX"));
1483 bool GuiView::focusNextPrevChild(bool /*next*/)
1490 bool GuiView::busy() const
1496 void GuiView::setBusy(bool busy)
1498 bool const busy_before = busy_ > 0;
1499 busy ? ++busy_ : --busy_;
1500 if ((busy_ > 0) == busy_before)
1501 // busy state didn't change
1505 QApplication::setOverrideCursor(Qt::WaitCursor);
1508 QApplication::restoreOverrideCursor();
1513 void GuiView::resetCommandExecute()
1515 command_execute_ = false;
1520 double GuiView::pixelRatio() const
1522 #if QT_VERSION >= 0x050000
1523 return qt_scale_factor * devicePixelRatio();
1530 GuiWorkArea * GuiView::workArea(int index)
1532 if (TabWorkArea * twa = d.currentTabWorkArea())
1533 if (index < twa->count())
1534 return twa->workArea(index);
1539 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1541 if (currentWorkArea()
1542 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1543 return currentWorkArea();
1544 if (TabWorkArea * twa = d.currentTabWorkArea())
1545 return twa->workArea(buffer);
1550 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1552 // Automatically create a TabWorkArea if there are none yet.
1553 TabWorkArea * tab_widget = d.splitter_->count()
1554 ? d.currentTabWorkArea() : addTabWorkArea();
1555 return tab_widget->addWorkArea(buffer, *this);
1559 TabWorkArea * GuiView::addTabWorkArea()
1561 TabWorkArea * twa = new TabWorkArea;
1562 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1563 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1564 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1565 this, SLOT(on_lastWorkAreaRemoved()));
1567 d.splitter_->addWidget(twa);
1568 d.stack_widget_->setCurrentWidget(d.splitter_);
1573 GuiWorkArea const * GuiView::currentWorkArea() const
1575 return d.current_work_area_;
1579 GuiWorkArea * GuiView::currentWorkArea()
1581 return d.current_work_area_;
1585 GuiWorkArea const * GuiView::currentMainWorkArea() const
1587 if (!d.currentTabWorkArea())
1589 return d.currentTabWorkArea()->currentWorkArea();
1593 GuiWorkArea * GuiView::currentMainWorkArea()
1595 if (!d.currentTabWorkArea())
1597 return d.currentTabWorkArea()->currentWorkArea();
1601 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1603 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1605 d.current_work_area_ = nullptr;
1607 Q_EMIT bufferViewChanged();
1611 // FIXME: I've no clue why this is here and why it accesses
1612 // theGuiApp()->currentView, which might be 0 (bug 6464).
1613 // See also 27525 (vfr).
1614 if (theGuiApp()->currentView() == this
1615 && theGuiApp()->currentView()->currentWorkArea() == wa)
1618 if (currentBufferView())
1619 cap::saveSelection(currentBufferView()->cursor());
1621 theGuiApp()->setCurrentView(this);
1622 d.current_work_area_ = wa;
1624 // We need to reset this now, because it will need to be
1625 // right if the tabWorkArea gets reset in the for loop. We
1626 // will change it back if we aren't in that case.
1627 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1628 d.current_main_work_area_ = wa;
1630 for (int i = 0; i != d.splitter_->count(); ++i) {
1631 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1632 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1633 << ", Current main wa: " << currentMainWorkArea());
1638 d.current_main_work_area_ = old_cmwa;
1640 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1641 on_currentWorkAreaChanged(wa);
1642 BufferView & bv = wa->bufferView();
1643 bv.cursor().fixIfBroken();
1645 wa->setUpdatesEnabled(true);
1646 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1650 void GuiView::removeWorkArea(GuiWorkArea * wa)
1652 LASSERT(wa, return);
1653 if (wa == d.current_work_area_) {
1655 disconnectBufferView();
1656 d.current_work_area_ = nullptr;
1657 d.current_main_work_area_ = nullptr;
1660 bool found_twa = false;
1661 for (int i = 0; i != d.splitter_->count(); ++i) {
1662 TabWorkArea * twa = d.tabWorkArea(i);
1663 if (twa->removeWorkArea(wa)) {
1664 // Found in this tab group, and deleted the GuiWorkArea.
1666 if (twa->count() != 0) {
1667 if (d.current_work_area_ == nullptr)
1668 // This means that we are closing the current GuiWorkArea, so
1669 // switch to the next GuiWorkArea in the found TabWorkArea.
1670 setCurrentWorkArea(twa->currentWorkArea());
1672 // No more WorkAreas in this tab group, so delete it.
1679 // It is not a tabbed work area (i.e., the search work area), so it
1680 // should be deleted by other means.
1681 LASSERT(found_twa, return);
1683 if (d.current_work_area_ == nullptr) {
1684 if (d.splitter_->count() != 0) {
1685 TabWorkArea * twa = d.currentTabWorkArea();
1686 setCurrentWorkArea(twa->currentWorkArea());
1688 // No more work areas, switch to the background widget.
1689 setCurrentWorkArea(nullptr);
1695 LayoutBox * GuiView::getLayoutDialog() const
1701 void GuiView::updateLayoutList()
1704 d.layout_->updateContents(false);
1708 void GuiView::updateToolbars()
1710 if (d.current_work_area_) {
1712 if (d.current_work_area_->bufferView().cursor().inMathed()
1713 && !d.current_work_area_->bufferView().cursor().inRegexped())
1714 context |= Toolbars::MATH;
1715 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1716 context |= Toolbars::TABLE;
1717 if (currentBufferView()->buffer().areChangesPresent()
1718 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1719 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1720 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1721 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1722 context |= Toolbars::REVIEW;
1723 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1724 context |= Toolbars::MATHMACROTEMPLATE;
1725 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1726 context |= Toolbars::IPA;
1727 if (command_execute_)
1728 context |= Toolbars::MINIBUFFER;
1729 if (minibuffer_focus_) {
1730 context |= Toolbars::MINIBUFFER_FOCUS;
1731 minibuffer_focus_ = false;
1734 for (auto const & tb_p : d.toolbars_)
1735 tb_p.second->update(context);
1737 for (auto const & tb_p : d.toolbars_)
1738 tb_p.second->update();
1742 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1744 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1745 LASSERT(newBuffer, return);
1747 GuiWorkArea * wa = workArea(*newBuffer);
1748 if (wa == nullptr) {
1750 newBuffer->masterBuffer()->updateBuffer();
1752 wa = addWorkArea(*newBuffer);
1753 // scroll to the position when the BufferView was last closed
1754 if (lyxrc.use_lastfilepos) {
1755 LastFilePosSection::FilePos filepos =
1756 theSession().lastFilePos().load(newBuffer->fileName());
1757 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1760 //Disconnect the old buffer...there's no new one.
1763 connectBuffer(*newBuffer);
1764 connectBufferView(wa->bufferView());
1766 setCurrentWorkArea(wa);
1770 void GuiView::connectBuffer(Buffer & buf)
1772 buf.setGuiDelegate(this);
1776 void GuiView::disconnectBuffer()
1778 if (d.current_work_area_)
1779 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1783 void GuiView::connectBufferView(BufferView & bv)
1785 bv.setGuiDelegate(this);
1789 void GuiView::disconnectBufferView()
1791 if (d.current_work_area_)
1792 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1796 void GuiView::errors(string const & error_type, bool from_master)
1798 BufferView const * const bv = currentBufferView();
1802 ErrorList const & el = from_master ?
1803 bv->buffer().masterBuffer()->errorList(error_type) :
1804 bv->buffer().errorList(error_type);
1809 string err = error_type;
1811 err = "from_master|" + error_type;
1812 showDialog("errorlist", err);
1816 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1818 d.toc_models_.updateItem(toqstr(type), dit);
1822 void GuiView::structureChanged()
1824 // This is called from the Buffer, which has no way to ensure that cursors
1825 // in BufferView remain valid.
1826 if (documentBufferView())
1827 documentBufferView()->cursor().sanitize();
1828 // FIXME: This is slightly expensive, though less than the tocBackend update
1829 // (#9880). This also resets the view in the Toc Widget (#6675).
1830 d.toc_models_.reset(documentBufferView());
1831 // Navigator needs more than a simple update in this case. It needs to be
1833 updateDialog("toc", "");
1837 void GuiView::updateDialog(string const & name, string const & sdata)
1839 if (!isDialogVisible(name))
1842 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1843 if (it == d.dialogs_.end())
1846 Dialog * const dialog = it->second.get();
1847 if (dialog->isVisibleView())
1848 dialog->initialiseParams(sdata);
1852 BufferView * GuiView::documentBufferView()
1854 return currentMainWorkArea()
1855 ? ¤tMainWorkArea()->bufferView()
1860 BufferView const * GuiView::documentBufferView() const
1862 return currentMainWorkArea()
1863 ? ¤tMainWorkArea()->bufferView()
1868 BufferView * GuiView::currentBufferView()
1870 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1874 BufferView const * GuiView::currentBufferView() const
1876 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1880 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1881 Buffer const * orig, Buffer * clone)
1883 bool const success = clone->autoSave();
1885 busyBuffers.remove(orig);
1887 ? _("Automatic save done.")
1888 : _("Automatic save failed!");
1892 void GuiView::autoSave()
1894 LYXERR(Debug::INFO, "Running autoSave()");
1896 Buffer * buffer = documentBufferView()
1897 ? &documentBufferView()->buffer() : nullptr;
1899 resetAutosaveTimers();
1903 GuiViewPrivate::busyBuffers.insert(buffer);
1904 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1905 buffer, buffer->cloneBufferOnly());
1906 d.autosave_watcher_.setFuture(f);
1907 resetAutosaveTimers();
1911 void GuiView::resetAutosaveTimers()
1914 d.autosave_timeout_.restart();
1918 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1921 Buffer * buf = currentBufferView()
1922 ? ¤tBufferView()->buffer() : nullptr;
1923 Buffer * doc_buffer = documentBufferView()
1924 ? &(documentBufferView()->buffer()) : nullptr;
1927 /* In LyX/Mac, when a dialog is open, the menus of the
1928 application can still be accessed without giving focus to
1929 the main window. In this case, we want to disable the menu
1930 entries that are buffer-related.
1931 This code must not be used on Linux and Windows, since it
1932 would disable buffer-related entries when hovering over the
1933 menu (see bug #9574).
1935 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1941 // Check whether we need a buffer
1942 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1943 // no, exit directly
1944 flag.message(from_utf8(N_("Command not allowed with"
1945 "out any document open")));
1946 flag.setEnabled(false);
1950 if (cmd.origin() == FuncRequest::TOC) {
1951 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1952 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1953 flag.setEnabled(false);
1957 switch(cmd.action()) {
1958 case LFUN_BUFFER_IMPORT:
1961 case LFUN_MASTER_BUFFER_EXPORT:
1963 && (doc_buffer->parent() != nullptr
1964 || doc_buffer->hasChildren())
1965 && !d.processing_thread_watcher_.isRunning()
1966 // this launches a dialog, which would be in the wrong Buffer
1967 && !(::lyx::operator==(cmd.argument(), "custom"));
1970 case LFUN_MASTER_BUFFER_UPDATE:
1971 case LFUN_MASTER_BUFFER_VIEW:
1973 && (doc_buffer->parent() != nullptr
1974 || doc_buffer->hasChildren())
1975 && !d.processing_thread_watcher_.isRunning();
1978 case LFUN_BUFFER_UPDATE:
1979 case LFUN_BUFFER_VIEW: {
1980 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1984 string format = to_utf8(cmd.argument());
1985 if (cmd.argument().empty())
1986 format = doc_buffer->params().getDefaultOutputFormat();
1987 enable = doc_buffer->params().isExportable(format, true);
1991 case LFUN_BUFFER_RELOAD:
1992 enable = doc_buffer && !doc_buffer->isUnnamed()
1993 && doc_buffer->fileName().exists()
1994 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1997 case LFUN_BUFFER_RESET_EXPORT:
1998 enable = doc_buffer != nullptr;
2001 case LFUN_BUFFER_CHILD_OPEN:
2002 enable = doc_buffer != nullptr;
2005 case LFUN_MASTER_BUFFER_FORALL: {
2006 if (doc_buffer == nullptr) {
2007 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2011 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2012 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2013 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2018 for (Buffer * buf : doc_buffer->allRelatives()) {
2019 GuiWorkArea * wa = workArea(*buf);
2022 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2023 enable = flag.enabled();
2030 case LFUN_BUFFER_WRITE:
2031 enable = doc_buffer && (doc_buffer->isUnnamed()
2032 || (!doc_buffer->isClean()
2033 || cmd.argument() == "force"));
2036 //FIXME: This LFUN should be moved to GuiApplication.
2037 case LFUN_BUFFER_WRITE_ALL: {
2038 // We enable the command only if there are some modified buffers
2039 Buffer * first = theBufferList().first();
2044 // We cannot use a for loop as the buffer list is a cycle.
2046 if (!b->isClean()) {
2050 b = theBufferList().next(b);
2051 } while (b != first);
2055 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2056 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2059 case LFUN_BUFFER_EXPORT: {
2060 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2064 return doc_buffer->getStatus(cmd, flag);
2067 case LFUN_BUFFER_EXPORT_AS:
2068 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2073 case LFUN_BUFFER_WRITE_AS:
2074 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2075 enable = doc_buffer != nullptr;
2078 case LFUN_EXPORT_CANCEL:
2079 enable = d.processing_thread_watcher_.isRunning();
2082 case LFUN_BUFFER_CLOSE:
2083 case LFUN_VIEW_CLOSE:
2084 enable = doc_buffer != nullptr;
2087 case LFUN_BUFFER_CLOSE_ALL:
2088 enable = theBufferList().last() != theBufferList().first();
2091 case LFUN_BUFFER_CHKTEX: {
2092 // hide if we have no checktex command
2093 if (lyxrc.chktex_command.empty()) {
2094 flag.setUnknown(true);
2098 if (!doc_buffer || !doc_buffer->params().isLatex()
2099 || d.processing_thread_watcher_.isRunning()) {
2100 // grey out, don't hide
2108 case LFUN_VIEW_SPLIT:
2109 if (cmd.getArg(0) == "vertical")
2110 enable = doc_buffer && (d.splitter_->count() == 1 ||
2111 d.splitter_->orientation() == Qt::Vertical);
2113 enable = doc_buffer && (d.splitter_->count() == 1 ||
2114 d.splitter_->orientation() == Qt::Horizontal);
2117 case LFUN_TAB_GROUP_CLOSE:
2118 enable = d.tabWorkAreaCount() > 1;
2121 case LFUN_DEVEL_MODE_TOGGLE:
2122 flag.setOnOff(devel_mode_);
2125 case LFUN_TOOLBAR_SET: {
2126 string const name = cmd.getArg(0);
2127 string const state = cmd.getArg(1);
2128 if (name.empty() || state.empty()) {
2130 docstring const msg =
2131 _("Function toolbar-set requires two arguments!");
2135 if (state != "on" && state != "off" && state != "auto") {
2137 docstring const msg =
2138 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2143 if (GuiToolbar * t = toolbar(name)) {
2144 bool const autovis = t->visibility() & Toolbars::AUTO;
2146 flag.setOnOff(t->isVisible() && !autovis);
2147 else if (state == "off")
2148 flag.setOnOff(!t->isVisible() && !autovis);
2149 else if (state == "auto")
2150 flag.setOnOff(autovis);
2153 docstring const msg =
2154 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2160 case LFUN_TOOLBAR_TOGGLE: {
2161 string const name = cmd.getArg(0);
2162 if (GuiToolbar * t = toolbar(name))
2163 flag.setOnOff(t->isVisible());
2166 docstring const msg =
2167 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2173 case LFUN_TOOLBAR_MOVABLE: {
2174 string const name = cmd.getArg(0);
2175 // use negation since locked == !movable
2177 // toolbar name * locks all toolbars
2178 flag.setOnOff(!toolbarsMovable_);
2179 else if (GuiToolbar * t = toolbar(name))
2180 flag.setOnOff(!(t->isMovable()));
2183 docstring const msg =
2184 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2190 case LFUN_ICON_SIZE:
2191 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2194 case LFUN_DROP_LAYOUTS_CHOICE:
2195 enable = buf != nullptr;
2198 case LFUN_UI_TOGGLE:
2199 flag.setOnOff(isFullScreen());
2202 case LFUN_DIALOG_DISCONNECT_INSET:
2205 case LFUN_DIALOG_HIDE:
2206 // FIXME: should we check if the dialog is shown?
2209 case LFUN_DIALOG_TOGGLE:
2210 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2213 case LFUN_DIALOG_SHOW: {
2214 string const name = cmd.getArg(0);
2216 enable = name == "aboutlyx"
2217 || name == "file" //FIXME: should be removed.
2218 || name == "lyxfiles"
2220 || name == "texinfo"
2221 || name == "progress"
2222 || name == "compare";
2223 else if (name == "character" || name == "symbols"
2224 || name == "mathdelimiter" || name == "mathmatrix") {
2225 if (!buf || buf->isReadonly())
2228 Cursor const & cur = currentBufferView()->cursor();
2229 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2232 else if (name == "latexlog")
2233 enable = FileName(doc_buffer->logName()).isReadableFile();
2234 else if (name == "spellchecker")
2235 enable = theSpellChecker()
2236 && !doc_buffer->text().empty();
2237 else if (name == "vclog")
2238 enable = doc_buffer->lyxvc().inUse();
2242 case LFUN_DIALOG_UPDATE: {
2243 string const name = cmd.getArg(0);
2245 enable = name == "prefs";
2249 case LFUN_COMMAND_EXECUTE:
2251 case LFUN_MENU_OPEN:
2252 // Nothing to check.
2255 case LFUN_COMPLETION_INLINE:
2256 if (!d.current_work_area_
2257 || !d.current_work_area_->completer().inlinePossible(
2258 currentBufferView()->cursor()))
2262 case LFUN_COMPLETION_POPUP:
2263 if (!d.current_work_area_
2264 || !d.current_work_area_->completer().popupPossible(
2265 currentBufferView()->cursor()))
2270 if (!d.current_work_area_
2271 || !d.current_work_area_->completer().inlinePossible(
2272 currentBufferView()->cursor()))
2276 case LFUN_COMPLETION_ACCEPT:
2277 if (!d.current_work_area_
2278 || (!d.current_work_area_->completer().popupVisible()
2279 && !d.current_work_area_->completer().inlineVisible()
2280 && !d.current_work_area_->completer().completionAvailable()))
2284 case LFUN_COMPLETION_CANCEL:
2285 if (!d.current_work_area_
2286 || (!d.current_work_area_->completer().popupVisible()
2287 && !d.current_work_area_->completer().inlineVisible()))
2291 case LFUN_BUFFER_ZOOM_OUT:
2292 case LFUN_BUFFER_ZOOM_IN: {
2293 // only diff between these two is that the default for ZOOM_OUT
2295 bool const neg_zoom =
2296 convert<int>(cmd.argument()) < 0 ||
2297 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2298 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2299 docstring const msg =
2300 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2304 enable = doc_buffer;
2308 case LFUN_BUFFER_ZOOM: {
2309 bool const less_than_min_zoom =
2310 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2311 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2312 docstring const msg =
2313 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2318 enable = doc_buffer;
2322 case LFUN_BUFFER_MOVE_NEXT:
2323 case LFUN_BUFFER_MOVE_PREVIOUS:
2324 // we do not cycle when moving
2325 case LFUN_BUFFER_NEXT:
2326 case LFUN_BUFFER_PREVIOUS:
2327 // because we cycle, it doesn't matter whether on first or last
2328 enable = (d.currentTabWorkArea()->count() > 1);
2330 case LFUN_BUFFER_SWITCH:
2331 // toggle on the current buffer, but do not toggle off
2332 // the other ones (is that a good idea?)
2334 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2335 flag.setOnOff(true);
2338 case LFUN_VC_REGISTER:
2339 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2341 case LFUN_VC_RENAME:
2342 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2345 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2347 case LFUN_VC_CHECK_IN:
2348 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2350 case LFUN_VC_CHECK_OUT:
2351 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2353 case LFUN_VC_LOCKING_TOGGLE:
2354 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2355 && doc_buffer->lyxvc().lockingToggleEnabled();
2356 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2358 case LFUN_VC_REVERT:
2359 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2360 && !doc_buffer->hasReadonlyFlag();
2362 case LFUN_VC_UNDO_LAST:
2363 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2365 case LFUN_VC_REPO_UPDATE:
2366 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2368 case LFUN_VC_COMMAND: {
2369 if (cmd.argument().empty())
2371 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2375 case LFUN_VC_COMPARE:
2376 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2379 case LFUN_SERVER_GOTO_FILE_ROW:
2380 case LFUN_LYX_ACTIVATE:
2381 case LFUN_WINDOW_RAISE:
2383 case LFUN_FORWARD_SEARCH:
2384 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2387 case LFUN_FILE_INSERT_PLAINTEXT:
2388 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2389 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2392 case LFUN_SPELLING_CONTINUOUSLY:
2393 flag.setOnOff(lyxrc.spellcheck_continuously);
2396 case LFUN_CITATION_OPEN:
2405 flag.setEnabled(false);
2411 static FileName selectTemplateFile()
2413 FileDialog dlg(qt_("Select template file"));
2414 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2415 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2417 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2418 QStringList(qt_("LyX Documents (*.lyx)")));
2420 if (result.first == FileDialog::Later)
2422 if (result.second.isEmpty())
2424 return FileName(fromqstr(result.second));
2428 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2432 Buffer * newBuffer = nullptr;
2434 newBuffer = checkAndLoadLyXFile(filename);
2435 } catch (ExceptionMessage const &) {
2442 message(_("Document not loaded."));
2446 setBuffer(newBuffer);
2447 newBuffer->errors("Parse");
2450 theSession().lastFiles().add(filename);
2451 theSession().writeFile();
2458 void GuiView::openDocument(string const & fname)
2460 string initpath = lyxrc.document_path;
2462 if (documentBufferView()) {
2463 string const trypath = documentBufferView()->buffer().filePath();
2464 // If directory is writeable, use this as default.
2465 if (FileName(trypath).isDirWritable())
2471 if (fname.empty()) {
2472 FileDialog dlg(qt_("Select document to open"));
2473 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2474 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2476 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2477 FileDialog::Result result =
2478 dlg.open(toqstr(initpath), filter);
2480 if (result.first == FileDialog::Later)
2483 filename = fromqstr(result.second);
2485 // check selected filename
2486 if (filename.empty()) {
2487 message(_("Canceled."));
2493 // get absolute path of file and add ".lyx" to the filename if
2495 FileName const fullname =
2496 fileSearch(string(), filename, "lyx", support::may_not_exist);
2497 if (!fullname.empty())
2498 filename = fullname.absFileName();
2500 if (!fullname.onlyPath().isDirectory()) {
2501 Alert::warning(_("Invalid filename"),
2502 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2503 from_utf8(fullname.absFileName())));
2507 // if the file doesn't exist and isn't already open (bug 6645),
2508 // let the user create one
2509 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2510 !LyXVC::file_not_found_hook(fullname)) {
2511 // the user specifically chose this name. Believe him.
2512 Buffer * const b = newFile(filename, string(), true);
2518 docstring const disp_fn = makeDisplayPath(filename);
2519 message(bformat(_("Opening document %1$s..."), disp_fn));
2522 Buffer * buf = loadDocument(fullname);
2524 str2 = bformat(_("Document %1$s opened."), disp_fn);
2525 if (buf->lyxvc().inUse())
2526 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2527 " " + _("Version control detected.");
2529 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2534 // FIXME: clean that
2535 static bool import(GuiView * lv, FileName const & filename,
2536 string const & format, ErrorList & errorList)
2538 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2540 string loader_format;
2541 vector<string> loaders = theConverters().loaders();
2542 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2543 for (string const & loader : loaders) {
2544 if (!theConverters().isReachable(format, loader))
2547 string const tofile =
2548 support::changeExtension(filename.absFileName(),
2549 theFormats().extension(loader));
2550 if (theConverters().convert(nullptr, filename, FileName(tofile),
2551 filename, format, loader, errorList) != Converters::SUCCESS)
2553 loader_format = loader;
2556 if (loader_format.empty()) {
2557 frontend::Alert::error(_("Couldn't import file"),
2558 bformat(_("No information for importing the format %1$s."),
2559 translateIfPossible(theFormats().prettyName(format))));
2563 loader_format = format;
2565 if (loader_format == "lyx") {
2566 Buffer * buf = lv->loadDocument(lyxfile);
2570 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2574 bool as_paragraphs = loader_format == "textparagraph";
2575 string filename2 = (loader_format == format) ? filename.absFileName()
2576 : support::changeExtension(filename.absFileName(),
2577 theFormats().extension(loader_format));
2578 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2580 guiApp->setCurrentView(lv);
2581 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2588 void GuiView::importDocument(string const & argument)
2591 string filename = split(argument, format, ' ');
2593 LYXERR(Debug::INFO, format << " file: " << filename);
2595 // need user interaction
2596 if (filename.empty()) {
2597 string initpath = lyxrc.document_path;
2598 if (documentBufferView()) {
2599 string const trypath = documentBufferView()->buffer().filePath();
2600 // If directory is writeable, use this as default.
2601 if (FileName(trypath).isDirWritable())
2605 docstring const text = bformat(_("Select %1$s file to import"),
2606 translateIfPossible(theFormats().prettyName(format)));
2608 FileDialog dlg(toqstr(text));
2609 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2610 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2612 docstring filter = translateIfPossible(theFormats().prettyName(format));
2615 filter += from_utf8(theFormats().extensions(format));
2618 FileDialog::Result result =
2619 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2621 if (result.first == FileDialog::Later)
2624 filename = fromqstr(result.second);
2626 // check selected filename
2627 if (filename.empty())
2628 message(_("Canceled."));
2631 if (filename.empty())
2634 // get absolute path of file
2635 FileName const fullname(support::makeAbsPath(filename));
2637 // Can happen if the user entered a path into the dialog
2639 if (fullname.onlyFileName().empty()) {
2640 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2641 "Aborting import."),
2642 from_utf8(fullname.absFileName()));
2643 frontend::Alert::error(_("File name error"), msg);
2644 message(_("Canceled."));
2649 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2651 // Check if the document already is open
2652 Buffer * buf = theBufferList().getBuffer(lyxfile);
2655 if (!closeBuffer()) {
2656 message(_("Canceled."));
2661 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2663 // if the file exists already, and we didn't do
2664 // -i lyx thefile.lyx, warn
2665 if (lyxfile.exists() && fullname != lyxfile) {
2667 docstring text = bformat(_("The document %1$s already exists.\n\n"
2668 "Do you want to overwrite that document?"), displaypath);
2669 int const ret = Alert::prompt(_("Overwrite document?"),
2670 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2673 message(_("Canceled."));
2678 message(bformat(_("Importing %1$s..."), displaypath));
2679 ErrorList errorList;
2680 if (import(this, fullname, format, errorList))
2681 message(_("imported."));
2683 message(_("file not imported!"));
2685 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2689 void GuiView::newDocument(string const & filename, string templatefile,
2692 FileName initpath(lyxrc.document_path);
2693 if (documentBufferView()) {
2694 FileName const trypath(documentBufferView()->buffer().filePath());
2695 // If directory is writeable, use this as default.
2696 if (trypath.isDirWritable())
2700 if (from_template) {
2701 if (templatefile.empty())
2702 templatefile = selectTemplateFile().absFileName();
2703 if (templatefile.empty())
2708 if (filename.empty())
2709 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2711 b = newFile(filename, templatefile, true);
2716 // If no new document could be created, it is unsure
2717 // whether there is a valid BufferView.
2718 if (currentBufferView())
2719 // Ensure the cursor is correctly positioned on screen.
2720 currentBufferView()->showCursor();
2724 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2726 BufferView * bv = documentBufferView();
2731 FileName filename(to_utf8(fname));
2732 if (filename.empty()) {
2733 // Launch a file browser
2735 string initpath = lyxrc.document_path;
2736 string const trypath = bv->buffer().filePath();
2737 // If directory is writeable, use this as default.
2738 if (FileName(trypath).isDirWritable())
2742 FileDialog dlg(qt_("Select LyX document to insert"));
2743 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2744 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2746 FileDialog::Result result = dlg.open(toqstr(initpath),
2747 QStringList(qt_("LyX Documents (*.lyx)")));
2749 if (result.first == FileDialog::Later)
2753 filename.set(fromqstr(result.second));
2755 // check selected filename
2756 if (filename.empty()) {
2757 // emit message signal.
2758 message(_("Canceled."));
2763 bv->insertLyXFile(filename, ignorelang);
2764 bv->buffer().errors("Parse");
2769 string const GuiView::getTemplatesPath(Buffer & b)
2771 // We start off with the user's templates path
2772 string result = addPath(package().user_support().absFileName(), "templates");
2773 // Check for the document language
2774 string const langcode = b.params().language->code();
2775 string const shortcode = langcode.substr(0, 2);
2776 if (!langcode.empty() && shortcode != "en") {
2777 string subpath = addPath(result, shortcode);
2778 string subpath_long = addPath(result, langcode);
2779 // If we have a subdirectory for the language already,
2781 FileName sp = FileName(subpath);
2782 if (sp.isDirectory())
2784 else if (FileName(subpath_long).isDirectory())
2785 result = subpath_long;
2787 // Ask whether we should create such a subdirectory
2788 docstring const text =
2789 bformat(_("It is suggested to save the template in a subdirectory\n"
2790 "appropriate to the document language (%1$s).\n"
2791 "This subdirectory does not exists yet.\n"
2792 "Do you want to create it?"),
2793 _(b.params().language->display()));
2794 if (Alert::prompt(_("Create Language Directory?"),
2795 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2796 // If the user agreed, we try to create it and report if this failed.
2797 if (!sp.createDirectory(0777))
2798 Alert::error(_("Subdirectory creation failed!"),
2799 _("Could not create subdirectory.\n"
2800 "The template will be saved in the parent directory."));
2806 // Do we have a layout category?
2807 string const cat = b.params().baseClass() ?
2808 b.params().baseClass()->category()
2811 string subpath = addPath(result, cat);
2812 // If we have a subdirectory for the category already,
2814 FileName sp = FileName(subpath);
2815 if (sp.isDirectory())
2818 // Ask whether we should create such a subdirectory
2819 docstring const text =
2820 bformat(_("It is suggested to save the template in a subdirectory\n"
2821 "appropriate to the layout category (%1$s).\n"
2822 "This subdirectory does not exists yet.\n"
2823 "Do you want to create it?"),
2825 if (Alert::prompt(_("Create Category Directory?"),
2826 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2827 // If the user agreed, we try to create it and report if this failed.
2828 if (!sp.createDirectory(0777))
2829 Alert::error(_("Subdirectory creation failed!"),
2830 _("Could not create subdirectory.\n"
2831 "The template will be saved in the parent directory."));
2841 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2843 FileName fname = b.fileName();
2844 FileName const oldname = fname;
2845 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2847 if (!newname.empty()) {
2850 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2852 fname = support::makeAbsPath(to_utf8(newname),
2853 oldname.onlyPath().absFileName());
2855 // Switch to this Buffer.
2858 // No argument? Ask user through dialog.
2860 QString const title = as_template ? qt_("Choose a filename to save template as")
2861 : qt_("Choose a filename to save document as");
2862 FileDialog dlg(title);
2863 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2864 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2866 if (!isLyXFileName(fname.absFileName()))
2867 fname.changeExtension(".lyx");
2869 string const path = as_template ?
2871 : fname.onlyPath().absFileName();
2872 FileDialog::Result result =
2873 dlg.save(toqstr(path),
2874 QStringList(qt_("LyX Documents (*.lyx)")),
2875 toqstr(fname.onlyFileName()));
2877 if (result.first == FileDialog::Later)
2880 fname.set(fromqstr(result.second));
2885 if (!isLyXFileName(fname.absFileName()))
2886 fname.changeExtension(".lyx");
2889 // fname is now the new Buffer location.
2891 // if there is already a Buffer open with this name, we do not want
2892 // to have another one. (the second test makes sure we're not just
2893 // trying to overwrite ourselves, which is fine.)
2894 if (theBufferList().exists(fname) && fname != oldname
2895 && theBufferList().getBuffer(fname) != &b) {
2896 docstring const text =
2897 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2898 "Please close it before attempting to overwrite it.\n"
2899 "Do you want to choose a new filename?"),
2900 from_utf8(fname.absFileName()));
2901 int const ret = Alert::prompt(_("Chosen File Already Open"),
2902 text, 0, 1, _("&Rename"), _("&Cancel"));
2904 case 0: return renameBuffer(b, docstring(), kind);
2905 case 1: return false;
2910 bool const existsLocal = fname.exists();
2911 bool const existsInVC = LyXVC::fileInVC(fname);
2912 if (existsLocal || existsInVC) {
2913 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2914 if (kind != LV_WRITE_AS && existsInVC) {
2915 // renaming to a name that is already in VC
2917 docstring text = bformat(_("The document %1$s "
2918 "is already registered.\n\n"
2919 "Do you want to choose a new name?"),
2921 docstring const title = (kind == LV_VC_RENAME) ?
2922 _("Rename document?") : _("Copy document?");
2923 docstring const button = (kind == LV_VC_RENAME) ?
2924 _("&Rename") : _("&Copy");
2925 int const ret = Alert::prompt(title, text, 0, 1,
2926 button, _("&Cancel"));
2928 case 0: return renameBuffer(b, docstring(), kind);
2929 case 1: return false;
2934 docstring text = bformat(_("The document %1$s "
2935 "already exists.\n\n"
2936 "Do you want to overwrite that document?"),
2938 int const ret = Alert::prompt(_("Overwrite document?"),
2939 text, 0, 2, _("&Overwrite"),
2940 _("&Rename"), _("&Cancel"));
2943 case 1: return renameBuffer(b, docstring(), kind);
2944 case 2: return false;
2950 case LV_VC_RENAME: {
2951 string msg = b.lyxvc().rename(fname);
2954 message(from_utf8(msg));
2958 string msg = b.lyxvc().copy(fname);
2961 message(from_utf8(msg));
2965 case LV_WRITE_AS_TEMPLATE:
2968 // LyXVC created the file already in case of LV_VC_RENAME or
2969 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2970 // relative paths of included stuff right if we moved e.g. from
2971 // /a/b.lyx to /a/c/b.lyx.
2973 bool const saved = saveBuffer(b, fname);
2980 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2982 FileName fname = b.fileName();
2984 FileDialog dlg(qt_("Choose a filename to export the document as"));
2985 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2988 QString const anyformat = qt_("Guess from extension (*.*)");
2991 vector<Format const *> export_formats;
2992 for (Format const & f : theFormats())
2993 if (f.documentFormat())
2994 export_formats.push_back(&f);
2995 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2996 map<QString, string> fmap;
2999 for (Format const * f : export_formats) {
3000 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3001 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3003 from_ascii(f->extension())));
3004 types << loc_filter;
3005 fmap[loc_filter] = f->name();
3006 if (from_ascii(f->name()) == iformat) {
3007 filter = loc_filter;
3008 ext = f->extension();
3011 string ofname = fname.onlyFileName();
3013 ofname = support::changeExtension(ofname, ext);
3014 FileDialog::Result result =
3015 dlg.save(toqstr(fname.onlyPath().absFileName()),
3019 if (result.first != FileDialog::Chosen)
3023 fname.set(fromqstr(result.second));
3024 if (filter == anyformat)
3025 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3027 fmt_name = fmap[filter];
3028 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3029 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3031 if (fmt_name.empty() || fname.empty())
3034 // fname is now the new Buffer location.
3035 if (FileName(fname).exists()) {
3036 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3037 docstring text = bformat(_("The document %1$s already "
3038 "exists.\n\nDo you want to "
3039 "overwrite that document?"),
3041 int const ret = Alert::prompt(_("Overwrite document?"),
3042 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3045 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3046 case 2: return false;
3050 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3053 return dr.dispatched();
3057 bool GuiView::saveBuffer(Buffer & b)
3059 return saveBuffer(b, FileName());
3063 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3065 if (workArea(b) && workArea(b)->inDialogMode())
3068 if (fn.empty() && b.isUnnamed())
3069 return renameBuffer(b, docstring());
3071 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3073 theSession().lastFiles().add(b.fileName());
3074 theSession().writeFile();
3078 // Switch to this Buffer.
3081 // FIXME: we don't tell the user *WHY* the save failed !!
3082 docstring const file = makeDisplayPath(b.absFileName(), 30);
3083 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3084 "Do you want to rename the document and "
3085 "try again?"), file);
3086 int const ret = Alert::prompt(_("Rename and save?"),
3087 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3090 if (!renameBuffer(b, docstring()))
3099 return saveBuffer(b, fn);
3103 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3105 return closeWorkArea(wa, false);
3109 // We only want to close the buffer if it is not visible in other workareas
3110 // of the same view, nor in other views, and if this is not a child
3111 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3113 Buffer & buf = wa->bufferView().buffer();
3115 bool last_wa = d.countWorkAreasOf(buf) == 1
3116 && !inOtherView(buf) && !buf.parent();
3118 bool close_buffer = last_wa;
3121 if (lyxrc.close_buffer_with_last_view == "yes")
3123 else if (lyxrc.close_buffer_with_last_view == "no")
3124 close_buffer = false;
3127 if (buf.isUnnamed())
3128 file = from_utf8(buf.fileName().onlyFileName());
3130 file = buf.fileName().displayName(30);
3131 docstring const text = bformat(
3132 _("Last view on document %1$s is being closed.\n"
3133 "Would you like to close or hide the document?\n"
3135 "Hidden documents can be displayed back through\n"
3136 "the menu: View->Hidden->...\n"
3138 "To remove this question, set your preference in:\n"
3139 " Tools->Preferences->Look&Feel->UserInterface\n"
3141 int ret = Alert::prompt(_("Close or hide document?"),
3142 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3145 close_buffer = (ret == 0);
3149 return closeWorkArea(wa, close_buffer);
3153 bool GuiView::closeBuffer()
3155 GuiWorkArea * wa = currentMainWorkArea();
3156 // coverity complained about this
3157 // it seems unnecessary, but perhaps is worth the check
3158 LASSERT(wa, return false);
3160 setCurrentWorkArea(wa);
3161 Buffer & buf = wa->bufferView().buffer();
3162 return closeWorkArea(wa, !buf.parent());
3166 void GuiView::writeSession() const {
3167 GuiWorkArea const * active_wa = currentMainWorkArea();
3168 for (int i = 0; i < d.splitter_->count(); ++i) {
3169 TabWorkArea * twa = d.tabWorkArea(i);
3170 for (int j = 0; j < twa->count(); ++j) {
3171 GuiWorkArea * wa = twa->workArea(j);
3172 Buffer & buf = wa->bufferView().buffer();
3173 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3179 bool GuiView::closeBufferAll()
3182 for (auto & buf : theBufferList()) {
3183 if (!saveBufferIfNeeded(*buf, false)) {
3184 // Closing has been cancelled, so abort.
3189 // Close the workareas in all other views
3190 QList<int> const ids = guiApp->viewIds();
3191 for (int i = 0; i != ids.size(); ++i) {
3192 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3196 // Close our own workareas
3197 if (!closeWorkAreaAll())
3204 bool GuiView::closeWorkAreaAll()
3206 setCurrentWorkArea(currentMainWorkArea());
3208 // We might be in a situation that there is still a tabWorkArea, but
3209 // there are no tabs anymore. This can happen when we get here after a
3210 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3211 // many TabWorkArea's have no documents anymore.
3214 // We have to call count() each time, because it can happen that
3215 // more than one splitter will disappear in one iteration (bug 5998).
3216 while (d.splitter_->count() > empty_twa) {
3217 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3219 if (twa->count() == 0)
3222 setCurrentWorkArea(twa->currentWorkArea());
3223 if (!closeTabWorkArea(twa))
3231 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3236 Buffer & buf = wa->bufferView().buffer();
3238 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3239 Alert::warning(_("Close document"),
3240 _("Document could not be closed because it is being processed by LyX."));
3245 return closeBuffer(buf);
3247 if (!inMultiTabs(wa))
3248 if (!saveBufferIfNeeded(buf, true))
3256 bool GuiView::closeBuffer(Buffer & buf)
3258 bool success = true;
3259 for (Buffer * child_buf : buf.getChildren()) {
3260 if (theBufferList().isOthersChild(&buf, child_buf)) {
3261 child_buf->setParent(nullptr);
3265 // FIXME: should we look in other tabworkareas?
3266 // ANSWER: I don't think so. I've tested, and if the child is
3267 // open in some other window, it closes without a problem.
3268 GuiWorkArea * child_wa = workArea(*child_buf);
3271 // If we are in a close_event all children will be closed in some time,
3272 // so no need to do it here. This will ensure that the children end up
3273 // in the session file in the correct order. If we close the master
3274 // buffer, we can close or release the child buffers here too.
3276 success = closeWorkArea(child_wa, true);
3280 // In this case the child buffer is open but hidden.
3281 // Even in this case, children can be dirty (e.g.,
3282 // after a label change in the master, see #11405).
3283 // Therefore, check this
3284 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3285 // If we are in a close_event all children will be closed in some time,
3286 // so no need to do it here. This will ensure that the children end up
3287 // in the session file in the correct order. If we close the master
3288 // buffer, we can close or release the child buffers here too.
3291 // Save dirty buffers also if closing_!
3292 if (saveBufferIfNeeded(*child_buf, false)) {
3293 child_buf->removeAutosaveFile();
3294 theBufferList().release(child_buf);
3296 // Saving of dirty children has been cancelled.
3297 // Cancel the whole process.
3304 // goto bookmark to update bookmark pit.
3305 // FIXME: we should update only the bookmarks related to this buffer!
3306 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3307 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3308 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3309 guiApp->gotoBookmark(i, false, false);
3311 if (saveBufferIfNeeded(buf, false)) {
3312 buf.removeAutosaveFile();
3313 theBufferList().release(&buf);
3317 // open all children again to avoid a crash because of dangling
3318 // pointers (bug 6603)
3324 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3326 while (twa == d.currentTabWorkArea()) {
3327 twa->setCurrentIndex(twa->count() - 1);
3329 GuiWorkArea * wa = twa->currentWorkArea();
3330 Buffer & b = wa->bufferView().buffer();
3332 // We only want to close the buffer if the same buffer is not visible
3333 // in another view, and if this is not a child and if we are closing
3334 // a view (not a tabgroup).
3335 bool const close_buffer =
3336 !inOtherView(b) && !b.parent() && closing_;
3338 if (!closeWorkArea(wa, close_buffer))
3345 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3347 if (buf.isClean() || buf.paragraphs().empty())
3350 // Switch to this Buffer.
3356 if (buf.isUnnamed()) {
3357 file = from_utf8(buf.fileName().onlyFileName());
3360 FileName filename = buf.fileName();
3362 file = filename.displayName(30);
3363 exists = filename.exists();
3366 // Bring this window to top before asking questions.
3371 if (hiding && buf.isUnnamed()) {
3372 docstring const text = bformat(_("The document %1$s has not been "
3373 "saved yet.\n\nDo you want to save "
3374 "the document?"), file);
3375 ret = Alert::prompt(_("Save new document?"),
3376 text, 0, 1, _("&Save"), _("&Cancel"));
3380 docstring const text = exists ?
3381 bformat(_("The document %1$s has unsaved changes."
3382 "\n\nDo you want to save the document or "
3383 "discard the changes?"), file) :
3384 bformat(_("The document %1$s has not been saved yet."
3385 "\n\nDo you want to save the document or "
3386 "discard it entirely?"), file);
3387 docstring const title = exists ?
3388 _("Save changed document?") : _("Save document?");
3389 ret = Alert::prompt(title, text, 0, 2,
3390 _("&Save"), _("&Discard"), _("&Cancel"));
3395 if (!saveBuffer(buf))
3399 // If we crash after this we could have no autosave file
3400 // but I guess this is really improbable (Jug).
3401 // Sometimes improbable things happen:
3402 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3403 // buf.removeAutosaveFile();
3405 // revert all changes
3416 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3418 Buffer & buf = wa->bufferView().buffer();
3420 for (int i = 0; i != d.splitter_->count(); ++i) {
3421 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3422 if (wa_ && wa_ != wa)
3425 return inOtherView(buf);
3429 bool GuiView::inOtherView(Buffer & buf)
3431 QList<int> const ids = guiApp->viewIds();
3433 for (int i = 0; i != ids.size(); ++i) {
3437 if (guiApp->view(ids[i]).workArea(buf))
3444 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3446 if (!documentBufferView())
3449 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3450 Buffer * const curbuf = &documentBufferView()->buffer();
3451 int nwa = twa->count();
3452 for (int i = 0; i < nwa; ++i) {
3453 if (&workArea(i)->bufferView().buffer() == curbuf) {
3455 if (np == NEXTBUFFER)
3456 next_index = (i == nwa - 1 ? 0 : i + 1);
3458 next_index = (i == 0 ? nwa - 1 : i - 1);
3460 twa->moveTab(i, next_index);
3462 setBuffer(&workArea(next_index)->bufferView().buffer());
3470 /// make sure the document is saved
3471 static bool ensureBufferClean(Buffer * buffer)
3473 LASSERT(buffer, return false);
3474 if (buffer->isClean() && !buffer->isUnnamed())
3477 docstring const file = buffer->fileName().displayName(30);
3480 if (!buffer->isUnnamed()) {
3481 text = bformat(_("The document %1$s has unsaved "
3482 "changes.\n\nDo you want to save "
3483 "the document?"), file);
3484 title = _("Save changed document?");
3487 text = bformat(_("The document %1$s has not been "
3488 "saved yet.\n\nDo you want to save "
3489 "the document?"), file);
3490 title = _("Save new document?");
3492 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3495 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3497 return buffer->isClean() && !buffer->isUnnamed();
3501 bool GuiView::reloadBuffer(Buffer & buf)
3503 currentBufferView()->cursor().reset();
3504 Buffer::ReadStatus status = buf.reload();
3505 return status == Buffer::ReadSuccess;
3509 void GuiView::checkExternallyModifiedBuffers()
3511 for (Buffer * buf : theBufferList()) {
3512 if (buf->fileName().exists() && buf->isChecksumModified()) {
3513 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3514 " Reload now? Any local changes will be lost."),
3515 from_utf8(buf->absFileName()));
3516 int const ret = Alert::prompt(_("Reload externally changed document?"),
3517 text, 0, 1, _("&Reload"), _("&Cancel"));
3525 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3527 Buffer * buffer = documentBufferView()
3528 ? &(documentBufferView()->buffer()) : nullptr;
3530 switch (cmd.action()) {
3531 case LFUN_VC_REGISTER:
3532 if (!buffer || !ensureBufferClean(buffer))
3534 if (!buffer->lyxvc().inUse()) {
3535 if (buffer->lyxvc().registrer()) {
3536 reloadBuffer(*buffer);
3537 dr.clearMessageUpdate();
3542 case LFUN_VC_RENAME:
3543 case LFUN_VC_COPY: {
3544 if (!buffer || !ensureBufferClean(buffer))
3546 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3547 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3548 // Some changes are not yet committed.
3549 // We test here and not in getStatus(), since
3550 // this test is expensive.
3552 LyXVC::CommandResult ret =
3553 buffer->lyxvc().checkIn(log);
3555 if (ret == LyXVC::ErrorCommand ||
3556 ret == LyXVC::VCSuccess)
3557 reloadBuffer(*buffer);
3558 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3559 frontend::Alert::error(
3560 _("Revision control error."),
3561 _("Document could not be checked in."));
3565 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3566 LV_VC_RENAME : LV_VC_COPY;
3567 renameBuffer(*buffer, cmd.argument(), kind);
3572 case LFUN_VC_CHECK_IN:
3573 if (!buffer || !ensureBufferClean(buffer))
3575 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3577 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3579 // Only skip reloading if the checkin was cancelled or
3580 // an error occurred before the real checkin VCS command
3581 // was executed, since the VCS might have changed the
3582 // file even if it could not checkin successfully.
3583 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3584 reloadBuffer(*buffer);
3588 case LFUN_VC_CHECK_OUT:
3589 if (!buffer || !ensureBufferClean(buffer))
3591 if (buffer->lyxvc().inUse()) {
3592 dr.setMessage(buffer->lyxvc().checkOut());
3593 reloadBuffer(*buffer);
3597 case LFUN_VC_LOCKING_TOGGLE:
3598 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3600 if (buffer->lyxvc().inUse()) {
3601 string res = buffer->lyxvc().lockingToggle();
3603 frontend::Alert::error(_("Revision control error."),
3604 _("Error when setting the locking property."));
3607 reloadBuffer(*buffer);
3612 case LFUN_VC_REVERT:
3615 if (buffer->lyxvc().revert()) {
3616 reloadBuffer(*buffer);
3617 dr.clearMessageUpdate();
3621 case LFUN_VC_UNDO_LAST:
3624 buffer->lyxvc().undoLast();
3625 reloadBuffer(*buffer);
3626 dr.clearMessageUpdate();
3629 case LFUN_VC_REPO_UPDATE:
3632 if (ensureBufferClean(buffer)) {
3633 dr.setMessage(buffer->lyxvc().repoUpdate());
3634 checkExternallyModifiedBuffers();
3638 case LFUN_VC_COMMAND: {
3639 string flag = cmd.getArg(0);
3640 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3643 if (contains(flag, 'M')) {
3644 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3647 string path = cmd.getArg(1);
3648 if (contains(path, "$$p") && buffer)
3649 path = subst(path, "$$p", buffer->filePath());
3650 LYXERR(Debug::LYXVC, "Directory: " << path);
3652 if (!pp.isReadableDirectory()) {
3653 lyxerr << _("Directory is not accessible.") << endl;
3656 support::PathChanger p(pp);
3658 string command = cmd.getArg(2);
3659 if (command.empty())
3662 command = subst(command, "$$i", buffer->absFileName());
3663 command = subst(command, "$$p", buffer->filePath());
3665 command = subst(command, "$$m", to_utf8(message));
3666 LYXERR(Debug::LYXVC, "Command: " << command);
3668 one.startscript(Systemcall::Wait, command);
3672 if (contains(flag, 'I'))
3673 buffer->markDirty();
3674 if (contains(flag, 'R'))
3675 reloadBuffer(*buffer);
3680 case LFUN_VC_COMPARE: {
3681 if (cmd.argument().empty()) {
3682 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3688 string rev1 = cmd.getArg(0);
3692 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3695 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3696 f2 = buffer->absFileName();
3698 string rev2 = cmd.getArg(1);
3702 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3706 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3707 f1 << "\n" << f2 << "\n" );
3708 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3709 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3719 void GuiView::openChildDocument(string const & fname)
3721 LASSERT(documentBufferView(), return);
3722 Buffer & buffer = documentBufferView()->buffer();
3723 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3724 documentBufferView()->saveBookmark(false);
3725 Buffer * child = nullptr;
3726 if (theBufferList().exists(filename)) {
3727 child = theBufferList().getBuffer(filename);
3730 message(bformat(_("Opening child document %1$s..."),
3731 makeDisplayPath(filename.absFileName())));
3732 child = loadDocument(filename, false);
3734 // Set the parent name of the child document.
3735 // This makes insertion of citations and references in the child work,
3736 // when the target is in the parent or another child document.
3738 child->setParent(&buffer);
3742 bool GuiView::goToFileRow(string const & argument)
3746 size_t i = argument.find_last_of(' ');
3747 if (i != string::npos) {
3748 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3749 istringstream is(argument.substr(i + 1));
3754 if (i == string::npos) {
3755 LYXERR0("Wrong argument: " << argument);
3758 Buffer * buf = nullptr;
3759 string const realtmp = package().temp_dir().realPath();
3760 // We have to use os::path_prefix_is() here, instead of
3761 // simply prefixIs(), because the file name comes from
3762 // an external application and may need case adjustment.
3763 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3764 buf = theBufferList().getBufferFromTmp(file_name, true);
3765 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3766 << (buf ? " success" : " failed"));
3768 // Must replace extension of the file to be .lyx
3769 // and get full path
3770 FileName const s = fileSearch(string(),
3771 support::changeExtension(file_name, ".lyx"), "lyx");
3772 // Either change buffer or load the file
3773 if (theBufferList().exists(s))
3774 buf = theBufferList().getBuffer(s);
3775 else if (s.exists()) {
3776 buf = loadDocument(s);
3781 _("File does not exist: %1$s"),
3782 makeDisplayPath(file_name)));
3788 _("No buffer for file: %1$s."),
3789 makeDisplayPath(file_name))
3794 bool success = documentBufferView()->setCursorFromRow(row);
3796 LYXERR(Debug::LATEX,
3797 "setCursorFromRow: invalid position for row " << row);
3798 frontend::Alert::error(_("Inverse Search Failed"),
3799 _("Invalid position requested by inverse search.\n"
3800 "You may need to update the viewed document."));
3806 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3808 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3809 menu->exec(QCursor::pos());
3814 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3815 Buffer const * orig, Buffer * clone, string const & format)
3817 Buffer::ExportStatus const status = func(format);
3819 // the cloning operation will have produced a clone of the entire set of
3820 // documents, starting from the master. so we must delete those.
3821 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3823 busyBuffers.remove(orig);
3828 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3829 Buffer const * orig, Buffer * clone, string const & format)
3831 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3833 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3837 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3838 Buffer const * orig, Buffer * clone, string const & format)
3840 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3842 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3846 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3847 Buffer const * orig, Buffer * clone, string const & format)
3849 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3851 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3855 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3856 Buffer const * used_buffer,
3857 docstring const & msg,
3858 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3859 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3860 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3861 bool allow_async, bool use_tmpdir)
3866 string format = argument;
3868 format = used_buffer->params().getDefaultOutputFormat();
3869 processing_format = format;
3871 progress_->clearMessages();
3874 #if EXPORT_in_THREAD
3876 GuiViewPrivate::busyBuffers.insert(used_buffer);
3877 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3878 if (!cloned_buffer) {
3879 Alert::error(_("Export Error"),
3880 _("Error cloning the Buffer."));
3883 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3888 setPreviewFuture(f);
3889 last_export_format = used_buffer->params().bufferFormat();
3892 // We are asynchronous, so we don't know here anything about the success
3895 Buffer::ExportStatus status;
3897 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3898 } else if (previewFunc) {
3899 status = (used_buffer->*previewFunc)(format);
3902 handleExportStatus(gv_, status, format);
3904 return (status == Buffer::ExportSuccess
3905 || status == Buffer::PreviewSuccess);
3909 Buffer::ExportStatus status;
3911 status = (used_buffer->*syncFunc)(format, true);
3912 } else if (previewFunc) {
3913 status = (used_buffer->*previewFunc)(format);
3916 handleExportStatus(gv_, status, format);
3918 return (status == Buffer::ExportSuccess
3919 || status == Buffer::PreviewSuccess);
3923 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3925 BufferView * bv = currentBufferView();
3926 LASSERT(bv, return);
3928 // Let the current BufferView dispatch its own actions.
3929 bv->dispatch(cmd, dr);
3930 if (dr.dispatched()) {
3931 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3932 updateDialog("document", "");
3936 // Try with the document BufferView dispatch if any.
3937 BufferView * doc_bv = documentBufferView();
3938 if (doc_bv && doc_bv != bv) {
3939 doc_bv->dispatch(cmd, dr);
3940 if (dr.dispatched()) {
3941 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3942 updateDialog("document", "");
3947 // Then let the current Cursor dispatch its own actions.
3948 bv->cursor().dispatch(cmd);
3950 // update completion. We do it here and not in
3951 // processKeySym to avoid another redraw just for a
3952 // changed inline completion
3953 if (cmd.origin() == FuncRequest::KEYBOARD) {
3954 if (cmd.action() == LFUN_SELF_INSERT
3955 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3956 updateCompletion(bv->cursor(), true, true);
3957 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3958 updateCompletion(bv->cursor(), false, true);
3960 updateCompletion(bv->cursor(), false, false);
3963 dr = bv->cursor().result();
3967 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3969 BufferView * bv = currentBufferView();
3970 // By default we won't need any update.
3971 dr.screenUpdate(Update::None);
3972 // assume cmd will be dispatched
3973 dr.dispatched(true);
3975 Buffer * doc_buffer = documentBufferView()
3976 ? &(documentBufferView()->buffer()) : nullptr;
3978 if (cmd.origin() == FuncRequest::TOC) {
3979 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3980 toc->doDispatch(bv->cursor(), cmd, dr);
3984 string const argument = to_utf8(cmd.argument());
3986 switch(cmd.action()) {
3987 case LFUN_BUFFER_CHILD_OPEN:
3988 openChildDocument(to_utf8(cmd.argument()));
3991 case LFUN_BUFFER_IMPORT:
3992 importDocument(to_utf8(cmd.argument()));
3995 case LFUN_MASTER_BUFFER_EXPORT:
3997 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3999 case LFUN_BUFFER_EXPORT: {
4002 // GCC only sees strfwd.h when building merged
4003 if (::lyx::operator==(cmd.argument(), "custom")) {
4004 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4005 // so the following test should not be needed.
4006 // In principle, we could try to switch to such a view...
4007 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4008 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4012 string const dest = cmd.getArg(1);
4013 FileName target_dir;
4014 if (!dest.empty() && FileName::isAbsolute(dest))
4015 target_dir = FileName(support::onlyPath(dest));
4017 target_dir = doc_buffer->fileName().onlyPath();
4019 string const format = (argument.empty() || argument == "default") ?
4020 doc_buffer->params().getDefaultOutputFormat() : argument;
4022 if ((dest.empty() && doc_buffer->isUnnamed())
4023 || !target_dir.isDirWritable()) {
4024 exportBufferAs(*doc_buffer, from_utf8(format));
4027 /* TODO/Review: Is it a problem to also export the children?
4028 See the update_unincluded flag */
4029 d.asyncBufferProcessing(format,
4032 &GuiViewPrivate::exportAndDestroy,
4034 nullptr, cmd.allowAsync());
4035 // TODO Inform user about success
4039 case LFUN_BUFFER_EXPORT_AS: {
4040 LASSERT(doc_buffer, break);
4041 docstring f = cmd.argument();
4043 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4044 exportBufferAs(*doc_buffer, f);
4048 case LFUN_BUFFER_UPDATE: {
4049 d.asyncBufferProcessing(argument,
4052 &GuiViewPrivate::compileAndDestroy,
4054 nullptr, cmd.allowAsync(), true);
4057 case LFUN_BUFFER_VIEW: {
4058 d.asyncBufferProcessing(argument,
4060 _("Previewing ..."),
4061 &GuiViewPrivate::previewAndDestroy,
4063 &Buffer::preview, cmd.allowAsync());
4066 case LFUN_MASTER_BUFFER_UPDATE: {
4067 d.asyncBufferProcessing(argument,
4068 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4070 &GuiViewPrivate::compileAndDestroy,
4072 nullptr, cmd.allowAsync(), true);
4075 case LFUN_MASTER_BUFFER_VIEW: {
4076 d.asyncBufferProcessing(argument,
4077 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4079 &GuiViewPrivate::previewAndDestroy,
4080 nullptr, &Buffer::preview, cmd.allowAsync());
4083 case LFUN_EXPORT_CANCEL: {
4084 Systemcall::killscript();
4087 case LFUN_BUFFER_SWITCH: {
4088 string const file_name = to_utf8(cmd.argument());
4089 if (!FileName::isAbsolute(file_name)) {
4091 dr.setMessage(_("Absolute filename expected."));
4095 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4098 dr.setMessage(_("Document not loaded"));
4102 // Do we open or switch to the buffer in this view ?
4103 if (workArea(*buffer)
4104 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4109 // Look for the buffer in other views
4110 QList<int> const ids = guiApp->viewIds();
4112 for (; i != ids.size(); ++i) {
4113 GuiView & gv = guiApp->view(ids[i]);
4114 if (gv.workArea(*buffer)) {
4116 gv.activateWindow();
4118 gv.setBuffer(buffer);
4123 // If necessary, open a new window as a last resort
4124 if (i == ids.size()) {
4125 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4131 case LFUN_BUFFER_NEXT:
4132 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4135 case LFUN_BUFFER_MOVE_NEXT:
4136 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4139 case LFUN_BUFFER_PREVIOUS:
4140 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4143 case LFUN_BUFFER_MOVE_PREVIOUS:
4144 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4147 case LFUN_BUFFER_CHKTEX:
4148 LASSERT(doc_buffer, break);
4149 doc_buffer->runChktex();
4152 case LFUN_COMMAND_EXECUTE: {
4153 command_execute_ = true;
4154 minibuffer_focus_ = true;
4157 case LFUN_DROP_LAYOUTS_CHOICE:
4158 d.layout_->showPopup();
4161 case LFUN_MENU_OPEN:
4162 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4163 menu->exec(QCursor::pos());
4166 case LFUN_FILE_INSERT: {
4167 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4168 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4169 dr.forceBufferUpdate();
4170 dr.screenUpdate(Update::Force);
4175 case LFUN_FILE_INSERT_PLAINTEXT:
4176 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4177 string const fname = to_utf8(cmd.argument());
4178 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4179 dr.setMessage(_("Absolute filename expected."));
4183 FileName filename(fname);
4184 if (fname.empty()) {
4185 FileDialog dlg(qt_("Select file to insert"));
4187 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4188 QStringList(qt_("All Files (*)")));
4190 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4191 dr.setMessage(_("Canceled."));
4195 filename.set(fromqstr(result.second));
4199 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4200 bv->dispatch(new_cmd, dr);
4205 case LFUN_BUFFER_RELOAD: {
4206 LASSERT(doc_buffer, break);
4209 bool drop = (cmd.argument() == "dump");
4212 if (!drop && !doc_buffer->isClean()) {
4213 docstring const file =
4214 makeDisplayPath(doc_buffer->absFileName(), 20);
4215 if (doc_buffer->notifiesExternalModification()) {
4216 docstring text = _("The current version will be lost. "
4217 "Are you sure you want to load the version on disk "
4218 "of the document %1$s?");
4219 ret = Alert::prompt(_("Reload saved document?"),
4220 bformat(text, file), 1, 1,
4221 _("&Reload"), _("&Cancel"));
4223 docstring text = _("Any changes will be lost. "
4224 "Are you sure you want to revert to the saved version "
4225 "of the document %1$s?");
4226 ret = Alert::prompt(_("Revert to saved document?"),
4227 bformat(text, file), 1, 1,
4228 _("&Revert"), _("&Cancel"));
4233 doc_buffer->markClean();
4234 reloadBuffer(*doc_buffer);
4235 dr.forceBufferUpdate();
4240 case LFUN_BUFFER_RESET_EXPORT:
4241 LASSERT(doc_buffer, break);
4242 doc_buffer->requireFreshStart(true);
4243 dr.setMessage(_("Buffer export reset."));
4246 case LFUN_BUFFER_WRITE:
4247 LASSERT(doc_buffer, break);
4248 saveBuffer(*doc_buffer);
4251 case LFUN_BUFFER_WRITE_AS:
4252 LASSERT(doc_buffer, break);
4253 renameBuffer(*doc_buffer, cmd.argument());
4256 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4257 LASSERT(doc_buffer, break);
4258 renameBuffer(*doc_buffer, cmd.argument(),
4259 LV_WRITE_AS_TEMPLATE);
4262 case LFUN_BUFFER_WRITE_ALL: {
4263 Buffer * first = theBufferList().first();
4266 message(_("Saving all documents..."));
4267 // We cannot use a for loop as the buffer list cycles.
4270 if (!b->isClean()) {
4272 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4274 b = theBufferList().next(b);
4275 } while (b != first);
4276 dr.setMessage(_("All documents saved."));
4280 case LFUN_MASTER_BUFFER_FORALL: {
4284 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4285 funcToRun.allowAsync(false);
4287 for (Buffer const * buf : doc_buffer->allRelatives()) {
4288 // Switch to other buffer view and resend cmd
4289 lyx::dispatch(FuncRequest(
4290 LFUN_BUFFER_SWITCH, buf->absFileName()));
4291 lyx::dispatch(funcToRun);
4294 lyx::dispatch(FuncRequest(
4295 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4299 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4300 LASSERT(doc_buffer, break);
4301 doc_buffer->clearExternalModification();
4304 case LFUN_BUFFER_CLOSE:
4308 case LFUN_BUFFER_CLOSE_ALL:
4312 case LFUN_DEVEL_MODE_TOGGLE:
4313 devel_mode_ = !devel_mode_;
4315 dr.setMessage(_("Developer mode is now enabled."));
4317 dr.setMessage(_("Developer mode is now disabled."));
4320 case LFUN_TOOLBAR_SET: {
4321 string const name = cmd.getArg(0);
4322 string const state = cmd.getArg(1);
4323 if (GuiToolbar * t = toolbar(name))
4328 case LFUN_TOOLBAR_TOGGLE: {
4329 string const name = cmd.getArg(0);
4330 if (GuiToolbar * t = toolbar(name))
4335 case LFUN_TOOLBAR_MOVABLE: {
4336 string const name = cmd.getArg(0);
4338 // toggle (all) toolbars movablility
4339 toolbarsMovable_ = !toolbarsMovable_;
4340 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4341 GuiToolbar * tb = toolbar(ti.name);
4342 if (tb && tb->isMovable() != toolbarsMovable_)
4343 // toggle toolbar movablity if it does not fit lock
4344 // (all) toolbars positions state silent = true, since
4345 // status bar notifications are slow
4348 if (toolbarsMovable_)
4349 dr.setMessage(_("Toolbars unlocked."));
4351 dr.setMessage(_("Toolbars locked."));
4352 } else if (GuiToolbar * t = toolbar(name)) {
4353 // toggle current toolbar movablity
4355 // update lock (all) toolbars positions
4356 updateLockToolbars();
4361 case LFUN_ICON_SIZE: {
4362 QSize size = d.iconSize(cmd.argument());
4364 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4365 size.width(), size.height()));
4369 case LFUN_DIALOG_UPDATE: {
4370 string const name = to_utf8(cmd.argument());
4371 if (name == "prefs" || name == "document")
4372 updateDialog(name, string());
4373 else if (name == "paragraph")
4374 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4375 else if (currentBufferView()) {
4376 Inset * inset = currentBufferView()->editedInset(name);
4377 // Can only update a dialog connected to an existing inset
4379 // FIXME: get rid of this indirection; GuiView ask the inset
4380 // if he is kind enough to update itself...
4381 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4382 //FIXME: pass DispatchResult here?
4383 inset->dispatch(currentBufferView()->cursor(), fr);
4389 case LFUN_DIALOG_TOGGLE: {
4390 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4391 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4392 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4396 case LFUN_DIALOG_DISCONNECT_INSET:
4397 disconnectDialog(to_utf8(cmd.argument()));
4400 case LFUN_DIALOG_HIDE: {
4401 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4405 case LFUN_DIALOG_SHOW: {
4406 string const name = cmd.getArg(0);
4407 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4409 if (name == "latexlog") {
4410 // getStatus checks that
4411 LASSERT(doc_buffer, break);
4412 Buffer::LogType type;
4413 string const logfile = doc_buffer->logName(&type);
4415 case Buffer::latexlog:
4418 case Buffer::buildlog:
4419 sdata = "literate ";
4422 sdata += Lexer::quoteString(logfile);
4423 showDialog("log", sdata);
4424 } else if (name == "vclog") {
4425 // getStatus checks that
4426 LASSERT(doc_buffer, break);
4427 string const sdata2 = "vc " +
4428 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4429 showDialog("log", sdata2);
4430 } else if (name == "symbols") {
4431 sdata = bv->cursor().getEncoding()->name();
4433 showDialog("symbols", sdata);
4434 } else if (name == "findreplace") {
4435 sdata = to_utf8(bv->cursor().selectionAsString(false));
4436 showDialog(name, sdata);
4438 } else if (name == "prefs" && isFullScreen()) {
4439 lfunUiToggle("fullscreen");
4440 showDialog("prefs", sdata);
4442 showDialog(name, sdata);
4447 dr.setMessage(cmd.argument());
4450 case LFUN_UI_TOGGLE: {
4451 string arg = cmd.getArg(0);
4452 if (!lfunUiToggle(arg)) {
4453 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4454 dr.setMessage(bformat(msg, from_utf8(arg)));
4456 // Make sure the keyboard focus stays in the work area.
4461 case LFUN_VIEW_SPLIT: {
4462 LASSERT(doc_buffer, break);
4463 string const orientation = cmd.getArg(0);
4464 d.splitter_->setOrientation(orientation == "vertical"
4465 ? Qt::Vertical : Qt::Horizontal);
4466 TabWorkArea * twa = addTabWorkArea();
4467 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4468 setCurrentWorkArea(wa);
4471 case LFUN_TAB_GROUP_CLOSE:
4472 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4473 closeTabWorkArea(twa);
4474 d.current_work_area_ = nullptr;
4475 twa = d.currentTabWorkArea();
4476 // Switch to the next GuiWorkArea in the found TabWorkArea.
4478 // Make sure the work area is up to date.
4479 setCurrentWorkArea(twa->currentWorkArea());
4481 setCurrentWorkArea(nullptr);
4486 case LFUN_VIEW_CLOSE:
4487 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4488 closeWorkArea(twa->currentWorkArea());
4489 d.current_work_area_ = nullptr;
4490 twa = d.currentTabWorkArea();
4491 // Switch to the next GuiWorkArea in the found TabWorkArea.
4493 // Make sure the work area is up to date.
4494 setCurrentWorkArea(twa->currentWorkArea());
4496 setCurrentWorkArea(nullptr);
4501 case LFUN_COMPLETION_INLINE:
4502 if (d.current_work_area_)
4503 d.current_work_area_->completer().showInline();
4506 case LFUN_COMPLETION_POPUP:
4507 if (d.current_work_area_)
4508 d.current_work_area_->completer().showPopup();
4513 if (d.current_work_area_)
4514 d.current_work_area_->completer().tab();
4517 case LFUN_COMPLETION_CANCEL:
4518 if (d.current_work_area_) {
4519 if (d.current_work_area_->completer().popupVisible())
4520 d.current_work_area_->completer().hidePopup();
4522 d.current_work_area_->completer().hideInline();
4526 case LFUN_COMPLETION_ACCEPT:
4527 if (d.current_work_area_)
4528 d.current_work_area_->completer().activate();
4531 case LFUN_BUFFER_ZOOM_IN:
4532 case LFUN_BUFFER_ZOOM_OUT:
4533 case LFUN_BUFFER_ZOOM: {
4534 if (cmd.argument().empty()) {
4535 if (cmd.action() == LFUN_BUFFER_ZOOM)
4537 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4542 if (cmd.action() == LFUN_BUFFER_ZOOM)
4543 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4544 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4545 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4547 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4550 // Actual zoom value: default zoom + fractional extra value
4551 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4552 if (zoom < static_cast<int>(zoom_min_))
4555 lyxrc.currentZoom = zoom;
4557 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4558 lyxrc.currentZoom, lyxrc.defaultZoom));
4560 guiApp->fontLoader().update();
4561 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4565 case LFUN_VC_REGISTER:
4566 case LFUN_VC_RENAME:
4568 case LFUN_VC_CHECK_IN:
4569 case LFUN_VC_CHECK_OUT:
4570 case LFUN_VC_REPO_UPDATE:
4571 case LFUN_VC_LOCKING_TOGGLE:
4572 case LFUN_VC_REVERT:
4573 case LFUN_VC_UNDO_LAST:
4574 case LFUN_VC_COMMAND:
4575 case LFUN_VC_COMPARE:
4576 dispatchVC(cmd, dr);
4579 case LFUN_SERVER_GOTO_FILE_ROW:
4580 if(goToFileRow(to_utf8(cmd.argument())))
4581 dr.screenUpdate(Update::Force | Update::FitCursor);
4584 case LFUN_LYX_ACTIVATE:
4588 case LFUN_WINDOW_RAISE:
4594 case LFUN_FORWARD_SEARCH: {
4595 // it seems safe to assume we have a document buffer, since
4596 // getStatus wants one.
4597 LASSERT(doc_buffer, break);
4598 Buffer const * doc_master = doc_buffer->masterBuffer();
4599 FileName const path(doc_master->temppath());
4600 string const texname = doc_master->isChild(doc_buffer)
4601 ? DocFileName(changeExtension(
4602 doc_buffer->absFileName(),
4603 "tex")).mangledFileName()
4604 : doc_buffer->latexName();
4605 string const fulltexname =
4606 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4607 string const mastername =
4608 removeExtension(doc_master->latexName());
4609 FileName const dviname(addName(path.absFileName(),
4610 addExtension(mastername, "dvi")));
4611 FileName const pdfname(addName(path.absFileName(),
4612 addExtension(mastername, "pdf")));
4613 bool const have_dvi = dviname.exists();
4614 bool const have_pdf = pdfname.exists();
4615 if (!have_dvi && !have_pdf) {
4616 dr.setMessage(_("Please, preview the document first."));
4619 string outname = dviname.onlyFileName();
4620 string command = lyxrc.forward_search_dvi;
4621 if (!have_dvi || (have_pdf &&
4622 pdfname.lastModified() > dviname.lastModified())) {
4623 outname = pdfname.onlyFileName();
4624 command = lyxrc.forward_search_pdf;
4627 DocIterator cur = bv->cursor();
4628 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4629 LYXERR(Debug::ACTION, "Forward search: row:" << row
4631 if (row == -1 || command.empty()) {
4632 dr.setMessage(_("Couldn't proceed."));
4635 string texrow = convert<string>(row);
4637 command = subst(command, "$$n", texrow);
4638 command = subst(command, "$$f", fulltexname);
4639 command = subst(command, "$$t", texname);
4640 command = subst(command, "$$o", outname);
4642 volatile PathChanger p(path);
4644 one.startscript(Systemcall::DontWait, command);
4648 case LFUN_SPELLING_CONTINUOUSLY:
4649 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4650 dr.screenUpdate(Update::Force);
4653 case LFUN_CITATION_OPEN: {
4655 if (theFormats().getFormat("pdf"))
4656 pdfv = theFormats().getFormat("pdf")->viewer();
4657 if (theFormats().getFormat("ps"))
4658 psv = theFormats().getFormat("ps")->viewer();
4659 frontend::showTarget(argument, pdfv, psv);
4664 // The LFUN must be for one of BufferView, Buffer or Cursor;
4666 dispatchToBufferView(cmd, dr);
4670 // Need to update bv because many LFUNs here might have destroyed it
4671 bv = currentBufferView();
4673 // Clear non-empty selections
4674 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4676 Cursor & cur = bv->cursor();
4677 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4678 cur.clearSelection();
4684 bool GuiView::lfunUiToggle(string const & ui_component)
4686 if (ui_component == "scrollbar") {
4687 // hide() is of no help
4688 if (d.current_work_area_->verticalScrollBarPolicy() ==
4689 Qt::ScrollBarAlwaysOff)
4691 d.current_work_area_->setVerticalScrollBarPolicy(
4692 Qt::ScrollBarAsNeeded);
4694 d.current_work_area_->setVerticalScrollBarPolicy(
4695 Qt::ScrollBarAlwaysOff);
4696 } else if (ui_component == "statusbar") {
4697 statusBar()->setVisible(!statusBar()->isVisible());
4698 } else if (ui_component == "menubar") {
4699 menuBar()->setVisible(!menuBar()->isVisible());
4701 if (ui_component == "frame") {
4702 int const l = contentsMargins().left();
4704 //are the frames in default state?
4705 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4707 #if QT_VERSION > 0x050903
4708 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4710 setContentsMargins(-2, -2, -2, -2);
4712 #if QT_VERSION > 0x050903
4713 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4715 setContentsMargins(0, 0, 0, 0);
4718 if (ui_component == "fullscreen") {
4726 void GuiView::toggleFullScreen()
4728 setWindowState(windowState() ^ Qt::WindowFullScreen);
4732 Buffer const * GuiView::updateInset(Inset const * inset)
4737 Buffer const * inset_buffer = &(inset->buffer());
4739 for (int i = 0; i != d.splitter_->count(); ++i) {
4740 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4743 Buffer const * buffer = &(wa->bufferView().buffer());
4744 if (inset_buffer == buffer)
4745 wa->scheduleRedraw(true);
4747 return inset_buffer;
4751 void GuiView::restartCaret()
4753 /* When we move around, or type, it's nice to be able to see
4754 * the caret immediately after the keypress.
4756 if (d.current_work_area_)
4757 d.current_work_area_->startBlinkingCaret();
4759 // Take this occasion to update the other GUI elements.
4765 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4767 if (d.current_work_area_)
4768 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4773 // This list should be kept in sync with the list of insets in
4774 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4775 // dialog should have the same name as the inset.
4776 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4777 // docs in LyXAction.cpp.
4779 char const * const dialognames[] = {
4781 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4782 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4783 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4784 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4785 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4786 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4787 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4788 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4790 char const * const * const end_dialognames =
4791 dialognames + (sizeof(dialognames) / sizeof(char *));
4795 cmpCStr(char const * name) : name_(name) {}
4796 bool operator()(char const * other) {
4797 return strcmp(other, name_) == 0;
4804 bool isValidName(string const & name)
4806 return find_if(dialognames, end_dialognames,
4807 cmpCStr(name.c_str())) != end_dialognames;
4813 void GuiView::resetDialogs()
4815 // Make sure that no LFUN uses any GuiView.
4816 guiApp->setCurrentView(nullptr);
4820 constructToolbars();
4821 guiApp->menus().fillMenuBar(menuBar(), this, false);
4822 d.layout_->updateContents(true);
4823 // Now update controls with current buffer.
4824 guiApp->setCurrentView(this);
4830 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4832 for (QObject * child: widget->children()) {
4833 if (child->inherits("QGroupBox")) {
4834 QGroupBox * box = (QGroupBox*) child;
4837 flatGroupBoxes(child, flag);
4843 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4845 if (!isValidName(name))
4848 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4850 if (it != d.dialogs_.end()) {
4852 it->second->hideView();
4853 return it->second.get();
4856 Dialog * dialog = build(name);
4857 d.dialogs_[name].reset(dialog);
4858 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4859 // Force a uniform style for group boxes
4860 // On Mac non-flat works better, on Linux flat is standard
4861 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4863 if (lyxrc.allow_geometry_session)
4864 dialog->restoreSession();
4871 void GuiView::showDialog(string const & name, string const & sdata,
4874 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4878 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4884 const string name = fromqstr(qname);
4885 const string sdata = fromqstr(qdata);
4889 Dialog * dialog = findOrBuild(name, false);
4891 bool const visible = dialog->isVisibleView();
4892 dialog->showData(sdata);
4893 if (currentBufferView())
4894 currentBufferView()->editInset(name, inset);
4895 // We only set the focus to the new dialog if it was not yet
4896 // visible in order not to change the existing previous behaviour
4898 // activateWindow is needed for floating dockviews
4899 dialog->asQWidget()->raise();
4900 dialog->asQWidget()->activateWindow();
4901 if (dialog->wantInitialFocus())
4902 dialog->asQWidget()->setFocus();
4906 catch (ExceptionMessage const &) {
4914 bool GuiView::isDialogVisible(string const & name) const
4916 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4917 if (it == d.dialogs_.end())
4919 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4923 void GuiView::hideDialog(string const & name, Inset * inset)
4925 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4926 if (it == d.dialogs_.end())
4930 if (!currentBufferView())
4932 if (inset != currentBufferView()->editedInset(name))
4936 Dialog * const dialog = it->second.get();
4937 if (dialog->isVisibleView())
4939 if (currentBufferView())
4940 currentBufferView()->editInset(name, nullptr);
4944 void GuiView::disconnectDialog(string const & name)
4946 if (!isValidName(name))
4948 if (currentBufferView())
4949 currentBufferView()->editInset(name, nullptr);
4953 void GuiView::hideAll() const
4955 for(auto const & dlg_p : d.dialogs_)
4956 dlg_p.second->hideView();
4960 void GuiView::updateDialogs()
4962 for(auto const & dlg_p : d.dialogs_) {
4963 Dialog * dialog = dlg_p.second.get();
4965 if (dialog->needBufferOpen() && !documentBufferView())
4966 hideDialog(fromqstr(dialog->name()), nullptr);
4967 else if (dialog->isVisibleView())
4968 dialog->checkStatus();
4976 Dialog * GuiView::build(string const & name)
4978 return createDialog(*this, name);
4982 SEMenu::SEMenu(QWidget * parent)
4984 QAction * action = addAction(qt_("Disable Shell Escape"));
4985 connect(action, SIGNAL(triggered()),
4986 parent, SLOT(disableShellEscape()));
4989 } // namespace frontend
4992 #include "moc_GuiView.cpp"