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 setAcceptDrops(true);
608 // add busy indicator to statusbar
609 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
610 statusBar()->addPermanentWidget(busylabel);
611 search_mode mode = theGuiApp()->imageSearchMode();
612 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
613 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
614 busylabel->setMovie(busyanim);
618 connect(&d.processing_thread_watcher_, SIGNAL(started()),
619 busylabel, SLOT(show()));
620 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
621 busylabel, SLOT(hide()));
622 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
624 QFontMetrics const fm(statusBar()->fontMetrics());
625 int const iconheight = max(int(d.normalIconSize), fm.height());
626 QSize const iconsize(iconheight, iconheight);
628 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
629 shell_escape_ = new QLabel(statusBar());
630 shell_escape_->setPixmap(shellescape);
631 shell_escape_->setScaledContents(true);
632 shell_escape_->setAlignment(Qt::AlignCenter);
633 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
634 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
635 "external commands for this document. "
636 "Right click to change."));
637 SEMenu * menu = new SEMenu(this);
638 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
639 menu, SLOT(showMenu(QPoint)));
640 shell_escape_->hide();
641 statusBar()->addPermanentWidget(shell_escape_);
643 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
644 read_only_ = new QLabel(statusBar());
645 read_only_->setPixmap(readonly);
646 read_only_->setScaledContents(true);
647 read_only_->setAlignment(Qt::AlignCenter);
649 statusBar()->addPermanentWidget(read_only_);
651 version_control_ = new QLabel(statusBar());
652 version_control_->setAlignment(Qt::AlignCenter);
653 version_control_->setFrameStyle(QFrame::StyledPanel);
654 version_control_->hide();
655 statusBar()->addPermanentWidget(version_control_);
657 statusBar()->setSizeGripEnabled(true);
660 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
661 SLOT(autoSaveThreadFinished()));
663 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
664 SLOT(processingThreadStarted()));
665 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
666 SLOT(processingThreadFinished()));
668 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
669 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
671 // set custom application bars context menu, e.g. tool bar and menu bar
672 setContextMenuPolicy(Qt::CustomContextMenu);
673 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
674 SLOT(toolBarPopup(const QPoint &)));
676 // Forbid too small unresizable window because it can happen
677 // with some window manager under X11.
678 setMinimumSize(300, 200);
680 if (lyxrc.allow_geometry_session) {
681 // Now take care of session management.
686 // no session handling, default to a sane size.
687 setGeometry(50, 50, 690, 510);
690 // clear session data if any.
692 settings.remove("views");
702 void GuiView::disableShellEscape()
704 BufferView * bv = documentBufferView();
707 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
708 bv->buffer().params().shell_escape = false;
709 bv->processUpdateFlags(Update::Force);
713 void GuiView::checkCancelBackground()
715 docstring const ttl = _("Cancel Export?");
716 docstring const msg = _("Do you want to cancel the background export process?");
718 Alert::prompt(ttl, msg, 1, 1,
719 _("&Cancel export"), _("Co&ntinue"));
721 Systemcall::killscript();
725 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
727 QVector<GuiWorkArea*> areas;
728 for (int i = 0; i < tabWorkAreaCount(); i++) {
729 TabWorkArea* ta = tabWorkArea(i);
730 for (int u = 0; u < ta->count(); u++) {
731 areas << ta->workArea(u);
737 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
738 string const & format)
740 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
743 case Buffer::ExportSuccess:
744 msg = bformat(_("Successful export to format: %1$s"), fmt);
746 case Buffer::ExportCancel:
747 msg = _("Document export cancelled.");
749 case Buffer::ExportError:
750 case Buffer::ExportNoPathToFormat:
751 case Buffer::ExportTexPathHasSpaces:
752 case Buffer::ExportConverterError:
753 msg = bformat(_("Error while exporting format: %1$s"), fmt);
755 case Buffer::PreviewSuccess:
756 msg = bformat(_("Successful preview of format: %1$s"), fmt);
758 case Buffer::PreviewError:
759 msg = bformat(_("Error while previewing format: %1$s"), fmt);
761 case Buffer::ExportKilled:
762 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
769 void GuiView::processingThreadStarted()
774 void GuiView::processingThreadFinished()
776 QFutureWatcher<Buffer::ExportStatus> const * watcher =
777 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
779 Buffer::ExportStatus const status = watcher->result();
780 handleExportStatus(this, status, d.processing_format);
783 BufferView const * const bv = currentBufferView();
784 if (bv && !bv->buffer().errorList("Export").empty()) {
789 bool const error = (status != Buffer::ExportSuccess &&
790 status != Buffer::PreviewSuccess &&
791 status != Buffer::ExportCancel);
793 ErrorList & el = bv->buffer().errorList(d.last_export_format);
794 // at this point, we do not know if buffer-view or
795 // master-buffer-view was called. If there was an export error,
796 // and the current buffer's error log is empty, we guess that
797 // it must be master-buffer-view that was called so we set
799 errors(d.last_export_format, el.empty());
804 void GuiView::autoSaveThreadFinished()
806 QFutureWatcher<docstring> const * watcher =
807 static_cast<QFutureWatcher<docstring> const *>(sender());
808 message(watcher->result());
813 void GuiView::saveLayout() const
816 settings.setValue("zoom_ratio", zoom_ratio_);
817 settings.setValue("devel_mode", devel_mode_);
818 settings.beginGroup("views");
819 settings.beginGroup(QString::number(id_));
820 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
821 settings.setValue("pos", pos());
822 settings.setValue("size", size());
824 settings.setValue("geometry", saveGeometry());
825 settings.setValue("layout", saveState(0));
826 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
830 void GuiView::saveUISettings() const
834 // Save the toolbar private states
835 for (auto const & tb_p : d.toolbars_)
836 tb_p.second->saveSession(settings);
837 // Now take care of all other dialogs
838 for (auto const & dlg_p : d.dialogs_)
839 dlg_p.second->saveSession(settings);
843 bool GuiView::restoreLayout()
846 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
847 // Actual zoom value: default zoom + fractional offset
848 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
849 if (zoom < static_cast<int>(zoom_min_))
851 lyxrc.currentZoom = zoom;
852 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
853 settings.beginGroup("views");
854 settings.beginGroup(QString::number(id_));
855 QString const icon_key = "icon_size";
856 if (!settings.contains(icon_key))
859 //code below is skipped when when ~/.config/LyX is (re)created
860 setIconSize(d.iconSize(settings.value(icon_key).toString()));
862 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
863 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
864 QSize size = settings.value("size", QSize(690, 510)).toSize();
868 // Work-around for bug #6034: the window ends up in an undetermined
869 // state when trying to restore a maximized window when it is
870 // already maximized.
871 if (!(windowState() & Qt::WindowMaximized))
872 if (!restoreGeometry(settings.value("geometry").toByteArray()))
873 setGeometry(50, 50, 690, 510);
876 // Make sure layout is correctly oriented.
877 setLayoutDirection(qApp->layoutDirection());
879 // Allow the toc and view-source dock widget to be restored if needed.
881 if ((dialog = findOrBuild("toc", true)))
882 // see bug 5082. At least setup title and enabled state.
883 // Visibility will be adjusted by restoreState below.
884 dialog->prepareView();
885 if ((dialog = findOrBuild("view-source", true)))
886 dialog->prepareView();
887 if ((dialog = findOrBuild("progress", true)))
888 dialog->prepareView();
890 if (!restoreState(settings.value("layout").toByteArray(), 0))
893 // init the toolbars that have not been restored
894 for (auto const & tb_p : guiApp->toolbars()) {
895 GuiToolbar * tb = toolbar(tb_p.name);
896 if (tb && !tb->isRestored())
897 initToolbar(tb_p.name);
900 // update lock (all) toolbars positions
901 updateLockToolbars();
908 GuiToolbar * GuiView::toolbar(string const & name)
910 ToolbarMap::iterator it = d.toolbars_.find(name);
911 if (it != d.toolbars_.end())
914 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
919 void GuiView::updateLockToolbars()
921 toolbarsMovable_ = false;
922 for (ToolbarInfo const & info : guiApp->toolbars()) {
923 GuiToolbar * tb = toolbar(info.name);
924 if (tb && tb->isMovable())
925 toolbarsMovable_ = true;
930 void GuiView::constructToolbars()
932 for (auto const & tb_p : d.toolbars_)
936 // I don't like doing this here, but the standard toolbar
937 // destroys this object when it's destroyed itself (vfr)
938 d.layout_ = new LayoutBox(*this);
939 d.stack_widget_->addWidget(d.layout_);
940 d.layout_->move(0,0);
942 // extracts the toolbars from the backend
943 for (ToolbarInfo const & inf : guiApp->toolbars())
944 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
948 void GuiView::initToolbars()
950 // extracts the toolbars from the backend
951 for (ToolbarInfo const & inf : guiApp->toolbars())
952 initToolbar(inf.name);
956 void GuiView::initToolbar(string const & name)
958 GuiToolbar * tb = toolbar(name);
961 int const visibility = guiApp->toolbars().defaultVisibility(name);
962 bool newline = !(visibility & Toolbars::SAMEROW);
963 tb->setVisible(false);
964 tb->setVisibility(visibility);
966 if (visibility & Toolbars::TOP) {
968 addToolBarBreak(Qt::TopToolBarArea);
969 addToolBar(Qt::TopToolBarArea, tb);
972 if (visibility & Toolbars::BOTTOM) {
974 addToolBarBreak(Qt::BottomToolBarArea);
975 addToolBar(Qt::BottomToolBarArea, tb);
978 if (visibility & Toolbars::LEFT) {
980 addToolBarBreak(Qt::LeftToolBarArea);
981 addToolBar(Qt::LeftToolBarArea, tb);
984 if (visibility & Toolbars::RIGHT) {
986 addToolBarBreak(Qt::RightToolBarArea);
987 addToolBar(Qt::RightToolBarArea, tb);
990 if (visibility & Toolbars::ON)
991 tb->setVisible(true);
993 tb->setMovable(true);
997 TocModels & GuiView::tocModels()
999 return d.toc_models_;
1003 void GuiView::setFocus()
1005 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1006 QMainWindow::setFocus();
1010 bool GuiView::hasFocus() const
1012 if (currentWorkArea())
1013 return currentWorkArea()->hasFocus();
1014 if (currentMainWorkArea())
1015 return currentMainWorkArea()->hasFocus();
1016 return d.bg_widget_->hasFocus();
1020 void GuiView::focusInEvent(QFocusEvent * e)
1022 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1023 QMainWindow::focusInEvent(e);
1024 // Make sure guiApp points to the correct view.
1025 guiApp->setCurrentView(this);
1026 if (currentWorkArea())
1027 currentWorkArea()->setFocus();
1028 else if (currentMainWorkArea())
1029 currentMainWorkArea()->setFocus();
1031 d.bg_widget_->setFocus();
1035 void GuiView::showEvent(QShowEvent * e)
1037 LYXERR(Debug::GUI, "Passed Geometry "
1038 << size().height() << "x" << size().width()
1039 << "+" << pos().x() << "+" << pos().y());
1041 if (d.splitter_->count() == 0)
1042 // No work area, switch to the background widget.
1046 QMainWindow::showEvent(e);
1050 bool GuiView::closeScheduled()
1057 bool GuiView::prepareAllBuffersForLogout()
1059 Buffer * first = theBufferList().first();
1063 // First, iterate over all buffers and ask the users if unsaved
1064 // changes should be saved.
1065 // We cannot use a for loop as the buffer list cycles.
1068 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1070 b = theBufferList().next(b);
1071 } while (b != first);
1073 // Next, save session state
1074 // When a view/window was closed before without quitting LyX, there
1075 // are already entries in the lastOpened list.
1076 theSession().lastOpened().clear();
1083 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1084 ** is responsibility of the container (e.g., dialog)
1086 void GuiView::closeEvent(QCloseEvent * close_event)
1088 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1090 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1091 Alert::warning(_("Exit LyX"),
1092 _("LyX could not be closed because documents are being processed by LyX."));
1093 close_event->setAccepted(false);
1097 // If the user pressed the x (so we didn't call closeView
1098 // programmatically), we want to clear all existing entries.
1100 theSession().lastOpened().clear();
1105 // it can happen that this event arrives without selecting the view,
1106 // e.g. when clicking the close button on a background window.
1108 if (!closeWorkAreaAll()) {
1110 close_event->ignore();
1114 // Make sure that nothing will use this to be closed View.
1115 guiApp->unregisterView(this);
1117 if (isFullScreen()) {
1118 // Switch off fullscreen before closing.
1123 // Make sure the timer time out will not trigger a statusbar update.
1124 d.statusbar_timer_.stop();
1126 // Saving fullscreen requires additional tweaks in the toolbar code.
1127 // It wouldn't also work under linux natively.
1128 if (lyxrc.allow_geometry_session) {
1133 close_event->accept();
1137 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1139 if (event->mimeData()->hasUrls())
1141 /// \todo Ask lyx-devel is this is enough:
1142 /// if (event->mimeData()->hasFormat("text/plain"))
1143 /// event->acceptProposedAction();
1147 void GuiView::dropEvent(QDropEvent * event)
1149 QList<QUrl> files = event->mimeData()->urls();
1150 if (files.isEmpty())
1153 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1154 for (int i = 0; i != files.size(); ++i) {
1155 string const file = os::internal_path(fromqstr(
1156 files.at(i).toLocalFile()));
1160 string const ext = support::getExtension(file);
1161 vector<const Format *> found_formats;
1163 // Find all formats that have the correct extension.
1164 for (const Format * fmt : theConverters().importableFormats())
1165 if (fmt->hasExtension(ext))
1166 found_formats.push_back(fmt);
1169 if (!found_formats.empty()) {
1170 if (found_formats.size() > 1) {
1171 //FIXME: show a dialog to choose the correct importable format
1172 LYXERR(Debug::FILES,
1173 "Multiple importable formats found, selecting first");
1175 string const arg = found_formats[0]->name() + " " + file;
1176 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1179 //FIXME: do we have to explicitly check whether it's a lyx file?
1180 LYXERR(Debug::FILES,
1181 "No formats found, trying to open it as a lyx file");
1182 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1184 // add the functions to the queue
1185 guiApp->addToFuncRequestQueue(cmd);
1188 // now process the collected functions. We perform the events
1189 // asynchronously. This prevents potential problems in case the
1190 // BufferView is closed within an event.
1191 guiApp->processFuncRequestQueueAsync();
1195 void GuiView::message(docstring const & str)
1197 if (ForkedProcess::iAmAChild())
1200 // call is moved to GUI-thread by GuiProgress
1201 d.progress_->appendMessage(toqstr(str));
1205 void GuiView::clearMessageText()
1207 message(docstring());
1211 void GuiView::updateStatusBarMessage(QString const & str)
1213 statusBar()->showMessage(str);
1214 d.statusbar_timer_.stop();
1215 d.statusbar_timer_.start(3000);
1219 void GuiView::clearMessage()
1221 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1222 // the hasFocus function mostly returns false, even if the focus is on
1223 // a workarea in this view.
1227 d.statusbar_timer_.stop();
1231 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1233 if (wa != d.current_work_area_
1234 || wa->bufferView().buffer().isInternal())
1236 Buffer const & buf = wa->bufferView().buffer();
1237 // Set the windows title
1238 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1239 if (buf.notifiesExternalModification()) {
1240 title = bformat(_("%1$s (modified externally)"), title);
1241 // If the external modification status has changed, then maybe the status of
1242 // buffer-save has changed too.
1246 title += from_ascii(" - LyX");
1248 setWindowTitle(toqstr(title));
1249 // Sets the path for the window: this is used by OSX to
1250 // allow a context click on the title bar showing a menu
1251 // with the path up to the file
1252 setWindowFilePath(toqstr(buf.absFileName()));
1253 // Tell Qt whether the current document is changed
1254 setWindowModified(!buf.isClean());
1256 if (buf.params().shell_escape)
1257 shell_escape_->show();
1259 shell_escape_->hide();
1261 if (buf.hasReadonlyFlag())
1266 if (buf.lyxvc().inUse()) {
1267 version_control_->show();
1268 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1270 version_control_->hide();
1274 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1276 if (d.current_work_area_)
1277 // disconnect the current work area from all slots
1278 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1280 disconnectBufferView();
1281 connectBufferView(wa->bufferView());
1282 connectBuffer(wa->bufferView().buffer());
1283 d.current_work_area_ = wa;
1284 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1285 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1286 QObject::connect(wa, SIGNAL(busy(bool)),
1287 this, SLOT(setBusy(bool)));
1288 // connection of a signal to a signal
1289 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1290 this, SIGNAL(bufferViewChanged()));
1291 Q_EMIT updateWindowTitle(wa);
1292 Q_EMIT bufferViewChanged();
1296 void GuiView::onBufferViewChanged()
1299 // Buffer-dependent dialogs must be updated. This is done here because
1300 // some dialogs require buffer()->text.
1305 void GuiView::on_lastWorkAreaRemoved()
1308 // We already are in a close event. Nothing more to do.
1311 if (d.splitter_->count() > 1)
1312 // We have a splitter so don't close anything.
1315 // Reset and updates the dialogs.
1316 Q_EMIT bufferViewChanged();
1321 if (lyxrc.open_buffers_in_tabs)
1322 // Nothing more to do, the window should stay open.
1325 if (guiApp->viewIds().size() > 1) {
1331 // On Mac we also close the last window because the application stay
1332 // resident in memory. On other platforms we don't close the last
1333 // window because this would quit the application.
1339 void GuiView::updateStatusBar()
1341 // let the user see the explicit message
1342 if (d.statusbar_timer_.isActive())
1349 void GuiView::showMessage()
1353 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1354 if (msg.isEmpty()) {
1355 BufferView const * bv = currentBufferView();
1357 msg = toqstr(bv->cursor().currentState(devel_mode_));
1359 msg = qt_("Welcome to LyX!");
1361 statusBar()->showMessage(msg);
1365 bool GuiView::event(QEvent * e)
1369 // Useful debug code:
1370 //case QEvent::ActivationChange:
1371 //case QEvent::WindowDeactivate:
1372 //case QEvent::Paint:
1373 //case QEvent::Enter:
1374 //case QEvent::Leave:
1375 //case QEvent::HoverEnter:
1376 //case QEvent::HoverLeave:
1377 //case QEvent::HoverMove:
1378 //case QEvent::StatusTip:
1379 //case QEvent::DragEnter:
1380 //case QEvent::DragLeave:
1381 //case QEvent::Drop:
1384 case QEvent::WindowStateChange: {
1385 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1386 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1387 bool result = QMainWindow::event(e);
1388 bool nfstate = (windowState() & Qt::WindowFullScreen);
1389 if (!ofstate && nfstate) {
1390 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1391 // switch to full-screen state
1392 if (lyxrc.full_screen_statusbar)
1393 statusBar()->hide();
1394 if (lyxrc.full_screen_menubar)
1396 if (lyxrc.full_screen_toolbars) {
1397 for (auto const & tb_p : d.toolbars_)
1398 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1399 tb_p.second->hide();
1401 for (int i = 0; i != d.splitter_->count(); ++i)
1402 d.tabWorkArea(i)->setFullScreen(true);
1403 #if QT_VERSION > 0x050903
1404 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1405 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1407 setContentsMargins(-2, -2, -2, -2);
1409 hideDialogs("prefs", nullptr);
1410 } else if (ofstate && !nfstate) {
1411 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1412 // switch back from full-screen state
1413 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1414 statusBar()->show();
1415 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1417 if (lyxrc.full_screen_toolbars) {
1418 for (auto const & tb_p : d.toolbars_)
1419 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1420 tb_p.second->show();
1423 for (int i = 0; i != d.splitter_->count(); ++i)
1424 d.tabWorkArea(i)->setFullScreen(false);
1425 #if QT_VERSION > 0x050903
1426 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1428 setContentsMargins(0, 0, 0, 0);
1432 case QEvent::WindowActivate: {
1433 GuiView * old_view = guiApp->currentView();
1434 if (this == old_view) {
1436 return QMainWindow::event(e);
1438 if (old_view && old_view->currentBufferView()) {
1439 // save current selection to the selection buffer to allow
1440 // middle-button paste in this window.
1441 cap::saveSelection(old_view->currentBufferView()->cursor());
1443 guiApp->setCurrentView(this);
1444 if (d.current_work_area_)
1445 on_currentWorkAreaChanged(d.current_work_area_);
1449 return QMainWindow::event(e);
1452 case QEvent::ShortcutOverride: {
1454 if (isFullScreen() && menuBar()->isHidden()) {
1455 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1456 // FIXME: we should also try to detect special LyX shortcut such as
1457 // Alt-P and Alt-M. Right now there is a hack in
1458 // GuiWorkArea::processKeySym() that hides again the menubar for
1460 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1462 return QMainWindow::event(e);
1465 return QMainWindow::event(e);
1469 return QMainWindow::event(e);
1473 void GuiView::resetWindowTitle()
1475 setWindowTitle(qt_("LyX"));
1478 bool GuiView::focusNextPrevChild(bool /*next*/)
1485 bool GuiView::busy() const
1491 void GuiView::setBusy(bool busy)
1493 bool const busy_before = busy_ > 0;
1494 busy ? ++busy_ : --busy_;
1495 if ((busy_ > 0) == busy_before)
1496 // busy state didn't change
1500 QApplication::setOverrideCursor(Qt::WaitCursor);
1503 QApplication::restoreOverrideCursor();
1508 void GuiView::resetCommandExecute()
1510 command_execute_ = false;
1515 double GuiView::pixelRatio() const
1517 #if QT_VERSION >= 0x050000
1518 return qt_scale_factor * devicePixelRatio();
1525 GuiWorkArea * GuiView::workArea(int index)
1527 if (TabWorkArea * twa = d.currentTabWorkArea())
1528 if (index < twa->count())
1529 return twa->workArea(index);
1534 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1536 if (currentWorkArea()
1537 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1538 return currentWorkArea();
1539 if (TabWorkArea * twa = d.currentTabWorkArea())
1540 return twa->workArea(buffer);
1545 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1547 // Automatically create a TabWorkArea if there are none yet.
1548 TabWorkArea * tab_widget = d.splitter_->count()
1549 ? d.currentTabWorkArea() : addTabWorkArea();
1550 return tab_widget->addWorkArea(buffer, *this);
1554 TabWorkArea * GuiView::addTabWorkArea()
1556 TabWorkArea * twa = new TabWorkArea;
1557 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1558 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1559 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1560 this, SLOT(on_lastWorkAreaRemoved()));
1562 d.splitter_->addWidget(twa);
1563 d.stack_widget_->setCurrentWidget(d.splitter_);
1568 GuiWorkArea const * GuiView::currentWorkArea() const
1570 return d.current_work_area_;
1574 GuiWorkArea * GuiView::currentWorkArea()
1576 return d.current_work_area_;
1580 GuiWorkArea const * GuiView::currentMainWorkArea() const
1582 if (!d.currentTabWorkArea())
1584 return d.currentTabWorkArea()->currentWorkArea();
1588 GuiWorkArea * GuiView::currentMainWorkArea()
1590 if (!d.currentTabWorkArea())
1592 return d.currentTabWorkArea()->currentWorkArea();
1596 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1598 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1600 d.current_work_area_ = nullptr;
1602 Q_EMIT bufferViewChanged();
1606 // FIXME: I've no clue why this is here and why it accesses
1607 // theGuiApp()->currentView, which might be 0 (bug 6464).
1608 // See also 27525 (vfr).
1609 if (theGuiApp()->currentView() == this
1610 && theGuiApp()->currentView()->currentWorkArea() == wa)
1613 if (currentBufferView())
1614 cap::saveSelection(currentBufferView()->cursor());
1616 theGuiApp()->setCurrentView(this);
1617 d.current_work_area_ = wa;
1619 // We need to reset this now, because it will need to be
1620 // right if the tabWorkArea gets reset in the for loop. We
1621 // will change it back if we aren't in that case.
1622 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1623 d.current_main_work_area_ = wa;
1625 for (int i = 0; i != d.splitter_->count(); ++i) {
1626 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1627 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1628 << ", Current main wa: " << currentMainWorkArea());
1633 d.current_main_work_area_ = old_cmwa;
1635 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1636 on_currentWorkAreaChanged(wa);
1637 BufferView & bv = wa->bufferView();
1638 bv.cursor().fixIfBroken();
1640 wa->setUpdatesEnabled(true);
1641 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1645 void GuiView::removeWorkArea(GuiWorkArea * wa)
1647 LASSERT(wa, return);
1648 if (wa == d.current_work_area_) {
1650 disconnectBufferView();
1651 d.current_work_area_ = nullptr;
1652 d.current_main_work_area_ = nullptr;
1655 bool found_twa = false;
1656 for (int i = 0; i != d.splitter_->count(); ++i) {
1657 TabWorkArea * twa = d.tabWorkArea(i);
1658 if (twa->removeWorkArea(wa)) {
1659 // Found in this tab group, and deleted the GuiWorkArea.
1661 if (twa->count() != 0) {
1662 if (d.current_work_area_ == nullptr)
1663 // This means that we are closing the current GuiWorkArea, so
1664 // switch to the next GuiWorkArea in the found TabWorkArea.
1665 setCurrentWorkArea(twa->currentWorkArea());
1667 // No more WorkAreas in this tab group, so delete it.
1674 // It is not a tabbed work area (i.e., the search work area), so it
1675 // should be deleted by other means.
1676 LASSERT(found_twa, return);
1678 if (d.current_work_area_ == nullptr) {
1679 if (d.splitter_->count() != 0) {
1680 TabWorkArea * twa = d.currentTabWorkArea();
1681 setCurrentWorkArea(twa->currentWorkArea());
1683 // No more work areas, switch to the background widget.
1684 setCurrentWorkArea(nullptr);
1690 LayoutBox * GuiView::getLayoutDialog() const
1696 void GuiView::updateLayoutList()
1699 d.layout_->updateContents(false);
1703 void GuiView::updateToolbars()
1705 if (d.current_work_area_) {
1707 if (d.current_work_area_->bufferView().cursor().inMathed()
1708 && !d.current_work_area_->bufferView().cursor().inRegexped())
1709 context |= Toolbars::MATH;
1710 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1711 context |= Toolbars::TABLE;
1712 if (currentBufferView()->buffer().areChangesPresent()
1713 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1714 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1715 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1716 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1717 context |= Toolbars::REVIEW;
1718 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1719 context |= Toolbars::MATHMACROTEMPLATE;
1720 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1721 context |= Toolbars::IPA;
1722 if (command_execute_)
1723 context |= Toolbars::MINIBUFFER;
1724 if (minibuffer_focus_) {
1725 context |= Toolbars::MINIBUFFER_FOCUS;
1726 minibuffer_focus_ = false;
1729 for (auto const & tb_p : d.toolbars_)
1730 tb_p.second->update(context);
1732 for (auto const & tb_p : d.toolbars_)
1733 tb_p.second->update();
1737 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1739 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1740 LASSERT(newBuffer, return);
1742 GuiWorkArea * wa = workArea(*newBuffer);
1743 if (wa == nullptr) {
1745 newBuffer->masterBuffer()->updateBuffer();
1747 wa = addWorkArea(*newBuffer);
1748 // scroll to the position when the BufferView was last closed
1749 if (lyxrc.use_lastfilepos) {
1750 LastFilePosSection::FilePos filepos =
1751 theSession().lastFilePos().load(newBuffer->fileName());
1752 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1755 //Disconnect the old buffer...there's no new one.
1758 connectBuffer(*newBuffer);
1759 connectBufferView(wa->bufferView());
1761 setCurrentWorkArea(wa);
1765 void GuiView::connectBuffer(Buffer & buf)
1767 buf.setGuiDelegate(this);
1771 void GuiView::disconnectBuffer()
1773 if (d.current_work_area_)
1774 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1778 void GuiView::connectBufferView(BufferView & bv)
1780 bv.setGuiDelegate(this);
1784 void GuiView::disconnectBufferView()
1786 if (d.current_work_area_)
1787 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1791 void GuiView::errors(string const & error_type, bool from_master)
1793 BufferView const * const bv = currentBufferView();
1797 ErrorList const & el = from_master ?
1798 bv->buffer().masterBuffer()->errorList(error_type) :
1799 bv->buffer().errorList(error_type);
1804 string err = error_type;
1806 err = "from_master|" + error_type;
1807 showDialog("errorlist", err);
1811 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1813 d.toc_models_.updateItem(toqstr(type), dit);
1817 void GuiView::structureChanged()
1819 // This is called from the Buffer, which has no way to ensure that cursors
1820 // in BufferView remain valid.
1821 if (documentBufferView())
1822 documentBufferView()->cursor().sanitize();
1823 // FIXME: This is slightly expensive, though less than the tocBackend update
1824 // (#9880). This also resets the view in the Toc Widget (#6675).
1825 d.toc_models_.reset(documentBufferView());
1826 // Navigator needs more than a simple update in this case. It needs to be
1828 updateDialog("toc", "");
1832 void GuiView::updateDialog(string const & name, string const & sdata)
1834 if (!isDialogVisible(name))
1837 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1838 if (it == d.dialogs_.end())
1841 Dialog * const dialog = it->second.get();
1842 if (dialog->isVisibleView())
1843 dialog->initialiseParams(sdata);
1847 BufferView * GuiView::documentBufferView()
1849 return currentMainWorkArea()
1850 ? ¤tMainWorkArea()->bufferView()
1855 BufferView const * GuiView::documentBufferView() const
1857 return currentMainWorkArea()
1858 ? ¤tMainWorkArea()->bufferView()
1863 BufferView * GuiView::currentBufferView()
1865 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1869 BufferView const * GuiView::currentBufferView() const
1871 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1875 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1876 Buffer const * orig, Buffer * clone)
1878 bool const success = clone->autoSave();
1880 busyBuffers.remove(orig);
1882 ? _("Automatic save done.")
1883 : _("Automatic save failed!");
1887 void GuiView::autoSave()
1889 LYXERR(Debug::INFO, "Running autoSave()");
1891 Buffer * buffer = documentBufferView()
1892 ? &documentBufferView()->buffer() : nullptr;
1894 resetAutosaveTimers();
1898 GuiViewPrivate::busyBuffers.insert(buffer);
1899 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1900 buffer, buffer->cloneBufferOnly());
1901 d.autosave_watcher_.setFuture(f);
1902 resetAutosaveTimers();
1906 void GuiView::resetAutosaveTimers()
1909 d.autosave_timeout_.restart();
1913 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1916 Buffer * buf = currentBufferView()
1917 ? ¤tBufferView()->buffer() : nullptr;
1918 Buffer * doc_buffer = documentBufferView()
1919 ? &(documentBufferView()->buffer()) : nullptr;
1922 /* In LyX/Mac, when a dialog is open, the menus of the
1923 application can still be accessed without giving focus to
1924 the main window. In this case, we want to disable the menu
1925 entries that are buffer-related.
1926 This code must not be used on Linux and Windows, since it
1927 would disable buffer-related entries when hovering over the
1928 menu (see bug #9574).
1930 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1936 // Check whether we need a buffer
1937 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1938 // no, exit directly
1939 flag.message(from_utf8(N_("Command not allowed with"
1940 "out any document open")));
1941 flag.setEnabled(false);
1945 if (cmd.origin() == FuncRequest::TOC) {
1946 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1947 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1948 flag.setEnabled(false);
1952 switch(cmd.action()) {
1953 case LFUN_BUFFER_IMPORT:
1956 case LFUN_MASTER_BUFFER_EXPORT:
1958 && (doc_buffer->parent() != nullptr
1959 || doc_buffer->hasChildren())
1960 && !d.processing_thread_watcher_.isRunning()
1961 // this launches a dialog, which would be in the wrong Buffer
1962 && !(::lyx::operator==(cmd.argument(), "custom"));
1965 case LFUN_MASTER_BUFFER_UPDATE:
1966 case LFUN_MASTER_BUFFER_VIEW:
1968 && (doc_buffer->parent() != nullptr
1969 || doc_buffer->hasChildren())
1970 && !d.processing_thread_watcher_.isRunning();
1973 case LFUN_BUFFER_UPDATE:
1974 case LFUN_BUFFER_VIEW: {
1975 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1979 string format = to_utf8(cmd.argument());
1980 if (cmd.argument().empty())
1981 format = doc_buffer->params().getDefaultOutputFormat();
1982 enable = doc_buffer->params().isExportable(format, true);
1986 case LFUN_BUFFER_RELOAD:
1987 enable = doc_buffer && !doc_buffer->isUnnamed()
1988 && doc_buffer->fileName().exists()
1989 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1992 case LFUN_BUFFER_RESET_EXPORT:
1993 enable = doc_buffer != nullptr;
1996 case LFUN_BUFFER_CHILD_OPEN:
1997 enable = doc_buffer != nullptr;
2000 case LFUN_MASTER_BUFFER_FORALL: {
2001 if (doc_buffer == nullptr) {
2002 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2006 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2007 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2008 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2013 for (Buffer * buf : doc_buffer->allRelatives()) {
2014 GuiWorkArea * wa = workArea(*buf);
2017 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2018 enable = flag.enabled();
2025 case LFUN_BUFFER_WRITE:
2026 enable = doc_buffer && (doc_buffer->isUnnamed()
2027 || (!doc_buffer->isClean()
2028 || cmd.argument() == "force"));
2031 //FIXME: This LFUN should be moved to GuiApplication.
2032 case LFUN_BUFFER_WRITE_ALL: {
2033 // We enable the command only if there are some modified buffers
2034 Buffer * first = theBufferList().first();
2039 // We cannot use a for loop as the buffer list is a cycle.
2041 if (!b->isClean()) {
2045 b = theBufferList().next(b);
2046 } while (b != first);
2050 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2051 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2054 case LFUN_BUFFER_EXPORT: {
2055 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2059 return doc_buffer->getStatus(cmd, flag);
2062 case LFUN_BUFFER_EXPORT_AS:
2063 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2068 case LFUN_BUFFER_WRITE_AS:
2069 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2070 enable = doc_buffer != nullptr;
2073 case LFUN_EXPORT_CANCEL:
2074 enable = d.processing_thread_watcher_.isRunning();
2077 case LFUN_BUFFER_CLOSE:
2078 case LFUN_VIEW_CLOSE:
2079 enable = doc_buffer != nullptr;
2082 case LFUN_BUFFER_CLOSE_ALL:
2083 enable = theBufferList().last() != theBufferList().first();
2086 case LFUN_BUFFER_CHKTEX: {
2087 // hide if we have no checktex command
2088 if (lyxrc.chktex_command.empty()) {
2089 flag.setUnknown(true);
2093 if (!doc_buffer || !doc_buffer->params().isLatex()
2094 || d.processing_thread_watcher_.isRunning()) {
2095 // grey out, don't hide
2103 case LFUN_VIEW_SPLIT:
2104 if (cmd.getArg(0) == "vertical")
2105 enable = doc_buffer && (d.splitter_->count() == 1 ||
2106 d.splitter_->orientation() == Qt::Vertical);
2108 enable = doc_buffer && (d.splitter_->count() == 1 ||
2109 d.splitter_->orientation() == Qt::Horizontal);
2112 case LFUN_TAB_GROUP_CLOSE:
2113 enable = d.tabWorkAreaCount() > 1;
2116 case LFUN_DEVEL_MODE_TOGGLE:
2117 flag.setOnOff(devel_mode_);
2120 case LFUN_TOOLBAR_SET: {
2121 string const name = cmd.getArg(0);
2122 string const state = cmd.getArg(1);
2123 if (name.empty() || state.empty()) {
2125 docstring const msg =
2126 _("Function toolbar-set requires two arguments!");
2130 if (state != "on" && state != "off" && state != "auto") {
2132 docstring const msg =
2133 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2138 if (GuiToolbar * t = toolbar(name)) {
2139 bool const autovis = t->visibility() & Toolbars::AUTO;
2141 flag.setOnOff(t->isVisible() && !autovis);
2142 else if (state == "off")
2143 flag.setOnOff(!t->isVisible() && !autovis);
2144 else if (state == "auto")
2145 flag.setOnOff(autovis);
2148 docstring const msg =
2149 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2155 case LFUN_TOOLBAR_TOGGLE: {
2156 string const name = cmd.getArg(0);
2157 if (GuiToolbar * t = toolbar(name))
2158 flag.setOnOff(t->isVisible());
2161 docstring const msg =
2162 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2168 case LFUN_TOOLBAR_MOVABLE: {
2169 string const name = cmd.getArg(0);
2170 // use negation since locked == !movable
2172 // toolbar name * locks all toolbars
2173 flag.setOnOff(!toolbarsMovable_);
2174 else if (GuiToolbar * t = toolbar(name))
2175 flag.setOnOff(!(t->isMovable()));
2178 docstring const msg =
2179 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2185 case LFUN_ICON_SIZE:
2186 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2189 case LFUN_DROP_LAYOUTS_CHOICE:
2190 enable = buf != nullptr;
2193 case LFUN_UI_TOGGLE:
2194 flag.setOnOff(isFullScreen());
2197 case LFUN_DIALOG_DISCONNECT_INSET:
2200 case LFUN_DIALOG_HIDE:
2201 // FIXME: should we check if the dialog is shown?
2204 case LFUN_DIALOG_TOGGLE:
2205 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2208 case LFUN_DIALOG_SHOW: {
2209 string const name = cmd.getArg(0);
2211 enable = name == "aboutlyx"
2212 || name == "file" //FIXME: should be removed.
2213 || name == "lyxfiles"
2215 || name == "texinfo"
2216 || name == "progress"
2217 || name == "compare";
2218 else if (name == "character" || name == "symbols"
2219 || name == "mathdelimiter" || name == "mathmatrix") {
2220 if (!buf || buf->isReadonly())
2223 Cursor const & cur = currentBufferView()->cursor();
2224 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2227 else if (name == "latexlog")
2228 enable = FileName(doc_buffer->logName()).isReadableFile();
2229 else if (name == "spellchecker")
2230 enable = theSpellChecker()
2231 && !doc_buffer->text().empty();
2232 else if (name == "vclog")
2233 enable = doc_buffer->lyxvc().inUse();
2237 case LFUN_DIALOG_UPDATE: {
2238 string const name = cmd.getArg(0);
2240 enable = name == "prefs";
2244 case LFUN_COMMAND_EXECUTE:
2246 case LFUN_MENU_OPEN:
2247 // Nothing to check.
2250 case LFUN_COMPLETION_INLINE:
2251 if (!d.current_work_area_
2252 || !d.current_work_area_->completer().inlinePossible(
2253 currentBufferView()->cursor()))
2257 case LFUN_COMPLETION_POPUP:
2258 if (!d.current_work_area_
2259 || !d.current_work_area_->completer().popupPossible(
2260 currentBufferView()->cursor()))
2265 if (!d.current_work_area_
2266 || !d.current_work_area_->completer().inlinePossible(
2267 currentBufferView()->cursor()))
2271 case LFUN_COMPLETION_ACCEPT:
2272 if (!d.current_work_area_
2273 || (!d.current_work_area_->completer().popupVisible()
2274 && !d.current_work_area_->completer().inlineVisible()
2275 && !d.current_work_area_->completer().completionAvailable()))
2279 case LFUN_COMPLETION_CANCEL:
2280 if (!d.current_work_area_
2281 || (!d.current_work_area_->completer().popupVisible()
2282 && !d.current_work_area_->completer().inlineVisible()))
2286 case LFUN_BUFFER_ZOOM_OUT:
2287 case LFUN_BUFFER_ZOOM_IN: {
2288 // only diff between these two is that the default for ZOOM_OUT
2290 bool const neg_zoom =
2291 convert<int>(cmd.argument()) < 0 ||
2292 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2293 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2294 docstring const msg =
2295 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2299 enable = doc_buffer;
2303 case LFUN_BUFFER_ZOOM: {
2304 bool const less_than_min_zoom =
2305 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2306 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2307 docstring const msg =
2308 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2313 enable = doc_buffer;
2317 case LFUN_BUFFER_MOVE_NEXT:
2318 case LFUN_BUFFER_MOVE_PREVIOUS:
2319 // we do not cycle when moving
2320 case LFUN_BUFFER_NEXT:
2321 case LFUN_BUFFER_PREVIOUS:
2322 // because we cycle, it doesn't matter whether on first or last
2323 enable = (d.currentTabWorkArea()->count() > 1);
2325 case LFUN_BUFFER_SWITCH:
2326 // toggle on the current buffer, but do not toggle off
2327 // the other ones (is that a good idea?)
2329 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2330 flag.setOnOff(true);
2333 case LFUN_VC_REGISTER:
2334 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2336 case LFUN_VC_RENAME:
2337 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2340 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2342 case LFUN_VC_CHECK_IN:
2343 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2345 case LFUN_VC_CHECK_OUT:
2346 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2348 case LFUN_VC_LOCKING_TOGGLE:
2349 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2350 && doc_buffer->lyxvc().lockingToggleEnabled();
2351 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2353 case LFUN_VC_REVERT:
2354 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2355 && !doc_buffer->hasReadonlyFlag();
2357 case LFUN_VC_UNDO_LAST:
2358 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2360 case LFUN_VC_REPO_UPDATE:
2361 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2363 case LFUN_VC_COMMAND: {
2364 if (cmd.argument().empty())
2366 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2370 case LFUN_VC_COMPARE:
2371 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2374 case LFUN_SERVER_GOTO_FILE_ROW:
2375 case LFUN_LYX_ACTIVATE:
2376 case LFUN_WINDOW_RAISE:
2378 case LFUN_FORWARD_SEARCH:
2379 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2382 case LFUN_FILE_INSERT_PLAINTEXT:
2383 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2384 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2387 case LFUN_SPELLING_CONTINUOUSLY:
2388 flag.setOnOff(lyxrc.spellcheck_continuously);
2391 case LFUN_CITATION_OPEN:
2400 flag.setEnabled(false);
2406 static FileName selectTemplateFile()
2408 FileDialog dlg(qt_("Select template file"));
2409 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2410 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2412 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2413 QStringList(qt_("LyX Documents (*.lyx)")));
2415 if (result.first == FileDialog::Later)
2417 if (result.second.isEmpty())
2419 return FileName(fromqstr(result.second));
2423 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2427 Buffer * newBuffer = nullptr;
2429 newBuffer = checkAndLoadLyXFile(filename);
2430 } catch (ExceptionMessage const &) {
2437 message(_("Document not loaded."));
2441 setBuffer(newBuffer);
2442 newBuffer->errors("Parse");
2445 theSession().lastFiles().add(filename);
2446 theSession().writeFile();
2453 void GuiView::openDocument(string const & fname)
2455 string initpath = lyxrc.document_path;
2457 if (documentBufferView()) {
2458 string const trypath = documentBufferView()->buffer().filePath();
2459 // If directory is writeable, use this as default.
2460 if (FileName(trypath).isDirWritable())
2466 if (fname.empty()) {
2467 FileDialog dlg(qt_("Select document to open"));
2468 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2469 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2471 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2472 FileDialog::Result result =
2473 dlg.open(toqstr(initpath), filter);
2475 if (result.first == FileDialog::Later)
2478 filename = fromqstr(result.second);
2480 // check selected filename
2481 if (filename.empty()) {
2482 message(_("Canceled."));
2488 // get absolute path of file and add ".lyx" to the filename if
2490 FileName const fullname =
2491 fileSearch(string(), filename, "lyx", support::may_not_exist);
2492 if (!fullname.empty())
2493 filename = fullname.absFileName();
2495 if (!fullname.onlyPath().isDirectory()) {
2496 Alert::warning(_("Invalid filename"),
2497 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2498 from_utf8(fullname.absFileName())));
2502 // if the file doesn't exist and isn't already open (bug 6645),
2503 // let the user create one
2504 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2505 !LyXVC::file_not_found_hook(fullname)) {
2506 // the user specifically chose this name. Believe him.
2507 Buffer * const b = newFile(filename, string(), true);
2513 docstring const disp_fn = makeDisplayPath(filename);
2514 message(bformat(_("Opening document %1$s..."), disp_fn));
2517 Buffer * buf = loadDocument(fullname);
2519 str2 = bformat(_("Document %1$s opened."), disp_fn);
2520 if (buf->lyxvc().inUse())
2521 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2522 " " + _("Version control detected.");
2524 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2529 // FIXME: clean that
2530 static bool import(GuiView * lv, FileName const & filename,
2531 string const & format, ErrorList & errorList)
2533 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2535 string loader_format;
2536 vector<string> loaders = theConverters().loaders();
2537 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2538 for (string const & loader : loaders) {
2539 if (!theConverters().isReachable(format, loader))
2542 string const tofile =
2543 support::changeExtension(filename.absFileName(),
2544 theFormats().extension(loader));
2545 if (theConverters().convert(nullptr, filename, FileName(tofile),
2546 filename, format, loader, errorList) != Converters::SUCCESS)
2548 loader_format = loader;
2551 if (loader_format.empty()) {
2552 frontend::Alert::error(_("Couldn't import file"),
2553 bformat(_("No information for importing the format %1$s."),
2554 translateIfPossible(theFormats().prettyName(format))));
2558 loader_format = format;
2560 if (loader_format == "lyx") {
2561 Buffer * buf = lv->loadDocument(lyxfile);
2565 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2569 bool as_paragraphs = loader_format == "textparagraph";
2570 string filename2 = (loader_format == format) ? filename.absFileName()
2571 : support::changeExtension(filename.absFileName(),
2572 theFormats().extension(loader_format));
2573 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2575 guiApp->setCurrentView(lv);
2576 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2583 void GuiView::importDocument(string const & argument)
2586 string filename = split(argument, format, ' ');
2588 LYXERR(Debug::INFO, format << " file: " << filename);
2590 // need user interaction
2591 if (filename.empty()) {
2592 string initpath = lyxrc.document_path;
2593 if (documentBufferView()) {
2594 string const trypath = documentBufferView()->buffer().filePath();
2595 // If directory is writeable, use this as default.
2596 if (FileName(trypath).isDirWritable())
2600 docstring const text = bformat(_("Select %1$s file to import"),
2601 translateIfPossible(theFormats().prettyName(format)));
2603 FileDialog dlg(toqstr(text));
2604 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2605 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2607 docstring filter = translateIfPossible(theFormats().prettyName(format));
2610 filter += from_utf8(theFormats().extensions(format));
2613 FileDialog::Result result =
2614 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2616 if (result.first == FileDialog::Later)
2619 filename = fromqstr(result.second);
2621 // check selected filename
2622 if (filename.empty())
2623 message(_("Canceled."));
2626 if (filename.empty())
2629 // get absolute path of file
2630 FileName const fullname(support::makeAbsPath(filename));
2632 // Can happen if the user entered a path into the dialog
2634 if (fullname.onlyFileName().empty()) {
2635 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2636 "Aborting import."),
2637 from_utf8(fullname.absFileName()));
2638 frontend::Alert::error(_("File name error"), msg);
2639 message(_("Canceled."));
2644 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2646 // Check if the document already is open
2647 Buffer * buf = theBufferList().getBuffer(lyxfile);
2650 if (!closeBuffer()) {
2651 message(_("Canceled."));
2656 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2658 // if the file exists already, and we didn't do
2659 // -i lyx thefile.lyx, warn
2660 if (lyxfile.exists() && fullname != lyxfile) {
2662 docstring text = bformat(_("The document %1$s already exists.\n\n"
2663 "Do you want to overwrite that document?"), displaypath);
2664 int const ret = Alert::prompt(_("Overwrite document?"),
2665 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2668 message(_("Canceled."));
2673 message(bformat(_("Importing %1$s..."), displaypath));
2674 ErrorList errorList;
2675 if (import(this, fullname, format, errorList))
2676 message(_("imported."));
2678 message(_("file not imported!"));
2680 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2684 void GuiView::newDocument(string const & filename, string templatefile,
2687 FileName initpath(lyxrc.document_path);
2688 if (documentBufferView()) {
2689 FileName const trypath(documentBufferView()->buffer().filePath());
2690 // If directory is writeable, use this as default.
2691 if (trypath.isDirWritable())
2695 if (from_template) {
2696 if (templatefile.empty())
2697 templatefile = selectTemplateFile().absFileName();
2698 if (templatefile.empty())
2703 if (filename.empty())
2704 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2706 b = newFile(filename, templatefile, true);
2711 // If no new document could be created, it is unsure
2712 // whether there is a valid BufferView.
2713 if (currentBufferView())
2714 // Ensure the cursor is correctly positioned on screen.
2715 currentBufferView()->showCursor();
2719 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2721 BufferView * bv = documentBufferView();
2726 FileName filename(to_utf8(fname));
2727 if (filename.empty()) {
2728 // Launch a file browser
2730 string initpath = lyxrc.document_path;
2731 string const trypath = bv->buffer().filePath();
2732 // If directory is writeable, use this as default.
2733 if (FileName(trypath).isDirWritable())
2737 FileDialog dlg(qt_("Select LyX document to insert"));
2738 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2739 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2741 FileDialog::Result result = dlg.open(toqstr(initpath),
2742 QStringList(qt_("LyX Documents (*.lyx)")));
2744 if (result.first == FileDialog::Later)
2748 filename.set(fromqstr(result.second));
2750 // check selected filename
2751 if (filename.empty()) {
2752 // emit message signal.
2753 message(_("Canceled."));
2758 bv->insertLyXFile(filename, ignorelang);
2759 bv->buffer().errors("Parse");
2764 string const GuiView::getTemplatesPath(Buffer & b)
2766 // We start off with the user's templates path
2767 string result = addPath(package().user_support().absFileName(), "templates");
2768 // Check for the document language
2769 string const langcode = b.params().language->code();
2770 string const shortcode = langcode.substr(0, 2);
2771 if (!langcode.empty() && shortcode != "en") {
2772 string subpath = addPath(result, shortcode);
2773 string subpath_long = addPath(result, langcode);
2774 // If we have a subdirectory for the language already,
2776 FileName sp = FileName(subpath);
2777 if (sp.isDirectory())
2779 else if (FileName(subpath_long).isDirectory())
2780 result = subpath_long;
2782 // Ask whether we should create such a subdirectory
2783 docstring const text =
2784 bformat(_("It is suggested to save the template in a subdirectory\n"
2785 "appropriate to the document language (%1$s).\n"
2786 "This subdirectory does not exists yet.\n"
2787 "Do you want to create it?"),
2788 _(b.params().language->display()));
2789 if (Alert::prompt(_("Create Language Directory?"),
2790 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2791 // If the user agreed, we try to create it and report if this failed.
2792 if (!sp.createDirectory(0777))
2793 Alert::error(_("Subdirectory creation failed!"),
2794 _("Could not create subdirectory.\n"
2795 "The template will be saved in the parent directory."));
2801 // Do we have a layout category?
2802 string const cat = b.params().baseClass() ?
2803 b.params().baseClass()->category()
2806 string subpath = addPath(result, cat);
2807 // If we have a subdirectory for the category already,
2809 FileName sp = FileName(subpath);
2810 if (sp.isDirectory())
2813 // Ask whether we should create such a subdirectory
2814 docstring const text =
2815 bformat(_("It is suggested to save the template in a subdirectory\n"
2816 "appropriate to the layout category (%1$s).\n"
2817 "This subdirectory does not exists yet.\n"
2818 "Do you want to create it?"),
2820 if (Alert::prompt(_("Create Category Directory?"),
2821 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2822 // If the user agreed, we try to create it and report if this failed.
2823 if (!sp.createDirectory(0777))
2824 Alert::error(_("Subdirectory creation failed!"),
2825 _("Could not create subdirectory.\n"
2826 "The template will be saved in the parent directory."));
2836 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2838 FileName fname = b.fileName();
2839 FileName const oldname = fname;
2840 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2842 if (!newname.empty()) {
2845 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2847 fname = support::makeAbsPath(to_utf8(newname),
2848 oldname.onlyPath().absFileName());
2850 // Switch to this Buffer.
2853 // No argument? Ask user through dialog.
2855 QString const title = as_template ? qt_("Choose a filename to save template as")
2856 : qt_("Choose a filename to save document as");
2857 FileDialog dlg(title);
2858 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2859 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2861 if (!isLyXFileName(fname.absFileName()))
2862 fname.changeExtension(".lyx");
2864 string const path = as_template ?
2866 : fname.onlyPath().absFileName();
2867 FileDialog::Result result =
2868 dlg.save(toqstr(path),
2869 QStringList(qt_("LyX Documents (*.lyx)")),
2870 toqstr(fname.onlyFileName()));
2872 if (result.first == FileDialog::Later)
2875 fname.set(fromqstr(result.second));
2880 if (!isLyXFileName(fname.absFileName()))
2881 fname.changeExtension(".lyx");
2884 // fname is now the new Buffer location.
2886 // if there is already a Buffer open with this name, we do not want
2887 // to have another one. (the second test makes sure we're not just
2888 // trying to overwrite ourselves, which is fine.)
2889 if (theBufferList().exists(fname) && fname != oldname
2890 && theBufferList().getBuffer(fname) != &b) {
2891 docstring const text =
2892 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2893 "Please close it before attempting to overwrite it.\n"
2894 "Do you want to choose a new filename?"),
2895 from_utf8(fname.absFileName()));
2896 int const ret = Alert::prompt(_("Chosen File Already Open"),
2897 text, 0, 1, _("&Rename"), _("&Cancel"));
2899 case 0: return renameBuffer(b, docstring(), kind);
2900 case 1: return false;
2905 bool const existsLocal = fname.exists();
2906 bool const existsInVC = LyXVC::fileInVC(fname);
2907 if (existsLocal || existsInVC) {
2908 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2909 if (kind != LV_WRITE_AS && existsInVC) {
2910 // renaming to a name that is already in VC
2912 docstring text = bformat(_("The document %1$s "
2913 "is already registered.\n\n"
2914 "Do you want to choose a new name?"),
2916 docstring const title = (kind == LV_VC_RENAME) ?
2917 _("Rename document?") : _("Copy document?");
2918 docstring const button = (kind == LV_VC_RENAME) ?
2919 _("&Rename") : _("&Copy");
2920 int const ret = Alert::prompt(title, text, 0, 1,
2921 button, _("&Cancel"));
2923 case 0: return renameBuffer(b, docstring(), kind);
2924 case 1: return false;
2929 docstring text = bformat(_("The document %1$s "
2930 "already exists.\n\n"
2931 "Do you want to overwrite that document?"),
2933 int const ret = Alert::prompt(_("Overwrite document?"),
2934 text, 0, 2, _("&Overwrite"),
2935 _("&Rename"), _("&Cancel"));
2938 case 1: return renameBuffer(b, docstring(), kind);
2939 case 2: return false;
2945 case LV_VC_RENAME: {
2946 string msg = b.lyxvc().rename(fname);
2949 message(from_utf8(msg));
2953 string msg = b.lyxvc().copy(fname);
2956 message(from_utf8(msg));
2960 case LV_WRITE_AS_TEMPLATE:
2963 // LyXVC created the file already in case of LV_VC_RENAME or
2964 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2965 // relative paths of included stuff right if we moved e.g. from
2966 // /a/b.lyx to /a/c/b.lyx.
2968 bool const saved = saveBuffer(b, fname);
2975 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2977 FileName fname = b.fileName();
2979 FileDialog dlg(qt_("Choose a filename to export the document as"));
2980 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2983 QString const anyformat = qt_("Guess from extension (*.*)");
2986 vector<Format const *> export_formats;
2987 for (Format const & f : theFormats())
2988 if (f.documentFormat())
2989 export_formats.push_back(&f);
2990 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2991 map<QString, string> fmap;
2994 for (Format const * f : export_formats) {
2995 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2996 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2998 from_ascii(f->extension())));
2999 types << loc_filter;
3000 fmap[loc_filter] = f->name();
3001 if (from_ascii(f->name()) == iformat) {
3002 filter = loc_filter;
3003 ext = f->extension();
3006 string ofname = fname.onlyFileName();
3008 ofname = support::changeExtension(ofname, ext);
3009 FileDialog::Result result =
3010 dlg.save(toqstr(fname.onlyPath().absFileName()),
3014 if (result.first != FileDialog::Chosen)
3018 fname.set(fromqstr(result.second));
3019 if (filter == anyformat)
3020 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3022 fmt_name = fmap[filter];
3023 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3024 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3026 if (fmt_name.empty() || fname.empty())
3029 // fname is now the new Buffer location.
3030 if (FileName(fname).exists()) {
3031 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3032 docstring text = bformat(_("The document %1$s already "
3033 "exists.\n\nDo you want to "
3034 "overwrite that document?"),
3036 int const ret = Alert::prompt(_("Overwrite document?"),
3037 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3040 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3041 case 2: return false;
3045 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3048 return dr.dispatched();
3052 bool GuiView::saveBuffer(Buffer & b)
3054 return saveBuffer(b, FileName());
3058 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3060 if (workArea(b) && workArea(b)->inDialogMode())
3063 if (fn.empty() && b.isUnnamed())
3064 return renameBuffer(b, docstring());
3066 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3068 theSession().lastFiles().add(b.fileName());
3069 theSession().writeFile();
3073 // Switch to this Buffer.
3076 // FIXME: we don't tell the user *WHY* the save failed !!
3077 docstring const file = makeDisplayPath(b.absFileName(), 30);
3078 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3079 "Do you want to rename the document and "
3080 "try again?"), file);
3081 int const ret = Alert::prompt(_("Rename and save?"),
3082 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3085 if (!renameBuffer(b, docstring()))
3094 return saveBuffer(b, fn);
3098 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3100 return closeWorkArea(wa, false);
3104 // We only want to close the buffer if it is not visible in other workareas
3105 // of the same view, nor in other views, and if this is not a child
3106 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3108 Buffer & buf = wa->bufferView().buffer();
3110 bool last_wa = d.countWorkAreasOf(buf) == 1
3111 && !inOtherView(buf) && !buf.parent();
3113 bool close_buffer = last_wa;
3116 if (lyxrc.close_buffer_with_last_view == "yes")
3118 else if (lyxrc.close_buffer_with_last_view == "no")
3119 close_buffer = false;
3122 if (buf.isUnnamed())
3123 file = from_utf8(buf.fileName().onlyFileName());
3125 file = buf.fileName().displayName(30);
3126 docstring const text = bformat(
3127 _("Last view on document %1$s is being closed.\n"
3128 "Would you like to close or hide the document?\n"
3130 "Hidden documents can be displayed back through\n"
3131 "the menu: View->Hidden->...\n"
3133 "To remove this question, set your preference in:\n"
3134 " Tools->Preferences->Look&Feel->UserInterface\n"
3136 int ret = Alert::prompt(_("Close or hide document?"),
3137 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3140 close_buffer = (ret == 0);
3144 return closeWorkArea(wa, close_buffer);
3148 bool GuiView::closeBuffer()
3150 GuiWorkArea * wa = currentMainWorkArea();
3151 // coverity complained about this
3152 // it seems unnecessary, but perhaps is worth the check
3153 LASSERT(wa, return false);
3155 setCurrentWorkArea(wa);
3156 Buffer & buf = wa->bufferView().buffer();
3157 return closeWorkArea(wa, !buf.parent());
3161 void GuiView::writeSession() const {
3162 GuiWorkArea const * active_wa = currentMainWorkArea();
3163 for (int i = 0; i < d.splitter_->count(); ++i) {
3164 TabWorkArea * twa = d.tabWorkArea(i);
3165 for (int j = 0; j < twa->count(); ++j) {
3166 GuiWorkArea * wa = twa->workArea(j);
3167 Buffer & buf = wa->bufferView().buffer();
3168 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3174 bool GuiView::closeBufferAll()
3177 for (auto & buf : theBufferList()) {
3178 if (!saveBufferIfNeeded(*buf, false)) {
3179 // Closing has been cancelled, so abort.
3184 // Close the workareas in all other views
3185 QList<int> const ids = guiApp->viewIds();
3186 for (int i = 0; i != ids.size(); ++i) {
3187 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3191 // Close our own workareas
3192 if (!closeWorkAreaAll())
3199 bool GuiView::closeWorkAreaAll()
3201 setCurrentWorkArea(currentMainWorkArea());
3203 // We might be in a situation that there is still a tabWorkArea, but
3204 // there are no tabs anymore. This can happen when we get here after a
3205 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3206 // many TabWorkArea's have no documents anymore.
3209 // We have to call count() each time, because it can happen that
3210 // more than one splitter will disappear in one iteration (bug 5998).
3211 while (d.splitter_->count() > empty_twa) {
3212 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3214 if (twa->count() == 0)
3217 setCurrentWorkArea(twa->currentWorkArea());
3218 if (!closeTabWorkArea(twa))
3226 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3231 Buffer & buf = wa->bufferView().buffer();
3233 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3234 Alert::warning(_("Close document"),
3235 _("Document could not be closed because it is being processed by LyX."));
3240 return closeBuffer(buf);
3242 if (!inMultiTabs(wa))
3243 if (!saveBufferIfNeeded(buf, true))
3251 bool GuiView::closeBuffer(Buffer & buf)
3253 bool success = true;
3254 for (Buffer * child_buf : buf.getChildren()) {
3255 if (theBufferList().isOthersChild(&buf, child_buf)) {
3256 child_buf->setParent(nullptr);
3260 // FIXME: should we look in other tabworkareas?
3261 // ANSWER: I don't think so. I've tested, and if the child is
3262 // open in some other window, it closes without a problem.
3263 GuiWorkArea * child_wa = workArea(*child_buf);
3266 // If we are in a close_event all children will be closed in some time,
3267 // so no need to do it here. This will ensure that the children end up
3268 // in the session file in the correct order. If we close the master
3269 // buffer, we can close or release the child buffers here too.
3271 success = closeWorkArea(child_wa, true);
3275 // In this case the child buffer is open but hidden.
3276 // Even in this case, children can be dirty (e.g.,
3277 // after a label change in the master, see #11405).
3278 // Therefore, check this
3279 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3280 // If we are in a close_event all children will be closed in some time,
3281 // so no need to do it here. This will ensure that the children end up
3282 // in the session file in the correct order. If we close the master
3283 // buffer, we can close or release the child buffers here too.
3286 // Save dirty buffers also if closing_!
3287 if (saveBufferIfNeeded(*child_buf, false)) {
3288 child_buf->removeAutosaveFile();
3289 theBufferList().release(child_buf);
3291 // Saving of dirty children has been cancelled.
3292 // Cancel the whole process.
3299 // goto bookmark to update bookmark pit.
3300 // FIXME: we should update only the bookmarks related to this buffer!
3301 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3302 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3303 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3304 guiApp->gotoBookmark(i, false, false);
3306 if (saveBufferIfNeeded(buf, false)) {
3307 buf.removeAutosaveFile();
3308 theBufferList().release(&buf);
3312 // open all children again to avoid a crash because of dangling
3313 // pointers (bug 6603)
3319 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3321 while (twa == d.currentTabWorkArea()) {
3322 twa->setCurrentIndex(twa->count() - 1);
3324 GuiWorkArea * wa = twa->currentWorkArea();
3325 Buffer & b = wa->bufferView().buffer();
3327 // We only want to close the buffer if the same buffer is not visible
3328 // in another view, and if this is not a child and if we are closing
3329 // a view (not a tabgroup).
3330 bool const close_buffer =
3331 !inOtherView(b) && !b.parent() && closing_;
3333 if (!closeWorkArea(wa, close_buffer))
3340 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3342 if (buf.isClean() || buf.paragraphs().empty())
3345 // Switch to this Buffer.
3351 if (buf.isUnnamed()) {
3352 file = from_utf8(buf.fileName().onlyFileName());
3355 FileName filename = buf.fileName();
3357 file = filename.displayName(30);
3358 exists = filename.exists();
3361 // Bring this window to top before asking questions.
3366 if (hiding && buf.isUnnamed()) {
3367 docstring const text = bformat(_("The document %1$s has not been "
3368 "saved yet.\n\nDo you want to save "
3369 "the document?"), file);
3370 ret = Alert::prompt(_("Save new document?"),
3371 text, 0, 1, _("&Save"), _("&Cancel"));
3375 docstring const text = exists ?
3376 bformat(_("The document %1$s has unsaved changes."
3377 "\n\nDo you want to save the document or "
3378 "discard the changes?"), file) :
3379 bformat(_("The document %1$s has not been saved yet."
3380 "\n\nDo you want to save the document or "
3381 "discard it entirely?"), file);
3382 docstring const title = exists ?
3383 _("Save changed document?") : _("Save document?");
3384 ret = Alert::prompt(title, text, 0, 2,
3385 _("&Save"), _("&Discard"), _("&Cancel"));
3390 if (!saveBuffer(buf))
3394 // If we crash after this we could have no autosave file
3395 // but I guess this is really improbable (Jug).
3396 // Sometimes improbable things happen:
3397 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3398 // buf.removeAutosaveFile();
3400 // revert all changes
3411 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3413 Buffer & buf = wa->bufferView().buffer();
3415 for (int i = 0; i != d.splitter_->count(); ++i) {
3416 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3417 if (wa_ && wa_ != wa)
3420 return inOtherView(buf);
3424 bool GuiView::inOtherView(Buffer & buf)
3426 QList<int> const ids = guiApp->viewIds();
3428 for (int i = 0; i != ids.size(); ++i) {
3432 if (guiApp->view(ids[i]).workArea(buf))
3439 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3441 if (!documentBufferView())
3444 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3445 Buffer * const curbuf = &documentBufferView()->buffer();
3446 int nwa = twa->count();
3447 for (int i = 0; i < nwa; ++i) {
3448 if (&workArea(i)->bufferView().buffer() == curbuf) {
3450 if (np == NEXTBUFFER)
3451 next_index = (i == nwa - 1 ? 0 : i + 1);
3453 next_index = (i == 0 ? nwa - 1 : i - 1);
3455 twa->moveTab(i, next_index);
3457 setBuffer(&workArea(next_index)->bufferView().buffer());
3465 /// make sure the document is saved
3466 static bool ensureBufferClean(Buffer * buffer)
3468 LASSERT(buffer, return false);
3469 if (buffer->isClean() && !buffer->isUnnamed())
3472 docstring const file = buffer->fileName().displayName(30);
3475 if (!buffer->isUnnamed()) {
3476 text = bformat(_("The document %1$s has unsaved "
3477 "changes.\n\nDo you want to save "
3478 "the document?"), file);
3479 title = _("Save changed document?");
3482 text = bformat(_("The document %1$s has not been "
3483 "saved yet.\n\nDo you want to save "
3484 "the document?"), file);
3485 title = _("Save new document?");
3487 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3490 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3492 return buffer->isClean() && !buffer->isUnnamed();
3496 bool GuiView::reloadBuffer(Buffer & buf)
3498 currentBufferView()->cursor().reset();
3499 Buffer::ReadStatus status = buf.reload();
3500 return status == Buffer::ReadSuccess;
3504 void GuiView::checkExternallyModifiedBuffers()
3506 for (Buffer * buf : theBufferList()) {
3507 if (buf->fileName().exists() && buf->isChecksumModified()) {
3508 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3509 " Reload now? Any local changes will be lost."),
3510 from_utf8(buf->absFileName()));
3511 int const ret = Alert::prompt(_("Reload externally changed document?"),
3512 text, 0, 1, _("&Reload"), _("&Cancel"));
3520 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3522 Buffer * buffer = documentBufferView()
3523 ? &(documentBufferView()->buffer()) : nullptr;
3525 switch (cmd.action()) {
3526 case LFUN_VC_REGISTER:
3527 if (!buffer || !ensureBufferClean(buffer))
3529 if (!buffer->lyxvc().inUse()) {
3530 if (buffer->lyxvc().registrer()) {
3531 reloadBuffer(*buffer);
3532 dr.clearMessageUpdate();
3537 case LFUN_VC_RENAME:
3538 case LFUN_VC_COPY: {
3539 if (!buffer || !ensureBufferClean(buffer))
3541 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3542 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3543 // Some changes are not yet committed.
3544 // We test here and not in getStatus(), since
3545 // this test is expensive.
3547 LyXVC::CommandResult ret =
3548 buffer->lyxvc().checkIn(log);
3550 if (ret == LyXVC::ErrorCommand ||
3551 ret == LyXVC::VCSuccess)
3552 reloadBuffer(*buffer);
3553 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3554 frontend::Alert::error(
3555 _("Revision control error."),
3556 _("Document could not be checked in."));
3560 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3561 LV_VC_RENAME : LV_VC_COPY;
3562 renameBuffer(*buffer, cmd.argument(), kind);
3567 case LFUN_VC_CHECK_IN:
3568 if (!buffer || !ensureBufferClean(buffer))
3570 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3572 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3574 // Only skip reloading if the checkin was cancelled or
3575 // an error occurred before the real checkin VCS command
3576 // was executed, since the VCS might have changed the
3577 // file even if it could not checkin successfully.
3578 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3579 reloadBuffer(*buffer);
3583 case LFUN_VC_CHECK_OUT:
3584 if (!buffer || !ensureBufferClean(buffer))
3586 if (buffer->lyxvc().inUse()) {
3587 dr.setMessage(buffer->lyxvc().checkOut());
3588 reloadBuffer(*buffer);
3592 case LFUN_VC_LOCKING_TOGGLE:
3593 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3595 if (buffer->lyxvc().inUse()) {
3596 string res = buffer->lyxvc().lockingToggle();
3598 frontend::Alert::error(_("Revision control error."),
3599 _("Error when setting the locking property."));
3602 reloadBuffer(*buffer);
3607 case LFUN_VC_REVERT:
3610 if (buffer->lyxvc().revert()) {
3611 reloadBuffer(*buffer);
3612 dr.clearMessageUpdate();
3616 case LFUN_VC_UNDO_LAST:
3619 buffer->lyxvc().undoLast();
3620 reloadBuffer(*buffer);
3621 dr.clearMessageUpdate();
3624 case LFUN_VC_REPO_UPDATE:
3627 if (ensureBufferClean(buffer)) {
3628 dr.setMessage(buffer->lyxvc().repoUpdate());
3629 checkExternallyModifiedBuffers();
3633 case LFUN_VC_COMMAND: {
3634 string flag = cmd.getArg(0);
3635 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3638 if (contains(flag, 'M')) {
3639 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3642 string path = cmd.getArg(1);
3643 if (contains(path, "$$p") && buffer)
3644 path = subst(path, "$$p", buffer->filePath());
3645 LYXERR(Debug::LYXVC, "Directory: " << path);
3647 if (!pp.isReadableDirectory()) {
3648 lyxerr << _("Directory is not accessible.") << endl;
3651 support::PathChanger p(pp);
3653 string command = cmd.getArg(2);
3654 if (command.empty())
3657 command = subst(command, "$$i", buffer->absFileName());
3658 command = subst(command, "$$p", buffer->filePath());
3660 command = subst(command, "$$m", to_utf8(message));
3661 LYXERR(Debug::LYXVC, "Command: " << command);
3663 one.startscript(Systemcall::Wait, command);
3667 if (contains(flag, 'I'))
3668 buffer->markDirty();
3669 if (contains(flag, 'R'))
3670 reloadBuffer(*buffer);
3675 case LFUN_VC_COMPARE: {
3676 if (cmd.argument().empty()) {
3677 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3683 string rev1 = cmd.getArg(0);
3687 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3690 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3691 f2 = buffer->absFileName();
3693 string rev2 = cmd.getArg(1);
3697 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3701 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3702 f1 << "\n" << f2 << "\n" );
3703 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3704 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3714 void GuiView::openChildDocument(string const & fname)
3716 LASSERT(documentBufferView(), return);
3717 Buffer & buffer = documentBufferView()->buffer();
3718 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3719 documentBufferView()->saveBookmark(false);
3720 Buffer * child = nullptr;
3721 if (theBufferList().exists(filename)) {
3722 child = theBufferList().getBuffer(filename);
3725 message(bformat(_("Opening child document %1$s..."),
3726 makeDisplayPath(filename.absFileName())));
3727 child = loadDocument(filename, false);
3729 // Set the parent name of the child document.
3730 // This makes insertion of citations and references in the child work,
3731 // when the target is in the parent or another child document.
3733 child->setParent(&buffer);
3737 bool GuiView::goToFileRow(string const & argument)
3741 size_t i = argument.find_last_of(' ');
3742 if (i != string::npos) {
3743 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3744 istringstream is(argument.substr(i + 1));
3749 if (i == string::npos) {
3750 LYXERR0("Wrong argument: " << argument);
3753 Buffer * buf = nullptr;
3754 string const realtmp = package().temp_dir().realPath();
3755 // We have to use os::path_prefix_is() here, instead of
3756 // simply prefixIs(), because the file name comes from
3757 // an external application and may need case adjustment.
3758 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3759 buf = theBufferList().getBufferFromTmp(file_name, true);
3760 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3761 << (buf ? " success" : " failed"));
3763 // Must replace extension of the file to be .lyx
3764 // and get full path
3765 FileName const s = fileSearch(string(),
3766 support::changeExtension(file_name, ".lyx"), "lyx");
3767 // Either change buffer or load the file
3768 if (theBufferList().exists(s))
3769 buf = theBufferList().getBuffer(s);
3770 else if (s.exists()) {
3771 buf = loadDocument(s);
3776 _("File does not exist: %1$s"),
3777 makeDisplayPath(file_name)));
3783 _("No buffer for file: %1$s."),
3784 makeDisplayPath(file_name))
3789 bool success = documentBufferView()->setCursorFromRow(row);
3791 LYXERR(Debug::LATEX,
3792 "setCursorFromRow: invalid position for row " << row);
3793 frontend::Alert::error(_("Inverse Search Failed"),
3794 _("Invalid position requested by inverse search.\n"
3795 "You may need to update the viewed document."));
3801 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3803 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3804 menu->exec(QCursor::pos());
3809 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3810 Buffer const * orig, Buffer * clone, string const & format)
3812 Buffer::ExportStatus const status = func(format);
3814 // the cloning operation will have produced a clone of the entire set of
3815 // documents, starting from the master. so we must delete those.
3816 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3818 busyBuffers.remove(orig);
3823 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3824 Buffer const * orig, Buffer * clone, string const & format)
3826 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3828 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3832 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3833 Buffer const * orig, Buffer * clone, string const & format)
3835 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3837 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3841 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3842 Buffer const * orig, Buffer * clone, string const & format)
3844 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3846 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3850 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3851 Buffer const * used_buffer,
3852 docstring const & msg,
3853 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3854 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3855 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3856 bool allow_async, bool use_tmpdir)
3861 string format = argument;
3863 format = used_buffer->params().getDefaultOutputFormat();
3864 processing_format = format;
3866 progress_->clearMessages();
3869 #if EXPORT_in_THREAD
3871 GuiViewPrivate::busyBuffers.insert(used_buffer);
3872 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3873 if (!cloned_buffer) {
3874 Alert::error(_("Export Error"),
3875 _("Error cloning the Buffer."));
3878 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3883 setPreviewFuture(f);
3884 last_export_format = used_buffer->params().bufferFormat();
3887 // We are asynchronous, so we don't know here anything about the success
3890 Buffer::ExportStatus status;
3892 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3893 } else if (previewFunc) {
3894 status = (used_buffer->*previewFunc)(format);
3897 handleExportStatus(gv_, status, format);
3899 return (status == Buffer::ExportSuccess
3900 || status == Buffer::PreviewSuccess);
3904 Buffer::ExportStatus status;
3906 status = (used_buffer->*syncFunc)(format, true);
3907 } else if (previewFunc) {
3908 status = (used_buffer->*previewFunc)(format);
3911 handleExportStatus(gv_, status, format);
3913 return (status == Buffer::ExportSuccess
3914 || status == Buffer::PreviewSuccess);
3918 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3920 BufferView * bv = currentBufferView();
3921 LASSERT(bv, return);
3923 // Let the current BufferView dispatch its own actions.
3924 bv->dispatch(cmd, dr);
3925 if (dr.dispatched()) {
3926 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3927 updateDialog("document", "");
3931 // Try with the document BufferView dispatch if any.
3932 BufferView * doc_bv = documentBufferView();
3933 if (doc_bv && doc_bv != bv) {
3934 doc_bv->dispatch(cmd, dr);
3935 if (dr.dispatched()) {
3936 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3937 updateDialog("document", "");
3942 // Then let the current Cursor dispatch its own actions.
3943 bv->cursor().dispatch(cmd);
3945 // update completion. We do it here and not in
3946 // processKeySym to avoid another redraw just for a
3947 // changed inline completion
3948 if (cmd.origin() == FuncRequest::KEYBOARD) {
3949 if (cmd.action() == LFUN_SELF_INSERT
3950 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3951 updateCompletion(bv->cursor(), true, true);
3952 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3953 updateCompletion(bv->cursor(), false, true);
3955 updateCompletion(bv->cursor(), false, false);
3958 dr = bv->cursor().result();
3962 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3964 BufferView * bv = currentBufferView();
3965 // By default we won't need any update.
3966 dr.screenUpdate(Update::None);
3967 // assume cmd will be dispatched
3968 dr.dispatched(true);
3970 Buffer * doc_buffer = documentBufferView()
3971 ? &(documentBufferView()->buffer()) : nullptr;
3973 if (cmd.origin() == FuncRequest::TOC) {
3974 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3975 toc->doDispatch(bv->cursor(), cmd, dr);
3979 string const argument = to_utf8(cmd.argument());
3981 switch(cmd.action()) {
3982 case LFUN_BUFFER_CHILD_OPEN:
3983 openChildDocument(to_utf8(cmd.argument()));
3986 case LFUN_BUFFER_IMPORT:
3987 importDocument(to_utf8(cmd.argument()));
3990 case LFUN_MASTER_BUFFER_EXPORT:
3992 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3994 case LFUN_BUFFER_EXPORT: {
3997 // GCC only sees strfwd.h when building merged
3998 if (::lyx::operator==(cmd.argument(), "custom")) {
3999 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4000 // so the following test should not be needed.
4001 // In principle, we could try to switch to such a view...
4002 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4003 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4007 string const dest = cmd.getArg(1);
4008 FileName target_dir;
4009 if (!dest.empty() && FileName::isAbsolute(dest))
4010 target_dir = FileName(support::onlyPath(dest));
4012 target_dir = doc_buffer->fileName().onlyPath();
4014 string const format = (argument.empty() || argument == "default") ?
4015 doc_buffer->params().getDefaultOutputFormat() : argument;
4017 if ((dest.empty() && doc_buffer->isUnnamed())
4018 || !target_dir.isDirWritable()) {
4019 exportBufferAs(*doc_buffer, from_utf8(format));
4022 /* TODO/Review: Is it a problem to also export the children?
4023 See the update_unincluded flag */
4024 d.asyncBufferProcessing(format,
4027 &GuiViewPrivate::exportAndDestroy,
4029 nullptr, cmd.allowAsync());
4030 // TODO Inform user about success
4034 case LFUN_BUFFER_EXPORT_AS: {
4035 LASSERT(doc_buffer, break);
4036 docstring f = cmd.argument();
4038 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4039 exportBufferAs(*doc_buffer, f);
4043 case LFUN_BUFFER_UPDATE: {
4044 d.asyncBufferProcessing(argument,
4047 &GuiViewPrivate::compileAndDestroy,
4049 nullptr, cmd.allowAsync(), true);
4052 case LFUN_BUFFER_VIEW: {
4053 d.asyncBufferProcessing(argument,
4055 _("Previewing ..."),
4056 &GuiViewPrivate::previewAndDestroy,
4058 &Buffer::preview, cmd.allowAsync());
4061 case LFUN_MASTER_BUFFER_UPDATE: {
4062 d.asyncBufferProcessing(argument,
4063 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4065 &GuiViewPrivate::compileAndDestroy,
4067 nullptr, cmd.allowAsync(), true);
4070 case LFUN_MASTER_BUFFER_VIEW: {
4071 d.asyncBufferProcessing(argument,
4072 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4074 &GuiViewPrivate::previewAndDestroy,
4075 nullptr, &Buffer::preview, cmd.allowAsync());
4078 case LFUN_EXPORT_CANCEL: {
4079 Systemcall::killscript();
4082 case LFUN_BUFFER_SWITCH: {
4083 string const file_name = to_utf8(cmd.argument());
4084 if (!FileName::isAbsolute(file_name)) {
4086 dr.setMessage(_("Absolute filename expected."));
4090 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4093 dr.setMessage(_("Document not loaded"));
4097 // Do we open or switch to the buffer in this view ?
4098 if (workArea(*buffer)
4099 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4104 // Look for the buffer in other views
4105 QList<int> const ids = guiApp->viewIds();
4107 for (; i != ids.size(); ++i) {
4108 GuiView & gv = guiApp->view(ids[i]);
4109 if (gv.workArea(*buffer)) {
4111 gv.activateWindow();
4113 gv.setBuffer(buffer);
4118 // If necessary, open a new window as a last resort
4119 if (i == ids.size()) {
4120 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4126 case LFUN_BUFFER_NEXT:
4127 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4130 case LFUN_BUFFER_MOVE_NEXT:
4131 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4134 case LFUN_BUFFER_PREVIOUS:
4135 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4138 case LFUN_BUFFER_MOVE_PREVIOUS:
4139 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4142 case LFUN_BUFFER_CHKTEX:
4143 LASSERT(doc_buffer, break);
4144 doc_buffer->runChktex();
4147 case LFUN_COMMAND_EXECUTE: {
4148 command_execute_ = true;
4149 minibuffer_focus_ = true;
4152 case LFUN_DROP_LAYOUTS_CHOICE:
4153 d.layout_->showPopup();
4156 case LFUN_MENU_OPEN:
4157 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4158 menu->exec(QCursor::pos());
4161 case LFUN_FILE_INSERT: {
4162 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4163 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4164 dr.forceBufferUpdate();
4165 dr.screenUpdate(Update::Force);
4170 case LFUN_FILE_INSERT_PLAINTEXT:
4171 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4172 string const fname = to_utf8(cmd.argument());
4173 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4174 dr.setMessage(_("Absolute filename expected."));
4178 FileName filename(fname);
4179 if (fname.empty()) {
4180 FileDialog dlg(qt_("Select file to insert"));
4182 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4183 QStringList(qt_("All Files (*)")));
4185 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4186 dr.setMessage(_("Canceled."));
4190 filename.set(fromqstr(result.second));
4194 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4195 bv->dispatch(new_cmd, dr);
4200 case LFUN_BUFFER_RELOAD: {
4201 LASSERT(doc_buffer, break);
4204 bool drop = (cmd.argument() == "dump");
4207 if (!drop && !doc_buffer->isClean()) {
4208 docstring const file =
4209 makeDisplayPath(doc_buffer->absFileName(), 20);
4210 if (doc_buffer->notifiesExternalModification()) {
4211 docstring text = _("The current version will be lost. "
4212 "Are you sure you want to load the version on disk "
4213 "of the document %1$s?");
4214 ret = Alert::prompt(_("Reload saved document?"),
4215 bformat(text, file), 1, 1,
4216 _("&Reload"), _("&Cancel"));
4218 docstring text = _("Any changes will be lost. "
4219 "Are you sure you want to revert to the saved version "
4220 "of the document %1$s?");
4221 ret = Alert::prompt(_("Revert to saved document?"),
4222 bformat(text, file), 1, 1,
4223 _("&Revert"), _("&Cancel"));
4228 doc_buffer->markClean();
4229 reloadBuffer(*doc_buffer);
4230 dr.forceBufferUpdate();
4235 case LFUN_BUFFER_RESET_EXPORT:
4236 LASSERT(doc_buffer, break);
4237 doc_buffer->requireFreshStart(true);
4238 dr.setMessage(_("Buffer export reset."));
4241 case LFUN_BUFFER_WRITE:
4242 LASSERT(doc_buffer, break);
4243 saveBuffer(*doc_buffer);
4246 case LFUN_BUFFER_WRITE_AS:
4247 LASSERT(doc_buffer, break);
4248 renameBuffer(*doc_buffer, cmd.argument());
4251 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4252 LASSERT(doc_buffer, break);
4253 renameBuffer(*doc_buffer, cmd.argument(),
4254 LV_WRITE_AS_TEMPLATE);
4257 case LFUN_BUFFER_WRITE_ALL: {
4258 Buffer * first = theBufferList().first();
4261 message(_("Saving all documents..."));
4262 // We cannot use a for loop as the buffer list cycles.
4265 if (!b->isClean()) {
4267 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4269 b = theBufferList().next(b);
4270 } while (b != first);
4271 dr.setMessage(_("All documents saved."));
4275 case LFUN_MASTER_BUFFER_FORALL: {
4279 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4280 funcToRun.allowAsync(false);
4282 for (Buffer const * buf : doc_buffer->allRelatives()) {
4283 // Switch to other buffer view and resend cmd
4284 lyx::dispatch(FuncRequest(
4285 LFUN_BUFFER_SWITCH, buf->absFileName()));
4286 lyx::dispatch(funcToRun);
4289 lyx::dispatch(FuncRequest(
4290 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4294 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4295 LASSERT(doc_buffer, break);
4296 doc_buffer->clearExternalModification();
4299 case LFUN_BUFFER_CLOSE:
4303 case LFUN_BUFFER_CLOSE_ALL:
4307 case LFUN_DEVEL_MODE_TOGGLE:
4308 devel_mode_ = !devel_mode_;
4310 dr.setMessage(_("Developer mode is now enabled."));
4312 dr.setMessage(_("Developer mode is now disabled."));
4315 case LFUN_TOOLBAR_SET: {
4316 string const name = cmd.getArg(0);
4317 string const state = cmd.getArg(1);
4318 if (GuiToolbar * t = toolbar(name))
4323 case LFUN_TOOLBAR_TOGGLE: {
4324 string const name = cmd.getArg(0);
4325 if (GuiToolbar * t = toolbar(name))
4330 case LFUN_TOOLBAR_MOVABLE: {
4331 string const name = cmd.getArg(0);
4333 // toggle (all) toolbars movablility
4334 toolbarsMovable_ = !toolbarsMovable_;
4335 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4336 GuiToolbar * tb = toolbar(ti.name);
4337 if (tb && tb->isMovable() != toolbarsMovable_)
4338 // toggle toolbar movablity if it does not fit lock
4339 // (all) toolbars positions state silent = true, since
4340 // status bar notifications are slow
4343 if (toolbarsMovable_)
4344 dr.setMessage(_("Toolbars unlocked."));
4346 dr.setMessage(_("Toolbars locked."));
4347 } else if (GuiToolbar * t = toolbar(name)) {
4348 // toggle current toolbar movablity
4350 // update lock (all) toolbars positions
4351 updateLockToolbars();
4356 case LFUN_ICON_SIZE: {
4357 QSize size = d.iconSize(cmd.argument());
4359 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4360 size.width(), size.height()));
4364 case LFUN_DIALOG_UPDATE: {
4365 string const name = to_utf8(cmd.argument());
4366 if (name == "prefs" || name == "document")
4367 updateDialog(name, string());
4368 else if (name == "paragraph")
4369 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4370 else if (currentBufferView()) {
4371 Inset * inset = currentBufferView()->editedInset(name);
4372 // Can only update a dialog connected to an existing inset
4374 // FIXME: get rid of this indirection; GuiView ask the inset
4375 // if he is kind enough to update itself...
4376 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4377 //FIXME: pass DispatchResult here?
4378 inset->dispatch(currentBufferView()->cursor(), fr);
4384 case LFUN_DIALOG_TOGGLE: {
4385 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4386 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4387 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4391 case LFUN_DIALOG_DISCONNECT_INSET:
4392 disconnectDialog(to_utf8(cmd.argument()));
4395 case LFUN_DIALOG_HIDE: {
4396 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4400 case LFUN_DIALOG_SHOW: {
4401 string const name = cmd.getArg(0);
4402 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4404 if (name == "latexlog") {
4405 // getStatus checks that
4406 LASSERT(doc_buffer, break);
4407 Buffer::LogType type;
4408 string const logfile = doc_buffer->logName(&type);
4410 case Buffer::latexlog:
4413 case Buffer::buildlog:
4414 sdata = "literate ";
4417 sdata += Lexer::quoteString(logfile);
4418 showDialog("log", sdata);
4419 } else if (name == "vclog") {
4420 // getStatus checks that
4421 LASSERT(doc_buffer, break);
4422 string const sdata2 = "vc " +
4423 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4424 showDialog("log", sdata2);
4425 } else if (name == "symbols") {
4426 sdata = bv->cursor().getEncoding()->name();
4428 showDialog("symbols", sdata);
4429 } else if (name == "findreplace") {
4430 sdata = to_utf8(bv->cursor().selectionAsString(false));
4431 showDialog(name, sdata);
4433 } else if (name == "prefs" && isFullScreen()) {
4434 lfunUiToggle("fullscreen");
4435 showDialog("prefs", sdata);
4437 showDialog(name, sdata);
4442 dr.setMessage(cmd.argument());
4445 case LFUN_UI_TOGGLE: {
4446 string arg = cmd.getArg(0);
4447 if (!lfunUiToggle(arg)) {
4448 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4449 dr.setMessage(bformat(msg, from_utf8(arg)));
4451 // Make sure the keyboard focus stays in the work area.
4456 case LFUN_VIEW_SPLIT: {
4457 LASSERT(doc_buffer, break);
4458 string const orientation = cmd.getArg(0);
4459 d.splitter_->setOrientation(orientation == "vertical"
4460 ? Qt::Vertical : Qt::Horizontal);
4461 TabWorkArea * twa = addTabWorkArea();
4462 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4463 setCurrentWorkArea(wa);
4466 case LFUN_TAB_GROUP_CLOSE:
4467 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4468 closeTabWorkArea(twa);
4469 d.current_work_area_ = nullptr;
4470 twa = d.currentTabWorkArea();
4471 // Switch to the next GuiWorkArea in the found TabWorkArea.
4473 // Make sure the work area is up to date.
4474 setCurrentWorkArea(twa->currentWorkArea());
4476 setCurrentWorkArea(nullptr);
4481 case LFUN_VIEW_CLOSE:
4482 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4483 closeWorkArea(twa->currentWorkArea());
4484 d.current_work_area_ = nullptr;
4485 twa = d.currentTabWorkArea();
4486 // Switch to the next GuiWorkArea in the found TabWorkArea.
4488 // Make sure the work area is up to date.
4489 setCurrentWorkArea(twa->currentWorkArea());
4491 setCurrentWorkArea(nullptr);
4496 case LFUN_COMPLETION_INLINE:
4497 if (d.current_work_area_)
4498 d.current_work_area_->completer().showInline();
4501 case LFUN_COMPLETION_POPUP:
4502 if (d.current_work_area_)
4503 d.current_work_area_->completer().showPopup();
4508 if (d.current_work_area_)
4509 d.current_work_area_->completer().tab();
4512 case LFUN_COMPLETION_CANCEL:
4513 if (d.current_work_area_) {
4514 if (d.current_work_area_->completer().popupVisible())
4515 d.current_work_area_->completer().hidePopup();
4517 d.current_work_area_->completer().hideInline();
4521 case LFUN_COMPLETION_ACCEPT:
4522 if (d.current_work_area_)
4523 d.current_work_area_->completer().activate();
4526 case LFUN_BUFFER_ZOOM_IN:
4527 case LFUN_BUFFER_ZOOM_OUT:
4528 case LFUN_BUFFER_ZOOM: {
4529 if (cmd.argument().empty()) {
4530 if (cmd.action() == LFUN_BUFFER_ZOOM)
4532 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4537 if (cmd.action() == LFUN_BUFFER_ZOOM)
4538 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4539 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4540 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4542 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4545 // Actual zoom value: default zoom + fractional extra value
4546 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4547 if (zoom < static_cast<int>(zoom_min_))
4550 lyxrc.currentZoom = zoom;
4552 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4553 lyxrc.currentZoom, lyxrc.defaultZoom));
4555 guiApp->fontLoader().update();
4556 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4560 case LFUN_VC_REGISTER:
4561 case LFUN_VC_RENAME:
4563 case LFUN_VC_CHECK_IN:
4564 case LFUN_VC_CHECK_OUT:
4565 case LFUN_VC_REPO_UPDATE:
4566 case LFUN_VC_LOCKING_TOGGLE:
4567 case LFUN_VC_REVERT:
4568 case LFUN_VC_UNDO_LAST:
4569 case LFUN_VC_COMMAND:
4570 case LFUN_VC_COMPARE:
4571 dispatchVC(cmd, dr);
4574 case LFUN_SERVER_GOTO_FILE_ROW:
4575 if(goToFileRow(to_utf8(cmd.argument())))
4576 dr.screenUpdate(Update::Force | Update::FitCursor);
4579 case LFUN_LYX_ACTIVATE:
4583 case LFUN_WINDOW_RAISE:
4589 case LFUN_FORWARD_SEARCH: {
4590 // it seems safe to assume we have a document buffer, since
4591 // getStatus wants one.
4592 LASSERT(doc_buffer, break);
4593 Buffer const * doc_master = doc_buffer->masterBuffer();
4594 FileName const path(doc_master->temppath());
4595 string const texname = doc_master->isChild(doc_buffer)
4596 ? DocFileName(changeExtension(
4597 doc_buffer->absFileName(),
4598 "tex")).mangledFileName()
4599 : doc_buffer->latexName();
4600 string const fulltexname =
4601 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4602 string const mastername =
4603 removeExtension(doc_master->latexName());
4604 FileName const dviname(addName(path.absFileName(),
4605 addExtension(mastername, "dvi")));
4606 FileName const pdfname(addName(path.absFileName(),
4607 addExtension(mastername, "pdf")));
4608 bool const have_dvi = dviname.exists();
4609 bool const have_pdf = pdfname.exists();
4610 if (!have_dvi && !have_pdf) {
4611 dr.setMessage(_("Please, preview the document first."));
4614 string outname = dviname.onlyFileName();
4615 string command = lyxrc.forward_search_dvi;
4616 if (!have_dvi || (have_pdf &&
4617 pdfname.lastModified() > dviname.lastModified())) {
4618 outname = pdfname.onlyFileName();
4619 command = lyxrc.forward_search_pdf;
4622 DocIterator cur = bv->cursor();
4623 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4624 LYXERR(Debug::ACTION, "Forward search: row:" << row
4626 if (row == -1 || command.empty()) {
4627 dr.setMessage(_("Couldn't proceed."));
4630 string texrow = convert<string>(row);
4632 command = subst(command, "$$n", texrow);
4633 command = subst(command, "$$f", fulltexname);
4634 command = subst(command, "$$t", texname);
4635 command = subst(command, "$$o", outname);
4637 volatile PathChanger p(path);
4639 one.startscript(Systemcall::DontWait, command);
4643 case LFUN_SPELLING_CONTINUOUSLY:
4644 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4645 dr.screenUpdate(Update::Force);
4648 case LFUN_CITATION_OPEN: {
4650 if (theFormats().getFormat("pdf"))
4651 pdfv = theFormats().getFormat("pdf")->viewer();
4652 if (theFormats().getFormat("ps"))
4653 psv = theFormats().getFormat("ps")->viewer();
4654 frontend::showTarget(argument, pdfv, psv);
4659 // The LFUN must be for one of BufferView, Buffer or Cursor;
4661 dispatchToBufferView(cmd, dr);
4665 // Need to update bv because many LFUNs here might have destroyed it
4666 bv = currentBufferView();
4668 // Clear non-empty selections
4669 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4671 Cursor & cur = bv->cursor();
4672 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4673 cur.clearSelection();
4679 bool GuiView::lfunUiToggle(string const & ui_component)
4681 if (ui_component == "scrollbar") {
4682 // hide() is of no help
4683 if (d.current_work_area_->verticalScrollBarPolicy() ==
4684 Qt::ScrollBarAlwaysOff)
4686 d.current_work_area_->setVerticalScrollBarPolicy(
4687 Qt::ScrollBarAsNeeded);
4689 d.current_work_area_->setVerticalScrollBarPolicy(
4690 Qt::ScrollBarAlwaysOff);
4691 } else if (ui_component == "statusbar") {
4692 statusBar()->setVisible(!statusBar()->isVisible());
4693 } else if (ui_component == "menubar") {
4694 menuBar()->setVisible(!menuBar()->isVisible());
4696 if (ui_component == "frame") {
4697 int const l = contentsMargins().left();
4699 //are the frames in default state?
4700 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4702 #if QT_VERSION > 0x050903
4703 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4705 setContentsMargins(-2, -2, -2, -2);
4707 #if QT_VERSION > 0x050903
4708 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4710 setContentsMargins(0, 0, 0, 0);
4713 if (ui_component == "fullscreen") {
4721 void GuiView::toggleFullScreen()
4723 setWindowState(windowState() ^ Qt::WindowFullScreen);
4727 Buffer const * GuiView::updateInset(Inset const * inset)
4732 Buffer const * inset_buffer = &(inset->buffer());
4734 for (int i = 0; i != d.splitter_->count(); ++i) {
4735 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4738 Buffer const * buffer = &(wa->bufferView().buffer());
4739 if (inset_buffer == buffer)
4740 wa->scheduleRedraw(true);
4742 return inset_buffer;
4746 void GuiView::restartCaret()
4748 /* When we move around, or type, it's nice to be able to see
4749 * the caret immediately after the keypress.
4751 if (d.current_work_area_)
4752 d.current_work_area_->startBlinkingCaret();
4754 // Take this occasion to update the other GUI elements.
4760 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4762 if (d.current_work_area_)
4763 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4768 // This list should be kept in sync with the list of insets in
4769 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4770 // dialog should have the same name as the inset.
4771 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4772 // docs in LyXAction.cpp.
4774 char const * const dialognames[] = {
4776 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4777 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4778 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4779 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4780 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4781 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4782 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4783 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4785 char const * const * const end_dialognames =
4786 dialognames + (sizeof(dialognames) / sizeof(char *));
4790 cmpCStr(char const * name) : name_(name) {}
4791 bool operator()(char const * other) {
4792 return strcmp(other, name_) == 0;
4799 bool isValidName(string const & name)
4801 return find_if(dialognames, end_dialognames,
4802 cmpCStr(name.c_str())) != end_dialognames;
4808 void GuiView::resetDialogs()
4810 // Make sure that no LFUN uses any GuiView.
4811 guiApp->setCurrentView(nullptr);
4815 constructToolbars();
4816 guiApp->menus().fillMenuBar(menuBar(), this, false);
4817 d.layout_->updateContents(true);
4818 // Now update controls with current buffer.
4819 guiApp->setCurrentView(this);
4825 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4827 for (QObject * child: widget->children()) {
4828 if (child->inherits("QGroupBox")) {
4829 QGroupBox * box = (QGroupBox*) child;
4832 flatGroupBoxes(child, flag);
4838 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4840 if (!isValidName(name))
4843 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4845 if (it != d.dialogs_.end()) {
4847 it->second->hideView();
4848 return it->second.get();
4851 Dialog * dialog = build(name);
4852 d.dialogs_[name].reset(dialog);
4853 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4854 // Force a uniform style for group boxes
4855 // On Mac non-flat works better, on Linux flat is standard
4856 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4858 if (lyxrc.allow_geometry_session)
4859 dialog->restoreSession();
4866 void GuiView::showDialog(string const & name, string const & sdata,
4869 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4873 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4879 const string name = fromqstr(qname);
4880 const string sdata = fromqstr(qdata);
4884 Dialog * dialog = findOrBuild(name, false);
4886 bool const visible = dialog->isVisibleView();
4887 dialog->showData(sdata);
4888 if (currentBufferView())
4889 currentBufferView()->editInset(name, inset);
4890 // We only set the focus to the new dialog if it was not yet
4891 // visible in order not to change the existing previous behaviour
4893 // activateWindow is needed for floating dockviews
4894 dialog->asQWidget()->raise();
4895 dialog->asQWidget()->activateWindow();
4896 dialog->asQWidget()->setFocus();
4900 catch (ExceptionMessage const &) {
4908 bool GuiView::isDialogVisible(string const & name) const
4910 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4911 if (it == d.dialogs_.end())
4913 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4917 void GuiView::hideDialog(string const & name, Inset * inset)
4919 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4920 if (it == d.dialogs_.end())
4924 if (!currentBufferView())
4926 if (inset != currentBufferView()->editedInset(name))
4930 Dialog * const dialog = it->second.get();
4931 if (dialog->isVisibleView())
4933 if (currentBufferView())
4934 currentBufferView()->editInset(name, nullptr);
4938 void GuiView::disconnectDialog(string const & name)
4940 if (!isValidName(name))
4942 if (currentBufferView())
4943 currentBufferView()->editInset(name, nullptr);
4947 void GuiView::hideAll() const
4949 for(auto const & dlg_p : d.dialogs_)
4950 dlg_p.second->hideView();
4954 void GuiView::updateDialogs()
4956 for(auto const & dlg_p : d.dialogs_) {
4957 Dialog * dialog = dlg_p.second.get();
4959 if (dialog->needBufferOpen() && !documentBufferView())
4960 hideDialog(fromqstr(dialog->name()), nullptr);
4961 else if (dialog->isVisibleView())
4962 dialog->checkStatus();
4970 Dialog * GuiView::build(string const & name)
4972 return createDialog(*this, name);
4976 SEMenu::SEMenu(QWidget * parent)
4978 QAction * action = addAction(qt_("Disable Shell Escape"));
4979 connect(action, SIGNAL(triggered()),
4980 parent, SLOT(disableShellEscape()));
4983 } // namespace frontend
4986 #include "moc_GuiView.cpp"