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>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
116 // sync with GuiAlert.cpp
117 #define EXPORT_in_THREAD 1
120 #include "support/bind.h"
124 #ifdef HAVE_SYS_TIME_H
125 # include <sys/time.h>
133 using namespace lyx::support;
137 using support::addExtension;
138 using support::changeExtension;
139 using support::removeExtension;
145 class BackgroundWidget : public QWidget
148 BackgroundWidget(int width, int height)
149 : width_(width), height_(height)
151 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
152 if (!lyxrc.show_banner)
154 /// The text to be written on top of the pixmap
155 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
156 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
157 /// The text to be written on top of the pixmap
158 QString const text = lyx_version ?
159 qt_("version ") + lyx_version : qt_("unknown version");
160 #if QT_VERSION >= 0x050000
161 QString imagedir = "images/";
162 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
163 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
164 if (svgRenderer.isValid()) {
165 splash_ = QPixmap(splashSize());
166 QPainter painter(&splash_);
167 svgRenderer.render(&painter);
168 splash_.setDevicePixelRatio(pixelRatio());
170 splash_ = getPixmap("images/", "banner", "png");
173 splash_ = getPixmap("images/", "banner", "svgz,png");
176 QPainter pain(&splash_);
177 pain.setPen(QColor(0, 0, 0));
178 qreal const fsize = fontSize();
181 qreal locscale = htextsize.toFloat(&ok);
184 QPointF const position = textPosition(false);
185 QPointF const hposition = textPosition(true);
186 QRectF const hrect(hposition, splashSize());
188 "widget pixel ratio: " << pixelRatio() <<
189 " splash pixel ratio: " << splashPixelRatio() <<
190 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
192 // The font used to display the version info
193 font.setStyleHint(QFont::SansSerif);
194 font.setWeight(QFont::Bold);
195 font.setPointSizeF(fsize);
197 pain.drawText(position, text);
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Normal);
201 font.setPointSizeF(hfsize);
202 // Check how long the logo gets with the current font
203 // and adapt if the font is running wider than what
205 GuiFontMetrics fm(font);
206 // Split the title into lines to measure the longest line
207 // in the current l7n.
208 QStringList titlesegs = htext.split('\n');
210 int hline = fm.maxHeight();
211 QStringList::const_iterator sit;
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
616 statusBar()->addPermanentWidget(busylabel);
617 search_mode mode = theGuiApp()->imageSearchMode();
618 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
619 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
620 busylabel->setMovie(busyanim);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busylabel, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busylabel, SLOT(hide()));
628 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 QSlider * zoomslider = new QSlider(Qt::Horizontal, statusBar());
633 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
634 zoomslider->setFixedWidth(fm.horizontalAdvance('x') * 15);
636 zoomslider->setFixedWidth(fm.width('x') * 15);
638 // Make the defaultZoom center
639 zoomslider->setRange(10, (lyxrc.defaultZoom * 2) - 10);
640 // Initialize proper zoom value
642 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
643 // Actual zoom value: default zoom + fractional offset
644 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
645 if (zoom < static_cast<int>(zoom_min_))
647 zoomslider->setValue(zoom);
648 zoomslider->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
649 zoomslider->setTickPosition(QSlider::TicksBelow);
650 zoomslider->setTickInterval(lyxrc.defaultZoom - 10);
651 statusBar()->addPermanentWidget(zoomslider);
653 connect(zoomslider, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
654 connect(zoomslider, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
655 connect(this, SIGNAL(currentZoomChanged(int)), zoomslider, SLOT(setValue(int)));
657 zoom_value_ = new QLabel(statusBar());
658 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
659 statusBar()->addPermanentWidget(zoom_value_);
661 int const iconheight = max(int(d.normalIconSize), fm.height());
662 QSize const iconsize(iconheight, iconheight);
664 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
665 shell_escape_ = new QLabel(statusBar());
666 shell_escape_->setPixmap(shellescape);
667 shell_escape_->setScaledContents(true);
668 shell_escape_->setAlignment(Qt::AlignCenter);
669 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
670 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
671 "external commands for this document. "
672 "Right click to change."));
673 SEMenu * menu = new SEMenu(this);
674 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
675 menu, SLOT(showMenu(QPoint)));
676 shell_escape_->hide();
677 statusBar()->addPermanentWidget(shell_escape_);
679 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
680 read_only_ = new QLabel(statusBar());
681 read_only_->setPixmap(readonly);
682 read_only_->setScaledContents(true);
683 read_only_->setAlignment(Qt::AlignCenter);
685 statusBar()->addPermanentWidget(read_only_);
687 version_control_ = new QLabel(statusBar());
688 version_control_->setAlignment(Qt::AlignCenter);
689 version_control_->setFrameStyle(QFrame::StyledPanel);
690 version_control_->hide();
691 statusBar()->addPermanentWidget(version_control_);
693 statusBar()->setSizeGripEnabled(true);
696 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
697 SLOT(autoSaveThreadFinished()));
699 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
700 SLOT(processingThreadStarted()));
701 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
702 SLOT(processingThreadFinished()));
704 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
705 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
707 // set custom application bars context menu, e.g. tool bar and menu bar
708 setContextMenuPolicy(Qt::CustomContextMenu);
709 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
710 SLOT(toolBarPopup(const QPoint &)));
712 // Forbid too small unresizable window because it can happen
713 // with some window manager under X11.
714 setMinimumSize(300, 200);
716 if (lyxrc.allow_geometry_session) {
717 // Now take care of session management.
722 // no session handling, default to a sane size.
723 setGeometry(50, 50, 690, 510);
726 // clear session data if any.
727 settings.remove("views");
737 void GuiView::disableShellEscape()
739 BufferView * bv = documentBufferView();
742 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
743 bv->buffer().params().shell_escape = false;
744 bv->processUpdateFlags(Update::Force);
748 void GuiView::checkCancelBackground()
750 docstring const ttl = _("Cancel Export?");
751 docstring const msg = _("Do you want to cancel the background export process?");
753 Alert::prompt(ttl, msg, 1, 1,
754 _("&Cancel export"), _("Co&ntinue"));
756 Systemcall::killscript();
760 void GuiView::zoomSliderMoved(int value)
763 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
764 currentWorkArea()->scheduleRedraw(true);
765 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
769 void GuiView::zoomValueChanged(int value)
771 if (value != lyxrc.currentZoom)
772 zoomSliderMoved(value);
776 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
778 QVector<GuiWorkArea*> areas;
779 for (int i = 0; i < tabWorkAreaCount(); i++) {
780 TabWorkArea* ta = tabWorkArea(i);
781 for (int u = 0; u < ta->count(); u++) {
782 areas << ta->workArea(u);
788 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
789 string const & format)
791 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
794 case Buffer::ExportSuccess:
795 msg = bformat(_("Successful export to format: %1$s"), fmt);
797 case Buffer::ExportCancel:
798 msg = _("Document export cancelled.");
800 case Buffer::ExportError:
801 case Buffer::ExportNoPathToFormat:
802 case Buffer::ExportTexPathHasSpaces:
803 case Buffer::ExportConverterError:
804 msg = bformat(_("Error while exporting format: %1$s"), fmt);
806 case Buffer::PreviewSuccess:
807 msg = bformat(_("Successful preview of format: %1$s"), fmt);
809 case Buffer::PreviewError:
810 msg = bformat(_("Error while previewing format: %1$s"), fmt);
812 case Buffer::ExportKilled:
813 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
820 void GuiView::processingThreadStarted()
825 void GuiView::processingThreadFinished()
827 QFutureWatcher<Buffer::ExportStatus> const * watcher =
828 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
830 Buffer::ExportStatus const status = watcher->result();
831 handleExportStatus(this, status, d.processing_format);
834 BufferView const * const bv = currentBufferView();
835 if (bv && !bv->buffer().errorList("Export").empty()) {
840 bool const error = (status != Buffer::ExportSuccess &&
841 status != Buffer::PreviewSuccess &&
842 status != Buffer::ExportCancel);
844 ErrorList & el = bv->buffer().errorList(d.last_export_format);
845 // at this point, we do not know if buffer-view or
846 // master-buffer-view was called. If there was an export error,
847 // and the current buffer's error log is empty, we guess that
848 // it must be master-buffer-view that was called so we set
850 errors(d.last_export_format, el.empty());
855 void GuiView::autoSaveThreadFinished()
857 QFutureWatcher<docstring> const * watcher =
858 static_cast<QFutureWatcher<docstring> const *>(sender());
859 message(watcher->result());
864 void GuiView::saveLayout() const
867 settings.setValue("zoom_ratio", zoom_ratio_);
868 settings.setValue("devel_mode", devel_mode_);
869 settings.beginGroup("views");
870 settings.beginGroup(QString::number(id_));
871 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
872 settings.setValue("pos", pos());
873 settings.setValue("size", size());
875 settings.setValue("geometry", saveGeometry());
876 settings.setValue("layout", saveState(0));
877 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
881 void GuiView::saveUISettings() const
885 // Save the toolbar private states
886 for (auto const & tb_p : d.toolbars_)
887 tb_p.second->saveSession(settings);
888 // Now take care of all other dialogs
889 for (auto const & dlg_p : d.dialogs_)
890 dlg_p.second->saveSession(settings);
894 void GuiView::setCurrentZoom(const int v)
896 lyxrc.currentZoom = v;
897 Q_EMIT currentZoomChanged(v);
901 bool GuiView::restoreLayout()
904 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
905 // Actual zoom value: default zoom + fractional offset
906 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
907 if (zoom < static_cast<int>(zoom_min_))
909 setCurrentZoom(zoom);
910 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
911 settings.beginGroup("views");
912 settings.beginGroup(QString::number(id_));
913 QString const icon_key = "icon_size";
914 if (!settings.contains(icon_key))
917 //code below is skipped when when ~/.config/LyX is (re)created
918 setIconSize(d.iconSize(settings.value(icon_key).toString()));
920 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
921 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
922 QSize size = settings.value("size", QSize(690, 510)).toSize();
926 // Work-around for bug #6034: the window ends up in an undetermined
927 // state when trying to restore a maximized window when it is
928 // already maximized.
929 if (!(windowState() & Qt::WindowMaximized))
930 if (!restoreGeometry(settings.value("geometry").toByteArray()))
931 setGeometry(50, 50, 690, 510);
934 // Make sure layout is correctly oriented.
935 setLayoutDirection(qApp->layoutDirection());
937 // Allow the toc and view-source dock widget to be restored if needed.
939 if ((dialog = findOrBuild("toc", true)))
940 // see bug 5082. At least setup title and enabled state.
941 // Visibility will be adjusted by restoreState below.
942 dialog->prepareView();
943 if ((dialog = findOrBuild("view-source", true)))
944 dialog->prepareView();
945 if ((dialog = findOrBuild("progress", true)))
946 dialog->prepareView();
948 if (!restoreState(settings.value("layout").toByteArray(), 0))
951 // init the toolbars that have not been restored
952 for (auto const & tb_p : guiApp->toolbars()) {
953 GuiToolbar * tb = toolbar(tb_p.name);
954 if (tb && !tb->isRestored())
955 initToolbar(tb_p.name);
958 // update lock (all) toolbars positions
959 updateLockToolbars();
966 GuiToolbar * GuiView::toolbar(string const & name)
968 ToolbarMap::iterator it = d.toolbars_.find(name);
969 if (it != d.toolbars_.end())
972 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
977 void GuiView::updateLockToolbars()
979 toolbarsMovable_ = false;
980 for (ToolbarInfo const & info : guiApp->toolbars()) {
981 GuiToolbar * tb = toolbar(info.name);
982 if (tb && tb->isMovable())
983 toolbarsMovable_ = true;
988 void GuiView::constructToolbars()
990 for (auto const & tb_p : d.toolbars_)
994 // I don't like doing this here, but the standard toolbar
995 // destroys this object when it's destroyed itself (vfr)
996 d.layout_ = new LayoutBox(*this);
997 d.stack_widget_->addWidget(d.layout_);
998 d.layout_->move(0,0);
1000 // extracts the toolbars from the backend
1001 for (ToolbarInfo const & inf : guiApp->toolbars())
1002 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1006 void GuiView::initToolbars()
1008 // extracts the toolbars from the backend
1009 for (ToolbarInfo const & inf : guiApp->toolbars())
1010 initToolbar(inf.name);
1014 void GuiView::initToolbar(string const & name)
1016 GuiToolbar * tb = toolbar(name);
1019 int const visibility = guiApp->toolbars().defaultVisibility(name);
1020 bool newline = !(visibility & Toolbars::SAMEROW);
1021 tb->setVisible(false);
1022 tb->setVisibility(visibility);
1024 if (visibility & Toolbars::TOP) {
1026 addToolBarBreak(Qt::TopToolBarArea);
1027 addToolBar(Qt::TopToolBarArea, tb);
1030 if (visibility & Toolbars::BOTTOM) {
1032 addToolBarBreak(Qt::BottomToolBarArea);
1033 addToolBar(Qt::BottomToolBarArea, tb);
1036 if (visibility & Toolbars::LEFT) {
1038 addToolBarBreak(Qt::LeftToolBarArea);
1039 addToolBar(Qt::LeftToolBarArea, tb);
1042 if (visibility & Toolbars::RIGHT) {
1044 addToolBarBreak(Qt::RightToolBarArea);
1045 addToolBar(Qt::RightToolBarArea, tb);
1048 if (visibility & Toolbars::ON)
1049 tb->setVisible(true);
1051 tb->setMovable(true);
1055 TocModels & GuiView::tocModels()
1057 return d.toc_models_;
1061 void GuiView::setFocus()
1063 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1064 QMainWindow::setFocus();
1068 bool GuiView::hasFocus() const
1070 if (currentWorkArea())
1071 return currentWorkArea()->hasFocus();
1072 if (currentMainWorkArea())
1073 return currentMainWorkArea()->hasFocus();
1074 return d.bg_widget_->hasFocus();
1078 void GuiView::focusInEvent(QFocusEvent * e)
1080 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1081 QMainWindow::focusInEvent(e);
1082 // Make sure guiApp points to the correct view.
1083 guiApp->setCurrentView(this);
1084 if (currentWorkArea())
1085 currentWorkArea()->setFocus();
1086 else if (currentMainWorkArea())
1087 currentMainWorkArea()->setFocus();
1089 d.bg_widget_->setFocus();
1093 void GuiView::showEvent(QShowEvent * e)
1095 LYXERR(Debug::GUI, "Passed Geometry "
1096 << size().height() << "x" << size().width()
1097 << "+" << pos().x() << "+" << pos().y());
1099 if (d.splitter_->count() == 0)
1100 // No work area, switch to the background widget.
1104 QMainWindow::showEvent(e);
1108 bool GuiView::closeScheduled()
1115 bool GuiView::prepareAllBuffersForLogout()
1117 Buffer * first = theBufferList().first();
1121 // First, iterate over all buffers and ask the users if unsaved
1122 // changes should be saved.
1123 // We cannot use a for loop as the buffer list cycles.
1126 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1128 b = theBufferList().next(b);
1129 } while (b != first);
1131 // Next, save session state
1132 // When a view/window was closed before without quitting LyX, there
1133 // are already entries in the lastOpened list.
1134 theSession().lastOpened().clear();
1141 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1142 ** is responsibility of the container (e.g., dialog)
1144 void GuiView::closeEvent(QCloseEvent * close_event)
1146 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1148 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1149 Alert::warning(_("Exit LyX"),
1150 _("LyX could not be closed because documents are being processed by LyX."));
1151 close_event->setAccepted(false);
1155 // If the user pressed the x (so we didn't call closeView
1156 // programmatically), we want to clear all existing entries.
1158 theSession().lastOpened().clear();
1163 // it can happen that this event arrives without selecting the view,
1164 // e.g. when clicking the close button on a background window.
1166 if (!closeWorkAreaAll()) {
1168 close_event->ignore();
1172 // Make sure that nothing will use this to be closed View.
1173 guiApp->unregisterView(this);
1175 if (isFullScreen()) {
1176 // Switch off fullscreen before closing.
1181 // Make sure the timer time out will not trigger a statusbar update.
1182 d.statusbar_timer_.stop();
1184 // Saving fullscreen requires additional tweaks in the toolbar code.
1185 // It wouldn't also work under linux natively.
1186 if (lyxrc.allow_geometry_session) {
1191 close_event->accept();
1195 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1197 if (event->mimeData()->hasUrls())
1199 /// \todo Ask lyx-devel is this is enough:
1200 /// if (event->mimeData()->hasFormat("text/plain"))
1201 /// event->acceptProposedAction();
1205 void GuiView::dropEvent(QDropEvent * event)
1207 QList<QUrl> files = event->mimeData()->urls();
1208 if (files.isEmpty())
1211 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1212 for (int i = 0; i != files.size(); ++i) {
1213 string const file = os::internal_path(fromqstr(
1214 files.at(i).toLocalFile()));
1218 string const ext = support::getExtension(file);
1219 vector<const Format *> found_formats;
1221 // Find all formats that have the correct extension.
1222 for (const Format * fmt : theConverters().importableFormats())
1223 if (fmt->hasExtension(ext))
1224 found_formats.push_back(fmt);
1227 if (!found_formats.empty()) {
1228 if (found_formats.size() > 1) {
1229 //FIXME: show a dialog to choose the correct importable format
1230 LYXERR(Debug::FILES,
1231 "Multiple importable formats found, selecting first");
1233 string const arg = found_formats[0]->name() + " " + file;
1234 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1237 //FIXME: do we have to explicitly check whether it's a lyx file?
1238 LYXERR(Debug::FILES,
1239 "No formats found, trying to open it as a lyx file");
1240 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1242 // add the functions to the queue
1243 guiApp->addToFuncRequestQueue(cmd);
1246 // now process the collected functions. We perform the events
1247 // asynchronously. This prevents potential problems in case the
1248 // BufferView is closed within an event.
1249 guiApp->processFuncRequestQueueAsync();
1253 void GuiView::message(docstring const & str)
1255 if (ForkedProcess::iAmAChild())
1258 // call is moved to GUI-thread by GuiProgress
1259 d.progress_->appendMessage(toqstr(str));
1263 void GuiView::clearMessageText()
1265 message(docstring());
1269 void GuiView::updateStatusBarMessage(QString const & str)
1271 statusBar()->showMessage(str);
1272 d.statusbar_timer_.stop();
1273 d.statusbar_timer_.start(3000);
1277 void GuiView::clearMessage()
1279 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1280 // the hasFocus function mostly returns false, even if the focus is on
1281 // a workarea in this view.
1285 d.statusbar_timer_.stop();
1289 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1291 if (wa != d.current_work_area_
1292 || wa->bufferView().buffer().isInternal())
1294 Buffer const & buf = wa->bufferView().buffer();
1295 // Set the windows title
1296 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1297 if (buf.notifiesExternalModification()) {
1298 title = bformat(_("%1$s (modified externally)"), title);
1299 // If the external modification status has changed, then maybe the status of
1300 // buffer-save has changed too.
1304 title += from_ascii(" - LyX");
1306 setWindowTitle(toqstr(title));
1307 // Sets the path for the window: this is used by OSX to
1308 // allow a context click on the title bar showing a menu
1309 // with the path up to the file
1310 setWindowFilePath(toqstr(buf.absFileName()));
1311 // Tell Qt whether the current document is changed
1312 setWindowModified(!buf.isClean());
1314 if (buf.params().shell_escape)
1315 shell_escape_->show();
1317 shell_escape_->hide();
1319 if (buf.hasReadonlyFlag())
1324 if (buf.lyxvc().inUse()) {
1325 version_control_->show();
1326 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1328 version_control_->hide();
1332 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1334 if (d.current_work_area_)
1335 // disconnect the current work area from all slots
1336 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1338 disconnectBufferView();
1339 connectBufferView(wa->bufferView());
1340 connectBuffer(wa->bufferView().buffer());
1341 d.current_work_area_ = wa;
1342 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1343 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1344 QObject::connect(wa, SIGNAL(busy(bool)),
1345 this, SLOT(setBusy(bool)));
1346 // connection of a signal to a signal
1347 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1348 this, SIGNAL(bufferViewChanged()));
1349 Q_EMIT updateWindowTitle(wa);
1350 Q_EMIT bufferViewChanged();
1354 void GuiView::onBufferViewChanged()
1357 // Buffer-dependent dialogs must be updated. This is done here because
1358 // some dialogs require buffer()->text.
1363 void GuiView::on_lastWorkAreaRemoved()
1366 // We already are in a close event. Nothing more to do.
1369 if (d.splitter_->count() > 1)
1370 // We have a splitter so don't close anything.
1373 // Reset and updates the dialogs.
1374 Q_EMIT bufferViewChanged();
1379 if (lyxrc.open_buffers_in_tabs)
1380 // Nothing more to do, the window should stay open.
1383 if (guiApp->viewIds().size() > 1) {
1389 // On Mac we also close the last window because the application stay
1390 // resident in memory. On other platforms we don't close the last
1391 // window because this would quit the application.
1397 void GuiView::updateStatusBar()
1399 // let the user see the explicit message
1400 if (d.statusbar_timer_.isActive())
1407 void GuiView::showMessage()
1411 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1412 if (msg.isEmpty()) {
1413 BufferView const * bv = currentBufferView();
1415 msg = toqstr(bv->cursor().currentState(devel_mode_));
1417 msg = qt_("Welcome to LyX!");
1419 statusBar()->showMessage(msg);
1423 bool GuiView::event(QEvent * e)
1427 // Useful debug code:
1428 //case QEvent::ActivationChange:
1429 //case QEvent::WindowDeactivate:
1430 //case QEvent::Paint:
1431 //case QEvent::Enter:
1432 //case QEvent::Leave:
1433 //case QEvent::HoverEnter:
1434 //case QEvent::HoverLeave:
1435 //case QEvent::HoverMove:
1436 //case QEvent::StatusTip:
1437 //case QEvent::DragEnter:
1438 //case QEvent::DragLeave:
1439 //case QEvent::Drop:
1442 case QEvent::WindowStateChange: {
1443 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1444 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1445 bool result = QMainWindow::event(e);
1446 bool nfstate = (windowState() & Qt::WindowFullScreen);
1447 if (!ofstate && nfstate) {
1448 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1449 // switch to full-screen state
1450 if (lyxrc.full_screen_statusbar)
1451 statusBar()->hide();
1452 if (lyxrc.full_screen_menubar)
1454 if (lyxrc.full_screen_toolbars) {
1455 for (auto const & tb_p : d.toolbars_)
1456 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1457 tb_p.second->hide();
1459 for (int i = 0; i != d.splitter_->count(); ++i)
1460 d.tabWorkArea(i)->setFullScreen(true);
1461 #if QT_VERSION > 0x050903
1462 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1463 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1465 setContentsMargins(-2, -2, -2, -2);
1467 hideDialogs("prefs", nullptr);
1468 } else if (ofstate && !nfstate) {
1469 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1470 // switch back from full-screen state
1471 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1472 statusBar()->show();
1473 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1475 if (lyxrc.full_screen_toolbars) {
1476 for (auto const & tb_p : d.toolbars_)
1477 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1478 tb_p.second->show();
1481 for (int i = 0; i != d.splitter_->count(); ++i)
1482 d.tabWorkArea(i)->setFullScreen(false);
1483 #if QT_VERSION > 0x050903
1484 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1486 setContentsMargins(0, 0, 0, 0);
1490 case QEvent::WindowActivate: {
1491 GuiView * old_view = guiApp->currentView();
1492 if (this == old_view) {
1494 return QMainWindow::event(e);
1496 if (old_view && old_view->currentBufferView()) {
1497 // save current selection to the selection buffer to allow
1498 // middle-button paste in this window.
1499 cap::saveSelection(old_view->currentBufferView()->cursor());
1501 guiApp->setCurrentView(this);
1502 if (d.current_work_area_)
1503 on_currentWorkAreaChanged(d.current_work_area_);
1507 return QMainWindow::event(e);
1510 case QEvent::ShortcutOverride: {
1512 if (isFullScreen() && menuBar()->isHidden()) {
1513 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1514 // FIXME: we should also try to detect special LyX shortcut such as
1515 // Alt-P and Alt-M. Right now there is a hack in
1516 // GuiWorkArea::processKeySym() that hides again the menubar for
1518 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1520 return QMainWindow::event(e);
1523 return QMainWindow::event(e);
1526 case QEvent::ApplicationPaletteChange: {
1527 // runtime switch from/to dark mode
1529 return QMainWindow::event(e);
1533 return QMainWindow::event(e);
1537 void GuiView::resetWindowTitle()
1539 setWindowTitle(qt_("LyX"));
1542 bool GuiView::focusNextPrevChild(bool /*next*/)
1549 bool GuiView::busy() const
1555 void GuiView::setBusy(bool busy)
1557 bool const busy_before = busy_ > 0;
1558 busy ? ++busy_ : --busy_;
1559 if ((busy_ > 0) == busy_before)
1560 // busy state didn't change
1564 QApplication::setOverrideCursor(Qt::WaitCursor);
1567 QApplication::restoreOverrideCursor();
1572 void GuiView::resetCommandExecute()
1574 command_execute_ = false;
1579 double GuiView::pixelRatio() const
1581 #if QT_VERSION >= 0x050000
1582 return qt_scale_factor * devicePixelRatio();
1589 GuiWorkArea * GuiView::workArea(int index)
1591 if (TabWorkArea * twa = d.currentTabWorkArea())
1592 if (index < twa->count())
1593 return twa->workArea(index);
1598 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1600 if (currentWorkArea()
1601 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1602 return currentWorkArea();
1603 if (TabWorkArea * twa = d.currentTabWorkArea())
1604 return twa->workArea(buffer);
1609 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1611 // Automatically create a TabWorkArea if there are none yet.
1612 TabWorkArea * tab_widget = d.splitter_->count()
1613 ? d.currentTabWorkArea() : addTabWorkArea();
1614 return tab_widget->addWorkArea(buffer, *this);
1618 TabWorkArea * GuiView::addTabWorkArea()
1620 TabWorkArea * twa = new TabWorkArea;
1621 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1622 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1623 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1624 this, SLOT(on_lastWorkAreaRemoved()));
1626 d.splitter_->addWidget(twa);
1627 d.stack_widget_->setCurrentWidget(d.splitter_);
1632 GuiWorkArea const * GuiView::currentWorkArea() const
1634 return d.current_work_area_;
1638 GuiWorkArea * GuiView::currentWorkArea()
1640 return d.current_work_area_;
1644 GuiWorkArea const * GuiView::currentMainWorkArea() const
1646 if (!d.currentTabWorkArea())
1648 return d.currentTabWorkArea()->currentWorkArea();
1652 GuiWorkArea * GuiView::currentMainWorkArea()
1654 if (!d.currentTabWorkArea())
1656 return d.currentTabWorkArea()->currentWorkArea();
1660 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1662 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1664 d.current_work_area_ = nullptr;
1666 Q_EMIT bufferViewChanged();
1670 // FIXME: I've no clue why this is here and why it accesses
1671 // theGuiApp()->currentView, which might be 0 (bug 6464).
1672 // See also 27525 (vfr).
1673 if (theGuiApp()->currentView() == this
1674 && theGuiApp()->currentView()->currentWorkArea() == wa)
1677 if (currentBufferView())
1678 cap::saveSelection(currentBufferView()->cursor());
1680 theGuiApp()->setCurrentView(this);
1681 d.current_work_area_ = wa;
1683 // We need to reset this now, because it will need to be
1684 // right if the tabWorkArea gets reset in the for loop. We
1685 // will change it back if we aren't in that case.
1686 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1687 d.current_main_work_area_ = wa;
1689 for (int i = 0; i != d.splitter_->count(); ++i) {
1690 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1691 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1692 << ", Current main wa: " << currentMainWorkArea());
1697 d.current_main_work_area_ = old_cmwa;
1699 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1700 on_currentWorkAreaChanged(wa);
1701 BufferView & bv = wa->bufferView();
1702 bv.cursor().fixIfBroken();
1704 wa->setUpdatesEnabled(true);
1705 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1709 void GuiView::removeWorkArea(GuiWorkArea * wa)
1711 LASSERT(wa, return);
1712 if (wa == d.current_work_area_) {
1714 disconnectBufferView();
1715 d.current_work_area_ = nullptr;
1716 d.current_main_work_area_ = nullptr;
1719 bool found_twa = false;
1720 for (int i = 0; i != d.splitter_->count(); ++i) {
1721 TabWorkArea * twa = d.tabWorkArea(i);
1722 if (twa->removeWorkArea(wa)) {
1723 // Found in this tab group, and deleted the GuiWorkArea.
1725 if (twa->count() != 0) {
1726 if (d.current_work_area_ == nullptr)
1727 // This means that we are closing the current GuiWorkArea, so
1728 // switch to the next GuiWorkArea in the found TabWorkArea.
1729 setCurrentWorkArea(twa->currentWorkArea());
1731 // No more WorkAreas in this tab group, so delete it.
1738 // It is not a tabbed work area (i.e., the search work area), so it
1739 // should be deleted by other means.
1740 LASSERT(found_twa, return);
1742 if (d.current_work_area_ == nullptr) {
1743 if (d.splitter_->count() != 0) {
1744 TabWorkArea * twa = d.currentTabWorkArea();
1745 setCurrentWorkArea(twa->currentWorkArea());
1747 // No more work areas, switch to the background widget.
1748 setCurrentWorkArea(nullptr);
1754 LayoutBox * GuiView::getLayoutDialog() const
1760 void GuiView::updateLayoutList()
1763 d.layout_->updateContents(false);
1767 void GuiView::updateToolbars()
1769 if (d.current_work_area_) {
1771 if (d.current_work_area_->bufferView().cursor().inMathed()
1772 && !d.current_work_area_->bufferView().cursor().inRegexped())
1773 context |= Toolbars::MATH;
1774 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1775 context |= Toolbars::TABLE;
1776 if (currentBufferView()->buffer().areChangesPresent()
1777 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1778 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1779 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1780 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1781 context |= Toolbars::REVIEW;
1782 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1783 context |= Toolbars::MATHMACROTEMPLATE;
1784 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1785 context |= Toolbars::IPA;
1786 if (command_execute_)
1787 context |= Toolbars::MINIBUFFER;
1788 if (minibuffer_focus_) {
1789 context |= Toolbars::MINIBUFFER_FOCUS;
1790 minibuffer_focus_ = false;
1793 for (auto const & tb_p : d.toolbars_)
1794 tb_p.second->update(context);
1796 for (auto const & tb_p : d.toolbars_)
1797 tb_p.second->update();
1801 void GuiView::refillToolbars()
1803 for (auto const & tb_p : d.toolbars_)
1804 tb_p.second->refill();
1808 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1810 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1811 LASSERT(newBuffer, return);
1813 GuiWorkArea * wa = workArea(*newBuffer);
1814 if (wa == nullptr) {
1816 newBuffer->masterBuffer()->updateBuffer();
1818 wa = addWorkArea(*newBuffer);
1819 // scroll to the position when the BufferView was last closed
1820 if (lyxrc.use_lastfilepos) {
1821 LastFilePosSection::FilePos filepos =
1822 theSession().lastFilePos().load(newBuffer->fileName());
1823 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1826 //Disconnect the old buffer...there's no new one.
1829 connectBuffer(*newBuffer);
1830 connectBufferView(wa->bufferView());
1832 setCurrentWorkArea(wa);
1836 void GuiView::connectBuffer(Buffer & buf)
1838 buf.setGuiDelegate(this);
1842 void GuiView::disconnectBuffer()
1844 if (d.current_work_area_)
1845 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1849 void GuiView::connectBufferView(BufferView & bv)
1851 bv.setGuiDelegate(this);
1855 void GuiView::disconnectBufferView()
1857 if (d.current_work_area_)
1858 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1862 void GuiView::errors(string const & error_type, bool from_master)
1864 BufferView const * const bv = currentBufferView();
1868 ErrorList const & el = from_master ?
1869 bv->buffer().masterBuffer()->errorList(error_type) :
1870 bv->buffer().errorList(error_type);
1875 string err = error_type;
1877 err = "from_master|" + error_type;
1878 showDialog("errorlist", err);
1882 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1884 d.toc_models_.updateItem(toqstr(type), dit);
1888 void GuiView::structureChanged()
1890 // This is called from the Buffer, which has no way to ensure that cursors
1891 // in BufferView remain valid.
1892 if (documentBufferView())
1893 documentBufferView()->cursor().sanitize();
1894 // FIXME: This is slightly expensive, though less than the tocBackend update
1895 // (#9880). This also resets the view in the Toc Widget (#6675).
1896 d.toc_models_.reset(documentBufferView());
1897 // Navigator needs more than a simple update in this case. It needs to be
1899 updateDialog("toc", "");
1903 void GuiView::updateDialog(string const & name, string const & sdata)
1905 if (!isDialogVisible(name))
1908 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1909 if (it == d.dialogs_.end())
1912 Dialog * const dialog = it->second.get();
1913 if (dialog->isVisibleView())
1914 dialog->initialiseParams(sdata);
1918 BufferView * GuiView::documentBufferView()
1920 return currentMainWorkArea()
1921 ? ¤tMainWorkArea()->bufferView()
1926 BufferView const * GuiView::documentBufferView() const
1928 return currentMainWorkArea()
1929 ? ¤tMainWorkArea()->bufferView()
1934 BufferView * GuiView::currentBufferView()
1936 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1940 BufferView const * GuiView::currentBufferView() const
1942 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1946 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1947 Buffer const * orig, Buffer * clone)
1949 bool const success = clone->autoSave();
1951 busyBuffers.remove(orig);
1953 ? _("Automatic save done.")
1954 : _("Automatic save failed!");
1958 void GuiView::autoSave()
1960 LYXERR(Debug::INFO, "Running autoSave()");
1962 Buffer * buffer = documentBufferView()
1963 ? &documentBufferView()->buffer() : nullptr;
1965 resetAutosaveTimers();
1969 GuiViewPrivate::busyBuffers.insert(buffer);
1970 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1971 buffer, buffer->cloneBufferOnly());
1972 d.autosave_watcher_.setFuture(f);
1973 resetAutosaveTimers();
1977 void GuiView::resetAutosaveTimers()
1980 d.autosave_timeout_.restart();
1984 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1987 Buffer * buf = currentBufferView()
1988 ? ¤tBufferView()->buffer() : nullptr;
1989 Buffer * doc_buffer = documentBufferView()
1990 ? &(documentBufferView()->buffer()) : nullptr;
1993 /* In LyX/Mac, when a dialog is open, the menus of the
1994 application can still be accessed without giving focus to
1995 the main window. In this case, we want to disable the menu
1996 entries that are buffer-related.
1997 This code must not be used on Linux and Windows, since it
1998 would disable buffer-related entries when hovering over the
1999 menu (see bug #9574).
2001 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2007 // Check whether we need a buffer
2008 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2009 // no, exit directly
2010 flag.message(from_utf8(N_("Command not allowed with"
2011 "out any document open")));
2012 flag.setEnabled(false);
2016 if (cmd.origin() == FuncRequest::TOC) {
2017 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2018 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2019 flag.setEnabled(false);
2023 switch(cmd.action()) {
2024 case LFUN_BUFFER_IMPORT:
2027 case LFUN_MASTER_BUFFER_EXPORT:
2029 && (doc_buffer->parent() != nullptr
2030 || doc_buffer->hasChildren())
2031 && !d.processing_thread_watcher_.isRunning()
2032 // this launches a dialog, which would be in the wrong Buffer
2033 && !(::lyx::operator==(cmd.argument(), "custom"));
2036 case LFUN_MASTER_BUFFER_UPDATE:
2037 case LFUN_MASTER_BUFFER_VIEW:
2039 && (doc_buffer->parent() != nullptr
2040 || doc_buffer->hasChildren())
2041 && !d.processing_thread_watcher_.isRunning();
2044 case LFUN_BUFFER_UPDATE:
2045 case LFUN_BUFFER_VIEW: {
2046 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2050 string format = to_utf8(cmd.argument());
2051 if (cmd.argument().empty())
2052 format = doc_buffer->params().getDefaultOutputFormat();
2053 enable = doc_buffer->params().isExportable(format, true);
2057 case LFUN_BUFFER_RELOAD:
2058 enable = doc_buffer && !doc_buffer->isUnnamed()
2059 && doc_buffer->fileName().exists()
2060 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2063 case LFUN_BUFFER_RESET_EXPORT:
2064 enable = doc_buffer != nullptr;
2067 case LFUN_BUFFER_CHILD_OPEN:
2068 enable = doc_buffer != nullptr;
2071 case LFUN_MASTER_BUFFER_FORALL: {
2072 if (doc_buffer == nullptr) {
2073 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2077 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2078 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2079 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2084 for (Buffer * buf : doc_buffer->allRelatives()) {
2085 GuiWorkArea * wa = workArea(*buf);
2088 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2089 enable = flag.enabled();
2096 case LFUN_BUFFER_WRITE:
2097 enable = doc_buffer && (doc_buffer->isUnnamed()
2098 || (!doc_buffer->isClean()
2099 || cmd.argument() == "force"));
2102 //FIXME: This LFUN should be moved to GuiApplication.
2103 case LFUN_BUFFER_WRITE_ALL: {
2104 // We enable the command only if there are some modified buffers
2105 Buffer * first = theBufferList().first();
2110 // We cannot use a for loop as the buffer list is a cycle.
2112 if (!b->isClean()) {
2116 b = theBufferList().next(b);
2117 } while (b != first);
2121 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2122 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2125 case LFUN_BUFFER_EXPORT: {
2126 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2130 return doc_buffer->getStatus(cmd, flag);
2133 case LFUN_BUFFER_EXPORT_AS:
2134 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2139 case LFUN_BUFFER_WRITE_AS:
2140 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2141 enable = doc_buffer != nullptr;
2144 case LFUN_EXPORT_CANCEL:
2145 enable = d.processing_thread_watcher_.isRunning();
2148 case LFUN_BUFFER_CLOSE:
2149 case LFUN_VIEW_CLOSE:
2150 enable = doc_buffer != nullptr;
2153 case LFUN_BUFFER_CLOSE_ALL:
2154 enable = theBufferList().last() != theBufferList().first();
2157 case LFUN_BUFFER_CHKTEX: {
2158 // hide if we have no checktex command
2159 if (lyxrc.chktex_command.empty()) {
2160 flag.setUnknown(true);
2164 if (!doc_buffer || !doc_buffer->params().isLatex()
2165 || d.processing_thread_watcher_.isRunning()) {
2166 // grey out, don't hide
2174 case LFUN_VIEW_SPLIT:
2175 if (cmd.getArg(0) == "vertical")
2176 enable = doc_buffer && (d.splitter_->count() == 1 ||
2177 d.splitter_->orientation() == Qt::Vertical);
2179 enable = doc_buffer && (d.splitter_->count() == 1 ||
2180 d.splitter_->orientation() == Qt::Horizontal);
2183 case LFUN_TAB_GROUP_CLOSE:
2184 enable = d.tabWorkAreaCount() > 1;
2187 case LFUN_DEVEL_MODE_TOGGLE:
2188 flag.setOnOff(devel_mode_);
2191 case LFUN_TOOLBAR_SET: {
2192 string const name = cmd.getArg(0);
2193 string const state = cmd.getArg(1);
2194 if (name.empty() || state.empty()) {
2196 docstring const msg =
2197 _("Function toolbar-set requires two arguments!");
2201 if (state != "on" && state != "off" && state != "auto") {
2203 docstring const msg =
2204 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2209 if (GuiToolbar * t = toolbar(name)) {
2210 bool const autovis = t->visibility() & Toolbars::AUTO;
2212 flag.setOnOff(t->isVisible() && !autovis);
2213 else if (state == "off")
2214 flag.setOnOff(!t->isVisible() && !autovis);
2215 else if (state == "auto")
2216 flag.setOnOff(autovis);
2219 docstring const msg =
2220 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2226 case LFUN_TOOLBAR_TOGGLE: {
2227 string const name = cmd.getArg(0);
2228 if (GuiToolbar * t = toolbar(name))
2229 flag.setOnOff(t->isVisible());
2232 docstring const msg =
2233 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2239 case LFUN_TOOLBAR_MOVABLE: {
2240 string const name = cmd.getArg(0);
2241 // use negation since locked == !movable
2243 // toolbar name * locks all toolbars
2244 flag.setOnOff(!toolbarsMovable_);
2245 else if (GuiToolbar * t = toolbar(name))
2246 flag.setOnOff(!(t->isMovable()));
2249 docstring const msg =
2250 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2256 case LFUN_ICON_SIZE:
2257 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2260 case LFUN_DROP_LAYOUTS_CHOICE:
2261 enable = buf != nullptr;
2264 case LFUN_UI_TOGGLE:
2265 flag.setOnOff(isFullScreen());
2268 case LFUN_DIALOG_DISCONNECT_INSET:
2271 case LFUN_DIALOG_HIDE:
2272 // FIXME: should we check if the dialog is shown?
2275 case LFUN_DIALOG_TOGGLE:
2276 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2279 case LFUN_DIALOG_SHOW: {
2280 string const name = cmd.getArg(0);
2282 enable = name == "aboutlyx"
2283 || name == "file" //FIXME: should be removed.
2284 || name == "lyxfiles"
2286 || name == "texinfo"
2287 || name == "progress"
2288 || name == "compare";
2289 else if (name == "character" || name == "symbols"
2290 || name == "mathdelimiter" || name == "mathmatrix") {
2291 if (!buf || buf->isReadonly())
2294 Cursor const & cur = currentBufferView()->cursor();
2295 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2298 else if (name == "latexlog")
2299 enable = FileName(doc_buffer->logName()).isReadableFile();
2300 else if (name == "spellchecker")
2301 enable = theSpellChecker()
2302 && !doc_buffer->text().empty();
2303 else if (name == "vclog")
2304 enable = doc_buffer->lyxvc().inUse();
2308 case LFUN_DIALOG_UPDATE: {
2309 string const name = cmd.getArg(0);
2311 enable = name == "prefs";
2315 case LFUN_COMMAND_EXECUTE:
2317 case LFUN_MENU_OPEN:
2318 // Nothing to check.
2321 case LFUN_COMPLETION_INLINE:
2322 if (!d.current_work_area_
2323 || !d.current_work_area_->completer().inlinePossible(
2324 currentBufferView()->cursor()))
2328 case LFUN_COMPLETION_POPUP:
2329 if (!d.current_work_area_
2330 || !d.current_work_area_->completer().popupPossible(
2331 currentBufferView()->cursor()))
2336 if (!d.current_work_area_
2337 || !d.current_work_area_->completer().inlinePossible(
2338 currentBufferView()->cursor()))
2342 case LFUN_COMPLETION_ACCEPT:
2343 if (!d.current_work_area_
2344 || (!d.current_work_area_->completer().popupVisible()
2345 && !d.current_work_area_->completer().inlineVisible()
2346 && !d.current_work_area_->completer().completionAvailable()))
2350 case LFUN_COMPLETION_CANCEL:
2351 if (!d.current_work_area_
2352 || (!d.current_work_area_->completer().popupVisible()
2353 && !d.current_work_area_->completer().inlineVisible()))
2357 case LFUN_BUFFER_ZOOM_OUT:
2358 case LFUN_BUFFER_ZOOM_IN: {
2359 // only diff between these two is that the default for ZOOM_OUT
2361 bool const neg_zoom =
2362 convert<int>(cmd.argument()) < 0 ||
2363 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2364 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2365 docstring const msg =
2366 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2370 enable = doc_buffer;
2374 case LFUN_BUFFER_ZOOM: {
2375 bool const less_than_min_zoom =
2376 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2377 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2378 docstring const msg =
2379 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2384 enable = doc_buffer;
2388 case LFUN_BUFFER_MOVE_NEXT:
2389 case LFUN_BUFFER_MOVE_PREVIOUS:
2390 // we do not cycle when moving
2391 case LFUN_BUFFER_NEXT:
2392 case LFUN_BUFFER_PREVIOUS:
2393 // because we cycle, it doesn't matter whether on first or last
2394 enable = (d.currentTabWorkArea()->count() > 1);
2396 case LFUN_BUFFER_SWITCH:
2397 // toggle on the current buffer, but do not toggle off
2398 // the other ones (is that a good idea?)
2400 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2401 flag.setOnOff(true);
2404 case LFUN_VC_REGISTER:
2405 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2407 case LFUN_VC_RENAME:
2408 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2411 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2413 case LFUN_VC_CHECK_IN:
2414 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2416 case LFUN_VC_CHECK_OUT:
2417 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2419 case LFUN_VC_LOCKING_TOGGLE:
2420 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2421 && doc_buffer->lyxvc().lockingToggleEnabled();
2422 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2424 case LFUN_VC_REVERT:
2425 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2426 && !doc_buffer->hasReadonlyFlag();
2428 case LFUN_VC_UNDO_LAST:
2429 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2431 case LFUN_VC_REPO_UPDATE:
2432 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2434 case LFUN_VC_COMMAND: {
2435 if (cmd.argument().empty())
2437 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2441 case LFUN_VC_COMPARE:
2442 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2445 case LFUN_SERVER_GOTO_FILE_ROW:
2446 case LFUN_LYX_ACTIVATE:
2447 case LFUN_WINDOW_RAISE:
2449 case LFUN_FORWARD_SEARCH:
2450 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2453 case LFUN_FILE_INSERT_PLAINTEXT:
2454 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2455 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2458 case LFUN_SPELLING_CONTINUOUSLY:
2459 flag.setOnOff(lyxrc.spellcheck_continuously);
2462 case LFUN_CITATION_OPEN:
2471 flag.setEnabled(false);
2477 static FileName selectTemplateFile()
2479 FileDialog dlg(qt_("Select template file"));
2480 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2481 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2483 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2484 QStringList(qt_("LyX Documents (*.lyx)")));
2486 if (result.first == FileDialog::Later)
2488 if (result.second.isEmpty())
2490 return FileName(fromqstr(result.second));
2494 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2498 Buffer * newBuffer = nullptr;
2500 newBuffer = checkAndLoadLyXFile(filename);
2501 } catch (ExceptionMessage const &) {
2508 message(_("Document not loaded."));
2512 setBuffer(newBuffer);
2513 newBuffer->errors("Parse");
2516 theSession().lastFiles().add(filename);
2517 theSession().writeFile();
2524 void GuiView::openDocument(string const & fname)
2526 string initpath = lyxrc.document_path;
2528 if (documentBufferView()) {
2529 string const trypath = documentBufferView()->buffer().filePath();
2530 // If directory is writeable, use this as default.
2531 if (FileName(trypath).isDirWritable())
2537 if (fname.empty()) {
2538 FileDialog dlg(qt_("Select document to open"));
2539 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2540 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2542 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2543 FileDialog::Result result =
2544 dlg.open(toqstr(initpath), filter);
2546 if (result.first == FileDialog::Later)
2549 filename = fromqstr(result.second);
2551 // check selected filename
2552 if (filename.empty()) {
2553 message(_("Canceled."));
2559 // get absolute path of file and add ".lyx" to the filename if
2561 FileName const fullname =
2562 fileSearch(string(), filename, "lyx", support::may_not_exist);
2563 if (!fullname.empty())
2564 filename = fullname.absFileName();
2566 if (!fullname.onlyPath().isDirectory()) {
2567 Alert::warning(_("Invalid filename"),
2568 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2569 from_utf8(fullname.absFileName())));
2573 // if the file doesn't exist and isn't already open (bug 6645),
2574 // let the user create one
2575 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2576 !LyXVC::file_not_found_hook(fullname)) {
2577 // the user specifically chose this name. Believe him.
2578 Buffer * const b = newFile(filename, string(), true);
2584 docstring const disp_fn = makeDisplayPath(filename);
2585 message(bformat(_("Opening document %1$s..."), disp_fn));
2588 Buffer * buf = loadDocument(fullname);
2590 str2 = bformat(_("Document %1$s opened."), disp_fn);
2591 if (buf->lyxvc().inUse())
2592 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2593 " " + _("Version control detected.");
2595 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2600 // FIXME: clean that
2601 static bool import(GuiView * lv, FileName const & filename,
2602 string const & format, ErrorList & errorList)
2604 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2606 string loader_format;
2607 vector<string> loaders = theConverters().loaders();
2608 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2609 for (string const & loader : loaders) {
2610 if (!theConverters().isReachable(format, loader))
2613 string const tofile =
2614 support::changeExtension(filename.absFileName(),
2615 theFormats().extension(loader));
2616 if (theConverters().convert(nullptr, filename, FileName(tofile),
2617 filename, format, loader, errorList) != Converters::SUCCESS)
2619 loader_format = loader;
2622 if (loader_format.empty()) {
2623 frontend::Alert::error(_("Couldn't import file"),
2624 bformat(_("No information for importing the format %1$s."),
2625 translateIfPossible(theFormats().prettyName(format))));
2629 loader_format = format;
2631 if (loader_format == "lyx") {
2632 Buffer * buf = lv->loadDocument(lyxfile);
2636 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2640 bool as_paragraphs = loader_format == "textparagraph";
2641 string filename2 = (loader_format == format) ? filename.absFileName()
2642 : support::changeExtension(filename.absFileName(),
2643 theFormats().extension(loader_format));
2644 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2646 guiApp->setCurrentView(lv);
2647 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2654 void GuiView::importDocument(string const & argument)
2657 string filename = split(argument, format, ' ');
2659 LYXERR(Debug::INFO, format << " file: " << filename);
2661 // need user interaction
2662 if (filename.empty()) {
2663 string initpath = lyxrc.document_path;
2664 if (documentBufferView()) {
2665 string const trypath = documentBufferView()->buffer().filePath();
2666 // If directory is writeable, use this as default.
2667 if (FileName(trypath).isDirWritable())
2671 docstring const text = bformat(_("Select %1$s file to import"),
2672 translateIfPossible(theFormats().prettyName(format)));
2674 FileDialog dlg(toqstr(text));
2675 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2676 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2678 docstring filter = translateIfPossible(theFormats().prettyName(format));
2681 filter += from_utf8(theFormats().extensions(format));
2684 FileDialog::Result result =
2685 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2687 if (result.first == FileDialog::Later)
2690 filename = fromqstr(result.second);
2692 // check selected filename
2693 if (filename.empty())
2694 message(_("Canceled."));
2697 if (filename.empty())
2700 // get absolute path of file
2701 FileName const fullname(support::makeAbsPath(filename));
2703 // Can happen if the user entered a path into the dialog
2705 if (fullname.onlyFileName().empty()) {
2706 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2707 "Aborting import."),
2708 from_utf8(fullname.absFileName()));
2709 frontend::Alert::error(_("File name error"), msg);
2710 message(_("Canceled."));
2715 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2717 // Check if the document already is open
2718 Buffer * buf = theBufferList().getBuffer(lyxfile);
2721 if (!closeBuffer()) {
2722 message(_("Canceled."));
2727 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2729 // if the file exists already, and we didn't do
2730 // -i lyx thefile.lyx, warn
2731 if (lyxfile.exists() && fullname != lyxfile) {
2733 docstring text = bformat(_("The document %1$s already exists.\n\n"
2734 "Do you want to overwrite that document?"), displaypath);
2735 int const ret = Alert::prompt(_("Overwrite document?"),
2736 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2739 message(_("Canceled."));
2744 message(bformat(_("Importing %1$s..."), displaypath));
2745 ErrorList errorList;
2746 if (import(this, fullname, format, errorList))
2747 message(_("imported."));
2749 message(_("file not imported!"));
2751 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2755 void GuiView::newDocument(string const & filename, string templatefile,
2758 FileName initpath(lyxrc.document_path);
2759 if (documentBufferView()) {
2760 FileName const trypath(documentBufferView()->buffer().filePath());
2761 // If directory is writeable, use this as default.
2762 if (trypath.isDirWritable())
2766 if (from_template) {
2767 if (templatefile.empty())
2768 templatefile = selectTemplateFile().absFileName();
2769 if (templatefile.empty())
2774 if (filename.empty())
2775 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2777 b = newFile(filename, templatefile, true);
2782 // If no new document could be created, it is unsure
2783 // whether there is a valid BufferView.
2784 if (currentBufferView())
2785 // Ensure the cursor is correctly positioned on screen.
2786 currentBufferView()->showCursor();
2790 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2792 BufferView * bv = documentBufferView();
2797 FileName filename(to_utf8(fname));
2798 if (filename.empty()) {
2799 // Launch a file browser
2801 string initpath = lyxrc.document_path;
2802 string const trypath = bv->buffer().filePath();
2803 // If directory is writeable, use this as default.
2804 if (FileName(trypath).isDirWritable())
2808 FileDialog dlg(qt_("Select LyX document to insert"));
2809 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2810 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2812 FileDialog::Result result = dlg.open(toqstr(initpath),
2813 QStringList(qt_("LyX Documents (*.lyx)")));
2815 if (result.first == FileDialog::Later)
2819 filename.set(fromqstr(result.second));
2821 // check selected filename
2822 if (filename.empty()) {
2823 // emit message signal.
2824 message(_("Canceled."));
2829 bv->insertLyXFile(filename, ignorelang);
2830 bv->buffer().errors("Parse");
2835 string const GuiView::getTemplatesPath(Buffer & b)
2837 // We start off with the user's templates path
2838 string result = addPath(package().user_support().absFileName(), "templates");
2839 // Check for the document language
2840 string const langcode = b.params().language->code();
2841 string const shortcode = langcode.substr(0, 2);
2842 if (!langcode.empty() && shortcode != "en") {
2843 string subpath = addPath(result, shortcode);
2844 string subpath_long = addPath(result, langcode);
2845 // If we have a subdirectory for the language already,
2847 FileName sp = FileName(subpath);
2848 if (sp.isDirectory())
2850 else if (FileName(subpath_long).isDirectory())
2851 result = subpath_long;
2853 // Ask whether we should create such a subdirectory
2854 docstring const text =
2855 bformat(_("It is suggested to save the template in a subdirectory\n"
2856 "appropriate to the document language (%1$s).\n"
2857 "This subdirectory does not exists yet.\n"
2858 "Do you want to create it?"),
2859 _(b.params().language->display()));
2860 if (Alert::prompt(_("Create Language Directory?"),
2861 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2862 // If the user agreed, we try to create it and report if this failed.
2863 if (!sp.createDirectory(0777))
2864 Alert::error(_("Subdirectory creation failed!"),
2865 _("Could not create subdirectory.\n"
2866 "The template will be saved in the parent directory."));
2872 // Do we have a layout category?
2873 string const cat = b.params().baseClass() ?
2874 b.params().baseClass()->category()
2877 string subpath = addPath(result, cat);
2878 // If we have a subdirectory for the category already,
2880 FileName sp = FileName(subpath);
2881 if (sp.isDirectory())
2884 // Ask whether we should create such a subdirectory
2885 docstring const text =
2886 bformat(_("It is suggested to save the template in a subdirectory\n"
2887 "appropriate to the layout category (%1$s).\n"
2888 "This subdirectory does not exists yet.\n"
2889 "Do you want to create it?"),
2891 if (Alert::prompt(_("Create Category Directory?"),
2892 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2893 // If the user agreed, we try to create it and report if this failed.
2894 if (!sp.createDirectory(0777))
2895 Alert::error(_("Subdirectory creation failed!"),
2896 _("Could not create subdirectory.\n"
2897 "The template will be saved in the parent directory."));
2907 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2909 FileName fname = b.fileName();
2910 FileName const oldname = fname;
2911 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2913 if (!newname.empty()) {
2916 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2918 fname = support::makeAbsPath(to_utf8(newname),
2919 oldname.onlyPath().absFileName());
2921 // Switch to this Buffer.
2924 // No argument? Ask user through dialog.
2926 QString const title = as_template ? qt_("Choose a filename to save template as")
2927 : qt_("Choose a filename to save document as");
2928 FileDialog dlg(title);
2929 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2930 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2932 if (!isLyXFileName(fname.absFileName()))
2933 fname.changeExtension(".lyx");
2935 string const path = as_template ?
2937 : fname.onlyPath().absFileName();
2938 FileDialog::Result result =
2939 dlg.save(toqstr(path),
2940 QStringList(qt_("LyX Documents (*.lyx)")),
2941 toqstr(fname.onlyFileName()));
2943 if (result.first == FileDialog::Later)
2946 fname.set(fromqstr(result.second));
2951 if (!isLyXFileName(fname.absFileName()))
2952 fname.changeExtension(".lyx");
2955 // fname is now the new Buffer location.
2957 // if there is already a Buffer open with this name, we do not want
2958 // to have another one. (the second test makes sure we're not just
2959 // trying to overwrite ourselves, which is fine.)
2960 if (theBufferList().exists(fname) && fname != oldname
2961 && theBufferList().getBuffer(fname) != &b) {
2962 docstring const text =
2963 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2964 "Please close it before attempting to overwrite it.\n"
2965 "Do you want to choose a new filename?"),
2966 from_utf8(fname.absFileName()));
2967 int const ret = Alert::prompt(_("Chosen File Already Open"),
2968 text, 0, 1, _("&Rename"), _("&Cancel"));
2970 case 0: return renameBuffer(b, docstring(), kind);
2971 case 1: return false;
2976 bool const existsLocal = fname.exists();
2977 bool const existsInVC = LyXVC::fileInVC(fname);
2978 if (existsLocal || existsInVC) {
2979 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2980 if (kind != LV_WRITE_AS && existsInVC) {
2981 // renaming to a name that is already in VC
2983 docstring text = bformat(_("The document %1$s "
2984 "is already registered.\n\n"
2985 "Do you want to choose a new name?"),
2987 docstring const title = (kind == LV_VC_RENAME) ?
2988 _("Rename document?") : _("Copy document?");
2989 docstring const button = (kind == LV_VC_RENAME) ?
2990 _("&Rename") : _("&Copy");
2991 int const ret = Alert::prompt(title, text, 0, 1,
2992 button, _("&Cancel"));
2994 case 0: return renameBuffer(b, docstring(), kind);
2995 case 1: return false;
3000 docstring text = bformat(_("The document %1$s "
3001 "already exists.\n\n"
3002 "Do you want to overwrite that document?"),
3004 int const ret = Alert::prompt(_("Overwrite document?"),
3005 text, 0, 2, _("&Overwrite"),
3006 _("&Rename"), _("&Cancel"));
3009 case 1: return renameBuffer(b, docstring(), kind);
3010 case 2: return false;
3016 case LV_VC_RENAME: {
3017 string msg = b.lyxvc().rename(fname);
3020 message(from_utf8(msg));
3024 string msg = b.lyxvc().copy(fname);
3027 message(from_utf8(msg));
3031 case LV_WRITE_AS_TEMPLATE:
3034 // LyXVC created the file already in case of LV_VC_RENAME or
3035 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3036 // relative paths of included stuff right if we moved e.g. from
3037 // /a/b.lyx to /a/c/b.lyx.
3039 bool const saved = saveBuffer(b, fname);
3046 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3048 FileName fname = b.fileName();
3050 FileDialog dlg(qt_("Choose a filename to export the document as"));
3051 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3054 QString const anyformat = qt_("Guess from extension (*.*)");
3057 vector<Format const *> export_formats;
3058 for (Format const & f : theFormats())
3059 if (f.documentFormat())
3060 export_formats.push_back(&f);
3061 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3062 map<QString, string> fmap;
3065 for (Format const * f : export_formats) {
3066 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3067 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3069 from_ascii(f->extension())));
3070 types << loc_filter;
3071 fmap[loc_filter] = f->name();
3072 if (from_ascii(f->name()) == iformat) {
3073 filter = loc_filter;
3074 ext = f->extension();
3077 string ofname = fname.onlyFileName();
3079 ofname = support::changeExtension(ofname, ext);
3080 FileDialog::Result result =
3081 dlg.save(toqstr(fname.onlyPath().absFileName()),
3085 if (result.first != FileDialog::Chosen)
3089 fname.set(fromqstr(result.second));
3090 if (filter == anyformat)
3091 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3093 fmt_name = fmap[filter];
3094 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3095 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3097 if (fmt_name.empty() || fname.empty())
3100 // fname is now the new Buffer location.
3101 if (FileName(fname).exists()) {
3102 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3103 docstring text = bformat(_("The document %1$s already "
3104 "exists.\n\nDo you want to "
3105 "overwrite that document?"),
3107 int const ret = Alert::prompt(_("Overwrite document?"),
3108 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3111 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3112 case 2: return false;
3116 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3119 return dr.dispatched();
3123 bool GuiView::saveBuffer(Buffer & b)
3125 return saveBuffer(b, FileName());
3129 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3131 if (workArea(b) && workArea(b)->inDialogMode())
3134 if (fn.empty() && b.isUnnamed())
3135 return renameBuffer(b, docstring());
3137 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3139 theSession().lastFiles().add(b.fileName());
3140 theSession().writeFile();
3144 // Switch to this Buffer.
3147 // FIXME: we don't tell the user *WHY* the save failed !!
3148 docstring const file = makeDisplayPath(b.absFileName(), 30);
3149 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3150 "Do you want to rename the document and "
3151 "try again?"), file);
3152 int const ret = Alert::prompt(_("Rename and save?"),
3153 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3156 if (!renameBuffer(b, docstring()))
3165 return saveBuffer(b, fn);
3169 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3171 return closeWorkArea(wa, false);
3175 // We only want to close the buffer if it is not visible in other workareas
3176 // of the same view, nor in other views, and if this is not a child
3177 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3179 Buffer & buf = wa->bufferView().buffer();
3181 bool last_wa = d.countWorkAreasOf(buf) == 1
3182 && !inOtherView(buf) && !buf.parent();
3184 bool close_buffer = last_wa;
3187 if (lyxrc.close_buffer_with_last_view == "yes")
3189 else if (lyxrc.close_buffer_with_last_view == "no")
3190 close_buffer = false;
3193 if (buf.isUnnamed())
3194 file = from_utf8(buf.fileName().onlyFileName());
3196 file = buf.fileName().displayName(30);
3197 docstring const text = bformat(
3198 _("Last view on document %1$s is being closed.\n"
3199 "Would you like to close or hide the document?\n"
3201 "Hidden documents can be displayed back through\n"
3202 "the menu: View->Hidden->...\n"
3204 "To remove this question, set your preference in:\n"
3205 " Tools->Preferences->Look&Feel->UserInterface\n"
3207 int ret = Alert::prompt(_("Close or hide document?"),
3208 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3211 close_buffer = (ret == 0);
3215 return closeWorkArea(wa, close_buffer);
3219 bool GuiView::closeBuffer()
3221 GuiWorkArea * wa = currentMainWorkArea();
3222 // coverity complained about this
3223 // it seems unnecessary, but perhaps is worth the check
3224 LASSERT(wa, return false);
3226 setCurrentWorkArea(wa);
3227 Buffer & buf = wa->bufferView().buffer();
3228 return closeWorkArea(wa, !buf.parent());
3232 void GuiView::writeSession() const {
3233 GuiWorkArea const * active_wa = currentMainWorkArea();
3234 for (int i = 0; i < d.splitter_->count(); ++i) {
3235 TabWorkArea * twa = d.tabWorkArea(i);
3236 for (int j = 0; j < twa->count(); ++j) {
3237 GuiWorkArea * wa = twa->workArea(j);
3238 Buffer & buf = wa->bufferView().buffer();
3239 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3245 bool GuiView::closeBufferAll()
3248 for (auto & buf : theBufferList()) {
3249 if (!saveBufferIfNeeded(*buf, false)) {
3250 // Closing has been cancelled, so abort.
3255 // Close the workareas in all other views
3256 QList<int> const ids = guiApp->viewIds();
3257 for (int i = 0; i != ids.size(); ++i) {
3258 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3262 // Close our own workareas
3263 if (!closeWorkAreaAll())
3270 bool GuiView::closeWorkAreaAll()
3272 setCurrentWorkArea(currentMainWorkArea());
3274 // We might be in a situation that there is still a tabWorkArea, but
3275 // there are no tabs anymore. This can happen when we get here after a
3276 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3277 // many TabWorkArea's have no documents anymore.
3280 // We have to call count() each time, because it can happen that
3281 // more than one splitter will disappear in one iteration (bug 5998).
3282 while (d.splitter_->count() > empty_twa) {
3283 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3285 if (twa->count() == 0)
3288 setCurrentWorkArea(twa->currentWorkArea());
3289 if (!closeTabWorkArea(twa))
3297 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3302 Buffer & buf = wa->bufferView().buffer();
3304 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3305 Alert::warning(_("Close document"),
3306 _("Document could not be closed because it is being processed by LyX."));
3311 return closeBuffer(buf);
3313 if (!inMultiTabs(wa))
3314 if (!saveBufferIfNeeded(buf, true))
3322 bool GuiView::closeBuffer(Buffer & buf)
3324 bool success = true;
3325 for (Buffer * child_buf : buf.getChildren()) {
3326 if (theBufferList().isOthersChild(&buf, child_buf)) {
3327 child_buf->setParent(nullptr);
3331 // FIXME: should we look in other tabworkareas?
3332 // ANSWER: I don't think so. I've tested, and if the child is
3333 // open in some other window, it closes without a problem.
3334 GuiWorkArea * child_wa = workArea(*child_buf);
3337 // If we are in a close_event all children will be closed in some time,
3338 // so no need to do it here. This will ensure that the children end up
3339 // in the session file in the correct order. If we close the master
3340 // buffer, we can close or release the child buffers here too.
3342 success = closeWorkArea(child_wa, true);
3346 // In this case the child buffer is open but hidden.
3347 // Even in this case, children can be dirty (e.g.,
3348 // after a label change in the master, see #11405).
3349 // Therefore, check this
3350 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3351 // If we are in a close_event all children will be closed in some time,
3352 // so no need to do it here. This will ensure that the children end up
3353 // in the session file in the correct order. If we close the master
3354 // buffer, we can close or release the child buffers here too.
3357 // Save dirty buffers also if closing_!
3358 if (saveBufferIfNeeded(*child_buf, false)) {
3359 child_buf->removeAutosaveFile();
3360 theBufferList().release(child_buf);
3362 // Saving of dirty children has been cancelled.
3363 // Cancel the whole process.
3370 // goto bookmark to update bookmark pit.
3371 // FIXME: we should update only the bookmarks related to this buffer!
3372 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3373 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3374 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3375 guiApp->gotoBookmark(i, false, false);
3377 if (saveBufferIfNeeded(buf, false)) {
3378 buf.removeAutosaveFile();
3379 theBufferList().release(&buf);
3383 // open all children again to avoid a crash because of dangling
3384 // pointers (bug 6603)
3390 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3392 while (twa == d.currentTabWorkArea()) {
3393 twa->setCurrentIndex(twa->count() - 1);
3395 GuiWorkArea * wa = twa->currentWorkArea();
3396 Buffer & b = wa->bufferView().buffer();
3398 // We only want to close the buffer if the same buffer is not visible
3399 // in another view, and if this is not a child and if we are closing
3400 // a view (not a tabgroup).
3401 bool const close_buffer =
3402 !inOtherView(b) && !b.parent() && closing_;
3404 if (!closeWorkArea(wa, close_buffer))
3411 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3413 if (buf.isClean() || buf.paragraphs().empty())
3416 // Switch to this Buffer.
3422 if (buf.isUnnamed()) {
3423 file = from_utf8(buf.fileName().onlyFileName());
3426 FileName filename = buf.fileName();
3428 file = filename.displayName(30);
3429 exists = filename.exists();
3432 // Bring this window to top before asking questions.
3437 if (hiding && buf.isUnnamed()) {
3438 docstring const text = bformat(_("The document %1$s has not been "
3439 "saved yet.\n\nDo you want to save "
3440 "the document?"), file);
3441 ret = Alert::prompt(_("Save new document?"),
3442 text, 0, 1, _("&Save"), _("&Cancel"));
3446 docstring const text = exists ?
3447 bformat(_("The document %1$s has unsaved changes."
3448 "\n\nDo you want to save the document or "
3449 "discard the changes?"), file) :
3450 bformat(_("The document %1$s has not been saved yet."
3451 "\n\nDo you want to save the document or "
3452 "discard it entirely?"), file);
3453 docstring const title = exists ?
3454 _("Save changed document?") : _("Save document?");
3455 ret = Alert::prompt(title, text, 0, 2,
3456 _("&Save"), _("&Discard"), _("&Cancel"));
3461 if (!saveBuffer(buf))
3465 // If we crash after this we could have no autosave file
3466 // but I guess this is really improbable (Jug).
3467 // Sometimes improbable things happen:
3468 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3469 // buf.removeAutosaveFile();
3471 // revert all changes
3482 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3484 Buffer & buf = wa->bufferView().buffer();
3486 for (int i = 0; i != d.splitter_->count(); ++i) {
3487 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3488 if (wa_ && wa_ != wa)
3491 return inOtherView(buf);
3495 bool GuiView::inOtherView(Buffer & buf)
3497 QList<int> const ids = guiApp->viewIds();
3499 for (int i = 0; i != ids.size(); ++i) {
3503 if (guiApp->view(ids[i]).workArea(buf))
3510 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3512 if (!documentBufferView())
3515 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3516 Buffer * const curbuf = &documentBufferView()->buffer();
3517 int nwa = twa->count();
3518 for (int i = 0; i < nwa; ++i) {
3519 if (&workArea(i)->bufferView().buffer() == curbuf) {
3521 if (np == NEXTBUFFER)
3522 next_index = (i == nwa - 1 ? 0 : i + 1);
3524 next_index = (i == 0 ? nwa - 1 : i - 1);
3526 twa->moveTab(i, next_index);
3528 setBuffer(&workArea(next_index)->bufferView().buffer());
3536 /// make sure the document is saved
3537 static bool ensureBufferClean(Buffer * buffer)
3539 LASSERT(buffer, return false);
3540 if (buffer->isClean() && !buffer->isUnnamed())
3543 docstring const file = buffer->fileName().displayName(30);
3546 if (!buffer->isUnnamed()) {
3547 text = bformat(_("The document %1$s has unsaved "
3548 "changes.\n\nDo you want to save "
3549 "the document?"), file);
3550 title = _("Save changed document?");
3553 text = bformat(_("The document %1$s has not been "
3554 "saved yet.\n\nDo you want to save "
3555 "the document?"), file);
3556 title = _("Save new document?");
3558 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3561 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3563 return buffer->isClean() && !buffer->isUnnamed();
3567 bool GuiView::reloadBuffer(Buffer & buf)
3569 currentBufferView()->cursor().reset();
3570 Buffer::ReadStatus status = buf.reload();
3571 return status == Buffer::ReadSuccess;
3575 void GuiView::checkExternallyModifiedBuffers()
3577 for (Buffer * buf : theBufferList()) {
3578 if (buf->fileName().exists() && buf->isChecksumModified()) {
3579 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3580 " Reload now? Any local changes will be lost."),
3581 from_utf8(buf->absFileName()));
3582 int const ret = Alert::prompt(_("Reload externally changed document?"),
3583 text, 0, 1, _("&Reload"), _("&Cancel"));
3591 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3593 Buffer * buffer = documentBufferView()
3594 ? &(documentBufferView()->buffer()) : nullptr;
3596 switch (cmd.action()) {
3597 case LFUN_VC_REGISTER:
3598 if (!buffer || !ensureBufferClean(buffer))
3600 if (!buffer->lyxvc().inUse()) {
3601 if (buffer->lyxvc().registrer()) {
3602 reloadBuffer(*buffer);
3603 dr.clearMessageUpdate();
3608 case LFUN_VC_RENAME:
3609 case LFUN_VC_COPY: {
3610 if (!buffer || !ensureBufferClean(buffer))
3612 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3613 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3614 // Some changes are not yet committed.
3615 // We test here and not in getStatus(), since
3616 // this test is expensive.
3618 LyXVC::CommandResult ret =
3619 buffer->lyxvc().checkIn(log);
3621 if (ret == LyXVC::ErrorCommand ||
3622 ret == LyXVC::VCSuccess)
3623 reloadBuffer(*buffer);
3624 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3625 frontend::Alert::error(
3626 _("Revision control error."),
3627 _("Document could not be checked in."));
3631 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3632 LV_VC_RENAME : LV_VC_COPY;
3633 renameBuffer(*buffer, cmd.argument(), kind);
3638 case LFUN_VC_CHECK_IN:
3639 if (!buffer || !ensureBufferClean(buffer))
3641 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3643 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3645 // Only skip reloading if the checkin was cancelled or
3646 // an error occurred before the real checkin VCS command
3647 // was executed, since the VCS might have changed the
3648 // file even if it could not checkin successfully.
3649 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3650 reloadBuffer(*buffer);
3654 case LFUN_VC_CHECK_OUT:
3655 if (!buffer || !ensureBufferClean(buffer))
3657 if (buffer->lyxvc().inUse()) {
3658 dr.setMessage(buffer->lyxvc().checkOut());
3659 reloadBuffer(*buffer);
3663 case LFUN_VC_LOCKING_TOGGLE:
3664 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3666 if (buffer->lyxvc().inUse()) {
3667 string res = buffer->lyxvc().lockingToggle();
3669 frontend::Alert::error(_("Revision control error."),
3670 _("Error when setting the locking property."));
3673 reloadBuffer(*buffer);
3678 case LFUN_VC_REVERT:
3681 if (buffer->lyxvc().revert()) {
3682 reloadBuffer(*buffer);
3683 dr.clearMessageUpdate();
3687 case LFUN_VC_UNDO_LAST:
3690 buffer->lyxvc().undoLast();
3691 reloadBuffer(*buffer);
3692 dr.clearMessageUpdate();
3695 case LFUN_VC_REPO_UPDATE:
3698 if (ensureBufferClean(buffer)) {
3699 dr.setMessage(buffer->lyxvc().repoUpdate());
3700 checkExternallyModifiedBuffers();
3704 case LFUN_VC_COMMAND: {
3705 string flag = cmd.getArg(0);
3706 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3709 if (contains(flag, 'M')) {
3710 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3713 string path = cmd.getArg(1);
3714 if (contains(path, "$$p") && buffer)
3715 path = subst(path, "$$p", buffer->filePath());
3716 LYXERR(Debug::LYXVC, "Directory: " << path);
3718 if (!pp.isReadableDirectory()) {
3719 lyxerr << _("Directory is not accessible.") << endl;
3722 support::PathChanger p(pp);
3724 string command = cmd.getArg(2);
3725 if (command.empty())
3728 command = subst(command, "$$i", buffer->absFileName());
3729 command = subst(command, "$$p", buffer->filePath());
3731 command = subst(command, "$$m", to_utf8(message));
3732 LYXERR(Debug::LYXVC, "Command: " << command);
3734 one.startscript(Systemcall::Wait, command);
3738 if (contains(flag, 'I'))
3739 buffer->markDirty();
3740 if (contains(flag, 'R'))
3741 reloadBuffer(*buffer);
3746 case LFUN_VC_COMPARE: {
3747 if (cmd.argument().empty()) {
3748 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3754 string rev1 = cmd.getArg(0);
3758 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3761 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3762 f2 = buffer->absFileName();
3764 string rev2 = cmd.getArg(1);
3768 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3772 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3773 f1 << "\n" << f2 << "\n" );
3774 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3775 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3785 void GuiView::openChildDocument(string const & fname)
3787 LASSERT(documentBufferView(), return);
3788 Buffer & buffer = documentBufferView()->buffer();
3789 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3790 documentBufferView()->saveBookmark(false);
3791 Buffer * child = nullptr;
3792 if (theBufferList().exists(filename)) {
3793 child = theBufferList().getBuffer(filename);
3796 message(bformat(_("Opening child document %1$s..."),
3797 makeDisplayPath(filename.absFileName())));
3798 child = loadDocument(filename, false);
3800 // Set the parent name of the child document.
3801 // This makes insertion of citations and references in the child work,
3802 // when the target is in the parent or another child document.
3804 child->setParent(&buffer);
3808 bool GuiView::goToFileRow(string const & argument)
3812 size_t i = argument.find_last_of(' ');
3813 if (i != string::npos) {
3814 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3815 istringstream is(argument.substr(i + 1));
3820 if (i == string::npos) {
3821 LYXERR0("Wrong argument: " << argument);
3824 Buffer * buf = nullptr;
3825 string const realtmp = package().temp_dir().realPath();
3826 // We have to use os::path_prefix_is() here, instead of
3827 // simply prefixIs(), because the file name comes from
3828 // an external application and may need case adjustment.
3829 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3830 buf = theBufferList().getBufferFromTmp(file_name, true);
3831 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3832 << (buf ? " success" : " failed"));
3834 // Must replace extension of the file to be .lyx
3835 // and get full path
3836 FileName const s = fileSearch(string(),
3837 support::changeExtension(file_name, ".lyx"), "lyx");
3838 // Either change buffer or load the file
3839 if (theBufferList().exists(s))
3840 buf = theBufferList().getBuffer(s);
3841 else if (s.exists()) {
3842 buf = loadDocument(s);
3847 _("File does not exist: %1$s"),
3848 makeDisplayPath(file_name)));
3854 _("No buffer for file: %1$s."),
3855 makeDisplayPath(file_name))
3860 bool success = documentBufferView()->setCursorFromRow(row);
3862 LYXERR(Debug::LATEX,
3863 "setCursorFromRow: invalid position for row " << row);
3864 frontend::Alert::error(_("Inverse Search Failed"),
3865 _("Invalid position requested by inverse search.\n"
3866 "You may need to update the viewed document."));
3872 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3874 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3875 menu->exec(QCursor::pos());
3880 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3881 Buffer const * orig, Buffer * clone, string const & format)
3883 Buffer::ExportStatus const status = func(format);
3885 // the cloning operation will have produced a clone of the entire set of
3886 // documents, starting from the master. so we must delete those.
3887 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3889 busyBuffers.remove(orig);
3894 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3895 Buffer const * orig, Buffer * clone, string const & format)
3897 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3899 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3903 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3904 Buffer const * orig, Buffer * clone, string const & format)
3906 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3908 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3912 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3913 Buffer const * orig, Buffer * clone, string const & format)
3915 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3917 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3921 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3922 Buffer const * used_buffer,
3923 docstring const & msg,
3924 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3925 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3926 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3927 bool allow_async, bool use_tmpdir)
3932 string format = argument;
3934 format = used_buffer->params().getDefaultOutputFormat();
3935 processing_format = format;
3937 progress_->clearMessages();
3940 #if EXPORT_in_THREAD
3942 GuiViewPrivate::busyBuffers.insert(used_buffer);
3943 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3944 if (!cloned_buffer) {
3945 Alert::error(_("Export Error"),
3946 _("Error cloning the Buffer."));
3949 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3954 setPreviewFuture(f);
3955 last_export_format = used_buffer->params().bufferFormat();
3958 // We are asynchronous, so we don't know here anything about the success
3961 Buffer::ExportStatus status;
3963 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3964 } else if (previewFunc) {
3965 status = (used_buffer->*previewFunc)(format);
3968 handleExportStatus(gv_, status, format);
3970 return (status == Buffer::ExportSuccess
3971 || status == Buffer::PreviewSuccess);
3975 Buffer::ExportStatus status;
3977 status = (used_buffer->*syncFunc)(format, true);
3978 } else if (previewFunc) {
3979 status = (used_buffer->*previewFunc)(format);
3982 handleExportStatus(gv_, status, format);
3984 return (status == Buffer::ExportSuccess
3985 || status == Buffer::PreviewSuccess);
3989 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3991 BufferView * bv = currentBufferView();
3992 LASSERT(bv, return);
3994 // Let the current BufferView dispatch its own actions.
3995 bv->dispatch(cmd, dr);
3996 if (dr.dispatched()) {
3997 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3998 updateDialog("document", "");
4002 // Try with the document BufferView dispatch if any.
4003 BufferView * doc_bv = documentBufferView();
4004 if (doc_bv && doc_bv != bv) {
4005 doc_bv->dispatch(cmd, dr);
4006 if (dr.dispatched()) {
4007 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4008 updateDialog("document", "");
4013 // Then let the current Cursor dispatch its own actions.
4014 bv->cursor().dispatch(cmd);
4016 // update completion. We do it here and not in
4017 // processKeySym to avoid another redraw just for a
4018 // changed inline completion
4019 if (cmd.origin() == FuncRequest::KEYBOARD) {
4020 if (cmd.action() == LFUN_SELF_INSERT
4021 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4022 updateCompletion(bv->cursor(), true, true);
4023 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4024 updateCompletion(bv->cursor(), false, true);
4026 updateCompletion(bv->cursor(), false, false);
4029 dr = bv->cursor().result();
4033 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4035 BufferView * bv = currentBufferView();
4036 // By default we won't need any update.
4037 dr.screenUpdate(Update::None);
4038 // assume cmd will be dispatched
4039 dr.dispatched(true);
4041 Buffer * doc_buffer = documentBufferView()
4042 ? &(documentBufferView()->buffer()) : nullptr;
4044 if (cmd.origin() == FuncRequest::TOC) {
4045 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4046 toc->doDispatch(bv->cursor(), cmd, dr);
4050 string const argument = to_utf8(cmd.argument());
4052 switch(cmd.action()) {
4053 case LFUN_BUFFER_CHILD_OPEN:
4054 openChildDocument(to_utf8(cmd.argument()));
4057 case LFUN_BUFFER_IMPORT:
4058 importDocument(to_utf8(cmd.argument()));
4061 case LFUN_MASTER_BUFFER_EXPORT:
4063 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4065 case LFUN_BUFFER_EXPORT: {
4068 // GCC only sees strfwd.h when building merged
4069 if (::lyx::operator==(cmd.argument(), "custom")) {
4070 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4071 // so the following test should not be needed.
4072 // In principle, we could try to switch to such a view...
4073 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4074 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4078 string const dest = cmd.getArg(1);
4079 FileName target_dir;
4080 if (!dest.empty() && FileName::isAbsolute(dest))
4081 target_dir = FileName(support::onlyPath(dest));
4083 target_dir = doc_buffer->fileName().onlyPath();
4085 string const format = (argument.empty() || argument == "default") ?
4086 doc_buffer->params().getDefaultOutputFormat() : argument;
4088 if ((dest.empty() && doc_buffer->isUnnamed())
4089 || !target_dir.isDirWritable()) {
4090 exportBufferAs(*doc_buffer, from_utf8(format));
4093 /* TODO/Review: Is it a problem to also export the children?
4094 See the update_unincluded flag */
4095 d.asyncBufferProcessing(format,
4098 &GuiViewPrivate::exportAndDestroy,
4100 nullptr, cmd.allowAsync());
4101 // TODO Inform user about success
4105 case LFUN_BUFFER_EXPORT_AS: {
4106 LASSERT(doc_buffer, break);
4107 docstring f = cmd.argument();
4109 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4110 exportBufferAs(*doc_buffer, f);
4114 case LFUN_BUFFER_UPDATE: {
4115 d.asyncBufferProcessing(argument,
4118 &GuiViewPrivate::compileAndDestroy,
4120 nullptr, cmd.allowAsync(), true);
4123 case LFUN_BUFFER_VIEW: {
4124 d.asyncBufferProcessing(argument,
4126 _("Previewing ..."),
4127 &GuiViewPrivate::previewAndDestroy,
4129 &Buffer::preview, cmd.allowAsync());
4132 case LFUN_MASTER_BUFFER_UPDATE: {
4133 d.asyncBufferProcessing(argument,
4134 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4136 &GuiViewPrivate::compileAndDestroy,
4138 nullptr, cmd.allowAsync(), true);
4141 case LFUN_MASTER_BUFFER_VIEW: {
4142 d.asyncBufferProcessing(argument,
4143 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4145 &GuiViewPrivate::previewAndDestroy,
4146 nullptr, &Buffer::preview, cmd.allowAsync());
4149 case LFUN_EXPORT_CANCEL: {
4150 Systemcall::killscript();
4153 case LFUN_BUFFER_SWITCH: {
4154 string const file_name = to_utf8(cmd.argument());
4155 if (!FileName::isAbsolute(file_name)) {
4157 dr.setMessage(_("Absolute filename expected."));
4161 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4164 dr.setMessage(_("Document not loaded"));
4168 // Do we open or switch to the buffer in this view ?
4169 if (workArea(*buffer)
4170 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4175 // Look for the buffer in other views
4176 QList<int> const ids = guiApp->viewIds();
4178 for (; i != ids.size(); ++i) {
4179 GuiView & gv = guiApp->view(ids[i]);
4180 if (gv.workArea(*buffer)) {
4182 gv.activateWindow();
4184 gv.setBuffer(buffer);
4189 // If necessary, open a new window as a last resort
4190 if (i == ids.size()) {
4191 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4197 case LFUN_BUFFER_NEXT:
4198 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4201 case LFUN_BUFFER_MOVE_NEXT:
4202 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4205 case LFUN_BUFFER_PREVIOUS:
4206 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4209 case LFUN_BUFFER_MOVE_PREVIOUS:
4210 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4213 case LFUN_BUFFER_CHKTEX:
4214 LASSERT(doc_buffer, break);
4215 doc_buffer->runChktex();
4218 case LFUN_COMMAND_EXECUTE: {
4219 command_execute_ = true;
4220 minibuffer_focus_ = true;
4223 case LFUN_DROP_LAYOUTS_CHOICE:
4224 d.layout_->showPopup();
4227 case LFUN_MENU_OPEN:
4228 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4229 menu->exec(QCursor::pos());
4232 case LFUN_FILE_INSERT: {
4233 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4234 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4235 dr.forceBufferUpdate();
4236 dr.screenUpdate(Update::Force);
4241 case LFUN_FILE_INSERT_PLAINTEXT:
4242 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4243 string const fname = to_utf8(cmd.argument());
4244 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4245 dr.setMessage(_("Absolute filename expected."));
4249 FileName filename(fname);
4250 if (fname.empty()) {
4251 FileDialog dlg(qt_("Select file to insert"));
4253 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4254 QStringList(qt_("All Files (*)")));
4256 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4257 dr.setMessage(_("Canceled."));
4261 filename.set(fromqstr(result.second));
4265 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4266 bv->dispatch(new_cmd, dr);
4271 case LFUN_BUFFER_RELOAD: {
4272 LASSERT(doc_buffer, break);
4275 bool drop = (cmd.argument() == "dump");
4278 if (!drop && !doc_buffer->isClean()) {
4279 docstring const file =
4280 makeDisplayPath(doc_buffer->absFileName(), 20);
4281 if (doc_buffer->notifiesExternalModification()) {
4282 docstring text = _("The current version will be lost. "
4283 "Are you sure you want to load the version on disk "
4284 "of the document %1$s?");
4285 ret = Alert::prompt(_("Reload saved document?"),
4286 bformat(text, file), 1, 1,
4287 _("&Reload"), _("&Cancel"));
4289 docstring text = _("Any changes will be lost. "
4290 "Are you sure you want to revert to the saved version "
4291 "of the document %1$s?");
4292 ret = Alert::prompt(_("Revert to saved document?"),
4293 bformat(text, file), 1, 1,
4294 _("&Revert"), _("&Cancel"));
4299 doc_buffer->markClean();
4300 reloadBuffer(*doc_buffer);
4301 dr.forceBufferUpdate();
4306 case LFUN_BUFFER_RESET_EXPORT:
4307 LASSERT(doc_buffer, break);
4308 doc_buffer->requireFreshStart(true);
4309 dr.setMessage(_("Buffer export reset."));
4312 case LFUN_BUFFER_WRITE:
4313 LASSERT(doc_buffer, break);
4314 saveBuffer(*doc_buffer);
4317 case LFUN_BUFFER_WRITE_AS:
4318 LASSERT(doc_buffer, break);
4319 renameBuffer(*doc_buffer, cmd.argument());
4322 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4323 LASSERT(doc_buffer, break);
4324 renameBuffer(*doc_buffer, cmd.argument(),
4325 LV_WRITE_AS_TEMPLATE);
4328 case LFUN_BUFFER_WRITE_ALL: {
4329 Buffer * first = theBufferList().first();
4332 message(_("Saving all documents..."));
4333 // We cannot use a for loop as the buffer list cycles.
4336 if (!b->isClean()) {
4338 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4340 b = theBufferList().next(b);
4341 } while (b != first);
4342 dr.setMessage(_("All documents saved."));
4346 case LFUN_MASTER_BUFFER_FORALL: {
4350 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4351 funcToRun.allowAsync(false);
4353 for (Buffer const * buf : doc_buffer->allRelatives()) {
4354 // Switch to other buffer view and resend cmd
4355 lyx::dispatch(FuncRequest(
4356 LFUN_BUFFER_SWITCH, buf->absFileName()));
4357 lyx::dispatch(funcToRun);
4360 lyx::dispatch(FuncRequest(
4361 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4365 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4366 LASSERT(doc_buffer, break);
4367 doc_buffer->clearExternalModification();
4370 case LFUN_BUFFER_CLOSE:
4374 case LFUN_BUFFER_CLOSE_ALL:
4378 case LFUN_DEVEL_MODE_TOGGLE:
4379 devel_mode_ = !devel_mode_;
4381 dr.setMessage(_("Developer mode is now enabled."));
4383 dr.setMessage(_("Developer mode is now disabled."));
4386 case LFUN_TOOLBAR_SET: {
4387 string const name = cmd.getArg(0);
4388 string const state = cmd.getArg(1);
4389 if (GuiToolbar * t = toolbar(name))
4394 case LFUN_TOOLBAR_TOGGLE: {
4395 string const name = cmd.getArg(0);
4396 if (GuiToolbar * t = toolbar(name))
4401 case LFUN_TOOLBAR_MOVABLE: {
4402 string const name = cmd.getArg(0);
4404 // toggle (all) toolbars movablility
4405 toolbarsMovable_ = !toolbarsMovable_;
4406 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4407 GuiToolbar * tb = toolbar(ti.name);
4408 if (tb && tb->isMovable() != toolbarsMovable_)
4409 // toggle toolbar movablity if it does not fit lock
4410 // (all) toolbars positions state silent = true, since
4411 // status bar notifications are slow
4414 if (toolbarsMovable_)
4415 dr.setMessage(_("Toolbars unlocked."));
4417 dr.setMessage(_("Toolbars locked."));
4418 } else if (GuiToolbar * t = toolbar(name)) {
4419 // toggle current toolbar movablity
4421 // update lock (all) toolbars positions
4422 updateLockToolbars();
4427 case LFUN_ICON_SIZE: {
4428 QSize size = d.iconSize(cmd.argument());
4430 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4431 size.width(), size.height()));
4435 case LFUN_DIALOG_UPDATE: {
4436 string const name = to_utf8(cmd.argument());
4437 if (name == "prefs" || name == "document")
4438 updateDialog(name, string());
4439 else if (name == "paragraph")
4440 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4441 else if (currentBufferView()) {
4442 Inset * inset = currentBufferView()->editedInset(name);
4443 // Can only update a dialog connected to an existing inset
4445 // FIXME: get rid of this indirection; GuiView ask the inset
4446 // if he is kind enough to update itself...
4447 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4448 //FIXME: pass DispatchResult here?
4449 inset->dispatch(currentBufferView()->cursor(), fr);
4455 case LFUN_DIALOG_TOGGLE: {
4456 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4457 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4458 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4462 case LFUN_DIALOG_DISCONNECT_INSET:
4463 disconnectDialog(to_utf8(cmd.argument()));
4466 case LFUN_DIALOG_HIDE: {
4467 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4471 case LFUN_DIALOG_SHOW: {
4472 string const name = cmd.getArg(0);
4473 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4475 if (name == "latexlog") {
4476 // getStatus checks that
4477 LASSERT(doc_buffer, break);
4478 Buffer::LogType type;
4479 string const logfile = doc_buffer->logName(&type);
4481 case Buffer::latexlog:
4484 case Buffer::buildlog:
4485 sdata = "literate ";
4488 sdata += Lexer::quoteString(logfile);
4489 showDialog("log", sdata);
4490 } else if (name == "vclog") {
4491 // getStatus checks that
4492 LASSERT(doc_buffer, break);
4493 string const sdata2 = "vc " +
4494 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4495 showDialog("log", sdata2);
4496 } else if (name == "symbols") {
4497 sdata = bv->cursor().getEncoding()->name();
4499 showDialog("symbols", sdata);
4500 } else if (name == "findreplace") {
4501 sdata = to_utf8(bv->cursor().selectionAsString(false));
4502 showDialog(name, sdata);
4504 } else if (name == "prefs" && isFullScreen()) {
4505 lfunUiToggle("fullscreen");
4506 showDialog("prefs", sdata);
4508 showDialog(name, sdata);
4513 dr.setMessage(cmd.argument());
4516 case LFUN_UI_TOGGLE: {
4517 string arg = cmd.getArg(0);
4518 if (!lfunUiToggle(arg)) {
4519 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4520 dr.setMessage(bformat(msg, from_utf8(arg)));
4522 // Make sure the keyboard focus stays in the work area.
4527 case LFUN_VIEW_SPLIT: {
4528 LASSERT(doc_buffer, break);
4529 string const orientation = cmd.getArg(0);
4530 d.splitter_->setOrientation(orientation == "vertical"
4531 ? Qt::Vertical : Qt::Horizontal);
4532 TabWorkArea * twa = addTabWorkArea();
4533 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4534 setCurrentWorkArea(wa);
4537 case LFUN_TAB_GROUP_CLOSE:
4538 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4539 closeTabWorkArea(twa);
4540 d.current_work_area_ = nullptr;
4541 twa = d.currentTabWorkArea();
4542 // Switch to the next GuiWorkArea in the found TabWorkArea.
4544 // Make sure the work area is up to date.
4545 setCurrentWorkArea(twa->currentWorkArea());
4547 setCurrentWorkArea(nullptr);
4552 case LFUN_VIEW_CLOSE:
4553 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4554 closeWorkArea(twa->currentWorkArea());
4555 d.current_work_area_ = nullptr;
4556 twa = d.currentTabWorkArea();
4557 // Switch to the next GuiWorkArea in the found TabWorkArea.
4559 // Make sure the work area is up to date.
4560 setCurrentWorkArea(twa->currentWorkArea());
4562 setCurrentWorkArea(nullptr);
4567 case LFUN_COMPLETION_INLINE:
4568 if (d.current_work_area_)
4569 d.current_work_area_->completer().showInline();
4572 case LFUN_COMPLETION_POPUP:
4573 if (d.current_work_area_)
4574 d.current_work_area_->completer().showPopup();
4579 if (d.current_work_area_)
4580 d.current_work_area_->completer().tab();
4583 case LFUN_COMPLETION_CANCEL:
4584 if (d.current_work_area_) {
4585 if (d.current_work_area_->completer().popupVisible())
4586 d.current_work_area_->completer().hidePopup();
4588 d.current_work_area_->completer().hideInline();
4592 case LFUN_COMPLETION_ACCEPT:
4593 if (d.current_work_area_)
4594 d.current_work_area_->completer().activate();
4597 case LFUN_BUFFER_ZOOM_IN:
4598 case LFUN_BUFFER_ZOOM_OUT:
4599 case LFUN_BUFFER_ZOOM: {
4600 if (cmd.argument().empty()) {
4601 if (cmd.action() == LFUN_BUFFER_ZOOM)
4603 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4608 if (cmd.action() == LFUN_BUFFER_ZOOM)
4609 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4610 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4611 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4613 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4616 // Actual zoom value: default zoom + fractional extra value
4617 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4618 if (zoom < static_cast<int>(zoom_min_))
4621 setCurrentZoom(zoom);
4623 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4624 lyxrc.currentZoom, lyxrc.defaultZoom));
4626 guiApp->fontLoader().update();
4627 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4631 case LFUN_VC_REGISTER:
4632 case LFUN_VC_RENAME:
4634 case LFUN_VC_CHECK_IN:
4635 case LFUN_VC_CHECK_OUT:
4636 case LFUN_VC_REPO_UPDATE:
4637 case LFUN_VC_LOCKING_TOGGLE:
4638 case LFUN_VC_REVERT:
4639 case LFUN_VC_UNDO_LAST:
4640 case LFUN_VC_COMMAND:
4641 case LFUN_VC_COMPARE:
4642 dispatchVC(cmd, dr);
4645 case LFUN_SERVER_GOTO_FILE_ROW:
4646 if(goToFileRow(to_utf8(cmd.argument())))
4647 dr.screenUpdate(Update::Force | Update::FitCursor);
4650 case LFUN_LYX_ACTIVATE:
4654 case LFUN_WINDOW_RAISE:
4660 case LFUN_FORWARD_SEARCH: {
4661 // it seems safe to assume we have a document buffer, since
4662 // getStatus wants one.
4663 LASSERT(doc_buffer, break);
4664 Buffer const * doc_master = doc_buffer->masterBuffer();
4665 FileName const path(doc_master->temppath());
4666 string const texname = doc_master->isChild(doc_buffer)
4667 ? DocFileName(changeExtension(
4668 doc_buffer->absFileName(),
4669 "tex")).mangledFileName()
4670 : doc_buffer->latexName();
4671 string const fulltexname =
4672 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4673 string const mastername =
4674 removeExtension(doc_master->latexName());
4675 FileName const dviname(addName(path.absFileName(),
4676 addExtension(mastername, "dvi")));
4677 FileName const pdfname(addName(path.absFileName(),
4678 addExtension(mastername, "pdf")));
4679 bool const have_dvi = dviname.exists();
4680 bool const have_pdf = pdfname.exists();
4681 if (!have_dvi && !have_pdf) {
4682 dr.setMessage(_("Please, preview the document first."));
4685 string outname = dviname.onlyFileName();
4686 string command = lyxrc.forward_search_dvi;
4687 if (!have_dvi || (have_pdf &&
4688 pdfname.lastModified() > dviname.lastModified())) {
4689 outname = pdfname.onlyFileName();
4690 command = lyxrc.forward_search_pdf;
4693 DocIterator cur = bv->cursor();
4694 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4695 LYXERR(Debug::ACTION, "Forward search: row:" << row
4697 if (row == -1 || command.empty()) {
4698 dr.setMessage(_("Couldn't proceed."));
4701 string texrow = convert<string>(row);
4703 command = subst(command, "$$n", texrow);
4704 command = subst(command, "$$f", fulltexname);
4705 command = subst(command, "$$t", texname);
4706 command = subst(command, "$$o", outname);
4708 volatile PathChanger p(path);
4710 one.startscript(Systemcall::DontWait, command);
4714 case LFUN_SPELLING_CONTINUOUSLY:
4715 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4716 dr.screenUpdate(Update::Force);
4719 case LFUN_CITATION_OPEN: {
4721 if (theFormats().getFormat("pdf"))
4722 pdfv = theFormats().getFormat("pdf")->viewer();
4723 if (theFormats().getFormat("ps"))
4724 psv = theFormats().getFormat("ps")->viewer();
4725 frontend::showTarget(argument, pdfv, psv);
4730 // The LFUN must be for one of BufferView, Buffer or Cursor;
4732 dispatchToBufferView(cmd, dr);
4736 // Need to update bv because many LFUNs here might have destroyed it
4737 bv = currentBufferView();
4739 // Clear non-empty selections
4740 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4742 Cursor & cur = bv->cursor();
4743 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4744 cur.clearSelection();
4750 bool GuiView::lfunUiToggle(string const & ui_component)
4752 if (ui_component == "scrollbar") {
4753 // hide() is of no help
4754 if (d.current_work_area_->verticalScrollBarPolicy() ==
4755 Qt::ScrollBarAlwaysOff)
4757 d.current_work_area_->setVerticalScrollBarPolicy(
4758 Qt::ScrollBarAsNeeded);
4760 d.current_work_area_->setVerticalScrollBarPolicy(
4761 Qt::ScrollBarAlwaysOff);
4762 } else if (ui_component == "statusbar") {
4763 statusBar()->setVisible(!statusBar()->isVisible());
4764 } else if (ui_component == "menubar") {
4765 menuBar()->setVisible(!menuBar()->isVisible());
4767 if (ui_component == "frame") {
4768 int const l = contentsMargins().left();
4770 //are the frames in default state?
4771 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4773 #if QT_VERSION > 0x050903
4774 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4776 setContentsMargins(-2, -2, -2, -2);
4778 #if QT_VERSION > 0x050903
4779 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4781 setContentsMargins(0, 0, 0, 0);
4784 if (ui_component == "fullscreen") {
4792 void GuiView::toggleFullScreen()
4794 setWindowState(windowState() ^ Qt::WindowFullScreen);
4798 Buffer const * GuiView::updateInset(Inset const * inset)
4803 Buffer const * inset_buffer = &(inset->buffer());
4805 for (int i = 0; i != d.splitter_->count(); ++i) {
4806 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4809 Buffer const * buffer = &(wa->bufferView().buffer());
4810 if (inset_buffer == buffer)
4811 wa->scheduleRedraw(true);
4813 return inset_buffer;
4817 void GuiView::restartCaret()
4819 /* When we move around, or type, it's nice to be able to see
4820 * the caret immediately after the keypress.
4822 if (d.current_work_area_)
4823 d.current_work_area_->startBlinkingCaret();
4825 // Take this occasion to update the other GUI elements.
4831 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4833 if (d.current_work_area_)
4834 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4839 // This list should be kept in sync with the list of insets in
4840 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4841 // dialog should have the same name as the inset.
4842 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4843 // docs in LyXAction.cpp.
4845 char const * const dialognames[] = {
4847 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4848 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4849 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4850 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4851 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4852 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4853 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4854 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4856 char const * const * const end_dialognames =
4857 dialognames + (sizeof(dialognames) / sizeof(char *));
4861 cmpCStr(char const * name) : name_(name) {}
4862 bool operator()(char const * other) {
4863 return strcmp(other, name_) == 0;
4870 bool isValidName(string const & name)
4872 return find_if(dialognames, end_dialognames,
4873 cmpCStr(name.c_str())) != end_dialognames;
4879 void GuiView::resetDialogs()
4881 // Make sure that no LFUN uses any GuiView.
4882 guiApp->setCurrentView(nullptr);
4886 constructToolbars();
4887 guiApp->menus().fillMenuBar(menuBar(), this, false);
4888 d.layout_->updateContents(true);
4889 // Now update controls with current buffer.
4890 guiApp->setCurrentView(this);
4896 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4898 for (QObject * child: widget->children()) {
4899 if (child->inherits("QGroupBox")) {
4900 QGroupBox * box = (QGroupBox*) child;
4903 flatGroupBoxes(child, flag);
4909 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4911 if (!isValidName(name))
4914 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4916 if (it != d.dialogs_.end()) {
4918 it->second->hideView();
4919 return it->second.get();
4922 Dialog * dialog = build(name);
4923 d.dialogs_[name].reset(dialog);
4924 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4925 // Force a uniform style for group boxes
4926 // On Mac non-flat works better, on Linux flat is standard
4927 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4929 if (lyxrc.allow_geometry_session)
4930 dialog->restoreSession();
4937 void GuiView::showDialog(string const & name, string const & sdata,
4940 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4944 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4950 const string name = fromqstr(qname);
4951 const string sdata = fromqstr(qdata);
4955 Dialog * dialog = findOrBuild(name, false);
4957 bool const visible = dialog->isVisibleView();
4958 dialog->showData(sdata);
4959 if (currentBufferView())
4960 currentBufferView()->editInset(name, inset);
4961 // We only set the focus to the new dialog if it was not yet
4962 // visible in order not to change the existing previous behaviour
4964 // activateWindow is needed for floating dockviews
4965 dialog->asQWidget()->raise();
4966 dialog->asQWidget()->activateWindow();
4967 if (dialog->wantInitialFocus())
4968 dialog->asQWidget()->setFocus();
4972 catch (ExceptionMessage const &) {
4980 bool GuiView::isDialogVisible(string const & name) const
4982 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4983 if (it == d.dialogs_.end())
4985 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4989 void GuiView::hideDialog(string const & name, Inset * inset)
4991 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4992 if (it == d.dialogs_.end())
4996 if (!currentBufferView())
4998 if (inset != currentBufferView()->editedInset(name))
5002 Dialog * const dialog = it->second.get();
5003 if (dialog->isVisibleView())
5005 if (currentBufferView())
5006 currentBufferView()->editInset(name, nullptr);
5010 void GuiView::disconnectDialog(string const & name)
5012 if (!isValidName(name))
5014 if (currentBufferView())
5015 currentBufferView()->editInset(name, nullptr);
5019 void GuiView::hideAll() const
5021 for(auto const & dlg_p : d.dialogs_)
5022 dlg_p.second->hideView();
5026 void GuiView::updateDialogs()
5028 for(auto const & dlg_p : d.dialogs_) {
5029 Dialog * dialog = dlg_p.second.get();
5031 if (dialog->needBufferOpen() && !documentBufferView())
5032 hideDialog(fromqstr(dialog->name()), nullptr);
5033 else if (dialog->isVisibleView())
5034 dialog->checkStatus();
5042 Dialog * GuiView::build(string const & name)
5044 return createDialog(*this, name);
5048 SEMenu::SEMenu(QWidget * parent)
5050 QAction * action = addAction(qt_("Disable Shell Escape"));
5051 connect(action, SIGNAL(triggered()),
5052 parent, SLOT(disableShellEscape()));
5055 } // namespace frontend
5058 #include "moc_GuiView.cpp"