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 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
633 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
634 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
636 zoom_slider_->setFixedWidth(fm.width('x') * 15);
638 // Make the defaultZoom center
639 zoom_slider_->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 zoom_slider_->setValue(zoom);
648 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
649 zoom_slider_->setTickPosition(QSlider::TicksBelow);
650 zoom_slider_->setTickInterval(lyxrc.defaultZoom - 10);
651 statusBar()->addPermanentWidget(zoom_slider_);
652 zoom_slider_->setEnabled(currentBufferView());
654 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
655 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
656 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
658 zoom_value_ = new QLabel(statusBar());
659 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
660 statusBar()->addPermanentWidget(zoom_value_);
661 zoom_value_->setEnabled(currentBufferView());
663 int const iconheight = max(int(d.normalIconSize), fm.height());
664 QSize const iconsize(iconheight, iconheight);
666 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
667 shell_escape_ = new QLabel(statusBar());
668 shell_escape_->setPixmap(shellescape);
669 shell_escape_->setScaledContents(true);
670 shell_escape_->setAlignment(Qt::AlignCenter);
671 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
672 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
673 "external commands for this document. "
674 "Right click to change."));
675 SEMenu * menu = new SEMenu(this);
676 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
677 menu, SLOT(showMenu(QPoint)));
678 shell_escape_->hide();
679 statusBar()->addPermanentWidget(shell_escape_);
681 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
682 read_only_ = new QLabel(statusBar());
683 read_only_->setPixmap(readonly);
684 read_only_->setScaledContents(true);
685 read_only_->setAlignment(Qt::AlignCenter);
687 statusBar()->addPermanentWidget(read_only_);
689 version_control_ = new QLabel(statusBar());
690 version_control_->setAlignment(Qt::AlignCenter);
691 version_control_->setFrameStyle(QFrame::StyledPanel);
692 version_control_->hide();
693 statusBar()->addPermanentWidget(version_control_);
695 statusBar()->setSizeGripEnabled(true);
698 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
699 SLOT(autoSaveThreadFinished()));
701 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
702 SLOT(processingThreadStarted()));
703 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
704 SLOT(processingThreadFinished()));
706 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
707 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
709 // set custom application bars context menu, e.g. tool bar and menu bar
710 setContextMenuPolicy(Qt::CustomContextMenu);
711 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
712 SLOT(toolBarPopup(const QPoint &)));
714 // Forbid too small unresizable window because it can happen
715 // with some window manager under X11.
716 setMinimumSize(300, 200);
718 if (lyxrc.allow_geometry_session) {
719 // Now take care of session management.
724 // no session handling, default to a sane size.
725 setGeometry(50, 50, 690, 510);
728 // clear session data if any.
729 settings.remove("views");
739 void GuiView::disableShellEscape()
741 BufferView * bv = documentBufferView();
744 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
745 bv->buffer().params().shell_escape = false;
746 bv->processUpdateFlags(Update::Force);
750 void GuiView::checkCancelBackground()
752 docstring const ttl = _("Cancel Export?");
753 docstring const msg = _("Do you want to cancel the background export process?");
755 Alert::prompt(ttl, msg, 1, 1,
756 _("&Cancel export"), _("Co&ntinue"));
758 Systemcall::killscript();
762 void GuiView::zoomSliderMoved(int value)
765 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
766 currentWorkArea()->scheduleRedraw(true);
767 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
771 void GuiView::zoomValueChanged(int value)
773 if (value != lyxrc.currentZoom)
774 zoomSliderMoved(value);
778 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
780 QVector<GuiWorkArea*> areas;
781 for (int i = 0; i < tabWorkAreaCount(); i++) {
782 TabWorkArea* ta = tabWorkArea(i);
783 for (int u = 0; u < ta->count(); u++) {
784 areas << ta->workArea(u);
790 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
791 string const & format)
793 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
796 case Buffer::ExportSuccess:
797 msg = bformat(_("Successful export to format: %1$s"), fmt);
799 case Buffer::ExportCancel:
800 msg = _("Document export cancelled.");
802 case Buffer::ExportError:
803 case Buffer::ExportNoPathToFormat:
804 case Buffer::ExportTexPathHasSpaces:
805 case Buffer::ExportConverterError:
806 msg = bformat(_("Error while exporting format: %1$s"), fmt);
808 case Buffer::PreviewSuccess:
809 msg = bformat(_("Successful preview of format: %1$s"), fmt);
811 case Buffer::PreviewError:
812 msg = bformat(_("Error while previewing format: %1$s"), fmt);
814 case Buffer::ExportKilled:
815 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
822 void GuiView::processingThreadStarted()
827 void GuiView::processingThreadFinished()
829 QFutureWatcher<Buffer::ExportStatus> const * watcher =
830 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
832 Buffer::ExportStatus const status = watcher->result();
833 handleExportStatus(this, status, d.processing_format);
836 BufferView const * const bv = currentBufferView();
837 if (bv && !bv->buffer().errorList("Export").empty()) {
842 bool const error = (status != Buffer::ExportSuccess &&
843 status != Buffer::PreviewSuccess &&
844 status != Buffer::ExportCancel);
846 ErrorList & el = bv->buffer().errorList(d.last_export_format);
847 // at this point, we do not know if buffer-view or
848 // master-buffer-view was called. If there was an export error,
849 // and the current buffer's error log is empty, we guess that
850 // it must be master-buffer-view that was called so we set
852 errors(d.last_export_format, el.empty());
857 void GuiView::autoSaveThreadFinished()
859 QFutureWatcher<docstring> const * watcher =
860 static_cast<QFutureWatcher<docstring> const *>(sender());
861 message(watcher->result());
866 void GuiView::saveLayout() const
869 settings.setValue("zoom_ratio", zoom_ratio_);
870 settings.setValue("devel_mode", devel_mode_);
871 settings.beginGroup("views");
872 settings.beginGroup(QString::number(id_));
873 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
874 settings.setValue("pos", pos());
875 settings.setValue("size", size());
877 settings.setValue("geometry", saveGeometry());
878 settings.setValue("layout", saveState(0));
879 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
883 void GuiView::saveUISettings() const
887 // Save the toolbar private states
888 for (auto const & tb_p : d.toolbars_)
889 tb_p.second->saveSession(settings);
890 // Now take care of all other dialogs
891 for (auto const & dlg_p : d.dialogs_)
892 dlg_p.second->saveSession(settings);
896 void GuiView::setCurrentZoom(const int v)
898 lyxrc.currentZoom = v;
899 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
900 Q_EMIT currentZoomChanged(v);
904 bool GuiView::restoreLayout()
907 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
908 // Actual zoom value: default zoom + fractional offset
909 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
910 if (zoom < static_cast<int>(zoom_min_))
912 setCurrentZoom(zoom);
913 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
914 settings.beginGroup("views");
915 settings.beginGroup(QString::number(id_));
916 QString const icon_key = "icon_size";
917 if (!settings.contains(icon_key))
920 //code below is skipped when when ~/.config/LyX is (re)created
921 setIconSize(d.iconSize(settings.value(icon_key).toString()));
923 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
924 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
925 QSize size = settings.value("size", QSize(690, 510)).toSize();
929 // Work-around for bug #6034: the window ends up in an undetermined
930 // state when trying to restore a maximized window when it is
931 // already maximized.
932 if (!(windowState() & Qt::WindowMaximized))
933 if (!restoreGeometry(settings.value("geometry").toByteArray()))
934 setGeometry(50, 50, 690, 510);
937 // Make sure layout is correctly oriented.
938 setLayoutDirection(qApp->layoutDirection());
940 // Allow the toc and view-source dock widget to be restored if needed.
942 if ((dialog = findOrBuild("toc", true)))
943 // see bug 5082. At least setup title and enabled state.
944 // Visibility will be adjusted by restoreState below.
945 dialog->prepareView();
946 if ((dialog = findOrBuild("view-source", true)))
947 dialog->prepareView();
948 if ((dialog = findOrBuild("progress", true)))
949 dialog->prepareView();
951 if (!restoreState(settings.value("layout").toByteArray(), 0))
954 // init the toolbars that have not been restored
955 for (auto const & tb_p : guiApp->toolbars()) {
956 GuiToolbar * tb = toolbar(tb_p.name);
957 if (tb && !tb->isRestored())
958 initToolbar(tb_p.name);
961 // update lock (all) toolbars positions
962 updateLockToolbars();
969 GuiToolbar * GuiView::toolbar(string const & name)
971 ToolbarMap::iterator it = d.toolbars_.find(name);
972 if (it != d.toolbars_.end())
975 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
980 void GuiView::updateLockToolbars()
982 toolbarsMovable_ = false;
983 for (ToolbarInfo const & info : guiApp->toolbars()) {
984 GuiToolbar * tb = toolbar(info.name);
985 if (tb && tb->isMovable())
986 toolbarsMovable_ = true;
991 void GuiView::constructToolbars()
993 for (auto const & tb_p : d.toolbars_)
997 // I don't like doing this here, but the standard toolbar
998 // destroys this object when it's destroyed itself (vfr)
999 d.layout_ = new LayoutBox(*this);
1000 d.stack_widget_->addWidget(d.layout_);
1001 d.layout_->move(0,0);
1003 // extracts the toolbars from the backend
1004 for (ToolbarInfo const & inf : guiApp->toolbars())
1005 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1009 void GuiView::initToolbars()
1011 // extracts the toolbars from the backend
1012 for (ToolbarInfo const & inf : guiApp->toolbars())
1013 initToolbar(inf.name);
1017 void GuiView::initToolbar(string const & name)
1019 GuiToolbar * tb = toolbar(name);
1022 int const visibility = guiApp->toolbars().defaultVisibility(name);
1023 bool newline = !(visibility & Toolbars::SAMEROW);
1024 tb->setVisible(false);
1025 tb->setVisibility(visibility);
1027 if (visibility & Toolbars::TOP) {
1029 addToolBarBreak(Qt::TopToolBarArea);
1030 addToolBar(Qt::TopToolBarArea, tb);
1033 if (visibility & Toolbars::BOTTOM) {
1035 addToolBarBreak(Qt::BottomToolBarArea);
1036 addToolBar(Qt::BottomToolBarArea, tb);
1039 if (visibility & Toolbars::LEFT) {
1041 addToolBarBreak(Qt::LeftToolBarArea);
1042 addToolBar(Qt::LeftToolBarArea, tb);
1045 if (visibility & Toolbars::RIGHT) {
1047 addToolBarBreak(Qt::RightToolBarArea);
1048 addToolBar(Qt::RightToolBarArea, tb);
1051 if (visibility & Toolbars::ON)
1052 tb->setVisible(true);
1054 tb->setMovable(true);
1058 TocModels & GuiView::tocModels()
1060 return d.toc_models_;
1064 void GuiView::setFocus()
1066 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1067 QMainWindow::setFocus();
1071 bool GuiView::hasFocus() const
1073 if (currentWorkArea())
1074 return currentWorkArea()->hasFocus();
1075 if (currentMainWorkArea())
1076 return currentMainWorkArea()->hasFocus();
1077 return d.bg_widget_->hasFocus();
1081 void GuiView::focusInEvent(QFocusEvent * e)
1083 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1084 QMainWindow::focusInEvent(e);
1085 // Make sure guiApp points to the correct view.
1086 guiApp->setCurrentView(this);
1087 if (currentWorkArea())
1088 currentWorkArea()->setFocus();
1089 else if (currentMainWorkArea())
1090 currentMainWorkArea()->setFocus();
1092 d.bg_widget_->setFocus();
1096 void GuiView::showEvent(QShowEvent * e)
1098 LYXERR(Debug::GUI, "Passed Geometry "
1099 << size().height() << "x" << size().width()
1100 << "+" << pos().x() << "+" << pos().y());
1102 if (d.splitter_->count() == 0)
1103 // No work area, switch to the background widget.
1107 QMainWindow::showEvent(e);
1111 bool GuiView::closeScheduled()
1118 bool GuiView::prepareAllBuffersForLogout()
1120 Buffer * first = theBufferList().first();
1124 // First, iterate over all buffers and ask the users if unsaved
1125 // changes should be saved.
1126 // We cannot use a for loop as the buffer list cycles.
1129 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1131 b = theBufferList().next(b);
1132 } while (b != first);
1134 // Next, save session state
1135 // When a view/window was closed before without quitting LyX, there
1136 // are already entries in the lastOpened list.
1137 theSession().lastOpened().clear();
1144 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1145 ** is responsibility of the container (e.g., dialog)
1147 void GuiView::closeEvent(QCloseEvent * close_event)
1149 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1151 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1152 Alert::warning(_("Exit LyX"),
1153 _("LyX could not be closed because documents are being processed by LyX."));
1154 close_event->setAccepted(false);
1158 // If the user pressed the x (so we didn't call closeView
1159 // programmatically), we want to clear all existing entries.
1161 theSession().lastOpened().clear();
1166 // it can happen that this event arrives without selecting the view,
1167 // e.g. when clicking the close button on a background window.
1169 if (!closeWorkAreaAll()) {
1171 close_event->ignore();
1175 // Make sure that nothing will use this to be closed View.
1176 guiApp->unregisterView(this);
1178 if (isFullScreen()) {
1179 // Switch off fullscreen before closing.
1184 // Make sure the timer time out will not trigger a statusbar update.
1185 d.statusbar_timer_.stop();
1187 // Saving fullscreen requires additional tweaks in the toolbar code.
1188 // It wouldn't also work under linux natively.
1189 if (lyxrc.allow_geometry_session) {
1194 close_event->accept();
1198 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1200 if (event->mimeData()->hasUrls())
1202 /// \todo Ask lyx-devel is this is enough:
1203 /// if (event->mimeData()->hasFormat("text/plain"))
1204 /// event->acceptProposedAction();
1208 void GuiView::dropEvent(QDropEvent * event)
1210 QList<QUrl> files = event->mimeData()->urls();
1211 if (files.isEmpty())
1214 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1215 for (int i = 0; i != files.size(); ++i) {
1216 string const file = os::internal_path(fromqstr(
1217 files.at(i).toLocalFile()));
1221 string const ext = support::getExtension(file);
1222 vector<const Format *> found_formats;
1224 // Find all formats that have the correct extension.
1225 for (const Format * fmt : theConverters().importableFormats())
1226 if (fmt->hasExtension(ext))
1227 found_formats.push_back(fmt);
1230 if (!found_formats.empty()) {
1231 if (found_formats.size() > 1) {
1232 //FIXME: show a dialog to choose the correct importable format
1233 LYXERR(Debug::FILES,
1234 "Multiple importable formats found, selecting first");
1236 string const arg = found_formats[0]->name() + " " + file;
1237 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1240 //FIXME: do we have to explicitly check whether it's a lyx file?
1241 LYXERR(Debug::FILES,
1242 "No formats found, trying to open it as a lyx file");
1243 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1245 // add the functions to the queue
1246 guiApp->addToFuncRequestQueue(cmd);
1249 // now process the collected functions. We perform the events
1250 // asynchronously. This prevents potential problems in case the
1251 // BufferView is closed within an event.
1252 guiApp->processFuncRequestQueueAsync();
1256 void GuiView::message(docstring const & str)
1258 if (ForkedProcess::iAmAChild())
1261 // call is moved to GUI-thread by GuiProgress
1262 d.progress_->appendMessage(toqstr(str));
1266 void GuiView::clearMessageText()
1268 message(docstring());
1272 void GuiView::updateStatusBarMessage(QString const & str)
1274 statusBar()->showMessage(str);
1275 d.statusbar_timer_.stop();
1276 d.statusbar_timer_.start(3000);
1280 void GuiView::clearMessage()
1282 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1283 // the hasFocus function mostly returns false, even if the focus is on
1284 // a workarea in this view.
1288 d.statusbar_timer_.stop();
1292 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1294 if (wa != d.current_work_area_
1295 || wa->bufferView().buffer().isInternal())
1297 Buffer const & buf = wa->bufferView().buffer();
1298 // Set the windows title
1299 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1300 if (buf.notifiesExternalModification()) {
1301 title = bformat(_("%1$s (modified externally)"), title);
1302 // If the external modification status has changed, then maybe the status of
1303 // buffer-save has changed too.
1307 title += from_ascii(" - LyX");
1309 setWindowTitle(toqstr(title));
1310 // Sets the path for the window: this is used by OSX to
1311 // allow a context click on the title bar showing a menu
1312 // with the path up to the file
1313 setWindowFilePath(toqstr(buf.absFileName()));
1314 // Tell Qt whether the current document is changed
1315 setWindowModified(!buf.isClean());
1317 if (buf.params().shell_escape)
1318 shell_escape_->show();
1320 shell_escape_->hide();
1322 if (buf.hasReadonlyFlag())
1327 if (buf.lyxvc().inUse()) {
1328 version_control_->show();
1329 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1331 version_control_->hide();
1335 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1337 if (d.current_work_area_)
1338 // disconnect the current work area from all slots
1339 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1341 disconnectBufferView();
1342 connectBufferView(wa->bufferView());
1343 connectBuffer(wa->bufferView().buffer());
1344 d.current_work_area_ = wa;
1345 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1346 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1347 QObject::connect(wa, SIGNAL(busy(bool)),
1348 this, SLOT(setBusy(bool)));
1349 // connection of a signal to a signal
1350 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1351 this, SIGNAL(bufferViewChanged()));
1352 Q_EMIT updateWindowTitle(wa);
1353 Q_EMIT bufferViewChanged();
1357 void GuiView::onBufferViewChanged()
1360 // Buffer-dependent dialogs must be updated. This is done here because
1361 // some dialogs require buffer()->text.
1363 zoom_slider_->setEnabled(currentBufferView());
1364 zoom_value_->setEnabled(currentBufferView());
1368 void GuiView::on_lastWorkAreaRemoved()
1371 // We already are in a close event. Nothing more to do.
1374 if (d.splitter_->count() > 1)
1375 // We have a splitter so don't close anything.
1378 // Reset and updates the dialogs.
1379 Q_EMIT bufferViewChanged();
1384 if (lyxrc.open_buffers_in_tabs)
1385 // Nothing more to do, the window should stay open.
1388 if (guiApp->viewIds().size() > 1) {
1394 // On Mac we also close the last window because the application stay
1395 // resident in memory. On other platforms we don't close the last
1396 // window because this would quit the application.
1402 void GuiView::updateStatusBar()
1404 // let the user see the explicit message
1405 if (d.statusbar_timer_.isActive())
1412 void GuiView::showMessage()
1416 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1417 if (msg.isEmpty()) {
1418 BufferView const * bv = currentBufferView();
1420 msg = toqstr(bv->cursor().currentState(devel_mode_));
1422 msg = qt_("Welcome to LyX!");
1424 statusBar()->showMessage(msg);
1428 bool GuiView::event(QEvent * e)
1432 // Useful debug code:
1433 //case QEvent::ActivationChange:
1434 //case QEvent::WindowDeactivate:
1435 //case QEvent::Paint:
1436 //case QEvent::Enter:
1437 //case QEvent::Leave:
1438 //case QEvent::HoverEnter:
1439 //case QEvent::HoverLeave:
1440 //case QEvent::HoverMove:
1441 //case QEvent::StatusTip:
1442 //case QEvent::DragEnter:
1443 //case QEvent::DragLeave:
1444 //case QEvent::Drop:
1447 case QEvent::WindowStateChange: {
1448 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1449 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1450 bool result = QMainWindow::event(e);
1451 bool nfstate = (windowState() & Qt::WindowFullScreen);
1452 if (!ofstate && nfstate) {
1453 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1454 // switch to full-screen state
1455 if (lyxrc.full_screen_statusbar)
1456 statusBar()->hide();
1457 if (lyxrc.full_screen_menubar)
1459 if (lyxrc.full_screen_toolbars) {
1460 for (auto const & tb_p : d.toolbars_)
1461 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1462 tb_p.second->hide();
1464 for (int i = 0; i != d.splitter_->count(); ++i)
1465 d.tabWorkArea(i)->setFullScreen(true);
1466 #if QT_VERSION > 0x050903
1467 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1468 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1470 setContentsMargins(-2, -2, -2, -2);
1472 hideDialogs("prefs", nullptr);
1473 } else if (ofstate && !nfstate) {
1474 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1475 // switch back from full-screen state
1476 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1477 statusBar()->show();
1478 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1480 if (lyxrc.full_screen_toolbars) {
1481 for (auto const & tb_p : d.toolbars_)
1482 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1483 tb_p.second->show();
1486 for (int i = 0; i != d.splitter_->count(); ++i)
1487 d.tabWorkArea(i)->setFullScreen(false);
1488 #if QT_VERSION > 0x050903
1489 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1491 setContentsMargins(0, 0, 0, 0);
1495 case QEvent::WindowActivate: {
1496 GuiView * old_view = guiApp->currentView();
1497 if (this == old_view) {
1499 return QMainWindow::event(e);
1501 if (old_view && old_view->currentBufferView()) {
1502 // save current selection to the selection buffer to allow
1503 // middle-button paste in this window.
1504 cap::saveSelection(old_view->currentBufferView()->cursor());
1506 guiApp->setCurrentView(this);
1507 if (d.current_work_area_)
1508 on_currentWorkAreaChanged(d.current_work_area_);
1512 return QMainWindow::event(e);
1515 case QEvent::ShortcutOverride: {
1517 if (isFullScreen() && menuBar()->isHidden()) {
1518 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1519 // FIXME: we should also try to detect special LyX shortcut such as
1520 // Alt-P and Alt-M. Right now there is a hack in
1521 // GuiWorkArea::processKeySym() that hides again the menubar for
1523 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1525 return QMainWindow::event(e);
1528 return QMainWindow::event(e);
1531 case QEvent::ApplicationPaletteChange: {
1532 // runtime switch from/to dark mode
1534 return QMainWindow::event(e);
1538 return QMainWindow::event(e);
1542 void GuiView::resetWindowTitle()
1544 setWindowTitle(qt_("LyX"));
1547 bool GuiView::focusNextPrevChild(bool /*next*/)
1554 bool GuiView::busy() const
1560 void GuiView::setBusy(bool busy)
1562 bool const busy_before = busy_ > 0;
1563 busy ? ++busy_ : --busy_;
1564 if ((busy_ > 0) == busy_before)
1565 // busy state didn't change
1569 QApplication::setOverrideCursor(Qt::WaitCursor);
1572 QApplication::restoreOverrideCursor();
1577 void GuiView::resetCommandExecute()
1579 command_execute_ = false;
1584 double GuiView::pixelRatio() const
1586 #if QT_VERSION >= 0x050000
1587 return qt_scale_factor * devicePixelRatio();
1594 GuiWorkArea * GuiView::workArea(int index)
1596 if (TabWorkArea * twa = d.currentTabWorkArea())
1597 if (index < twa->count())
1598 return twa->workArea(index);
1603 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1605 if (currentWorkArea()
1606 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1607 return currentWorkArea();
1608 if (TabWorkArea * twa = d.currentTabWorkArea())
1609 return twa->workArea(buffer);
1614 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1616 // Automatically create a TabWorkArea if there are none yet.
1617 TabWorkArea * tab_widget = d.splitter_->count()
1618 ? d.currentTabWorkArea() : addTabWorkArea();
1619 return tab_widget->addWorkArea(buffer, *this);
1623 TabWorkArea * GuiView::addTabWorkArea()
1625 TabWorkArea * twa = new TabWorkArea;
1626 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1627 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1628 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1629 this, SLOT(on_lastWorkAreaRemoved()));
1631 d.splitter_->addWidget(twa);
1632 d.stack_widget_->setCurrentWidget(d.splitter_);
1637 GuiWorkArea const * GuiView::currentWorkArea() const
1639 return d.current_work_area_;
1643 GuiWorkArea * GuiView::currentWorkArea()
1645 return d.current_work_area_;
1649 GuiWorkArea const * GuiView::currentMainWorkArea() const
1651 if (!d.currentTabWorkArea())
1653 return d.currentTabWorkArea()->currentWorkArea();
1657 GuiWorkArea * GuiView::currentMainWorkArea()
1659 if (!d.currentTabWorkArea())
1661 return d.currentTabWorkArea()->currentWorkArea();
1665 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1667 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1669 d.current_work_area_ = nullptr;
1671 Q_EMIT bufferViewChanged();
1675 // FIXME: I've no clue why this is here and why it accesses
1676 // theGuiApp()->currentView, which might be 0 (bug 6464).
1677 // See also 27525 (vfr).
1678 if (theGuiApp()->currentView() == this
1679 && theGuiApp()->currentView()->currentWorkArea() == wa)
1682 if (currentBufferView())
1683 cap::saveSelection(currentBufferView()->cursor());
1685 theGuiApp()->setCurrentView(this);
1686 d.current_work_area_ = wa;
1688 // We need to reset this now, because it will need to be
1689 // right if the tabWorkArea gets reset in the for loop. We
1690 // will change it back if we aren't in that case.
1691 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1692 d.current_main_work_area_ = wa;
1694 for (int i = 0; i != d.splitter_->count(); ++i) {
1695 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1696 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1697 << ", Current main wa: " << currentMainWorkArea());
1702 d.current_main_work_area_ = old_cmwa;
1704 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1705 on_currentWorkAreaChanged(wa);
1706 BufferView & bv = wa->bufferView();
1707 bv.cursor().fixIfBroken();
1709 wa->setUpdatesEnabled(true);
1710 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1714 void GuiView::removeWorkArea(GuiWorkArea * wa)
1716 LASSERT(wa, return);
1717 if (wa == d.current_work_area_) {
1719 disconnectBufferView();
1720 d.current_work_area_ = nullptr;
1721 d.current_main_work_area_ = nullptr;
1724 bool found_twa = false;
1725 for (int i = 0; i != d.splitter_->count(); ++i) {
1726 TabWorkArea * twa = d.tabWorkArea(i);
1727 if (twa->removeWorkArea(wa)) {
1728 // Found in this tab group, and deleted the GuiWorkArea.
1730 if (twa->count() != 0) {
1731 if (d.current_work_area_ == nullptr)
1732 // This means that we are closing the current GuiWorkArea, so
1733 // switch to the next GuiWorkArea in the found TabWorkArea.
1734 setCurrentWorkArea(twa->currentWorkArea());
1736 // No more WorkAreas in this tab group, so delete it.
1743 // It is not a tabbed work area (i.e., the search work area), so it
1744 // should be deleted by other means.
1745 LASSERT(found_twa, return);
1747 if (d.current_work_area_ == nullptr) {
1748 if (d.splitter_->count() != 0) {
1749 TabWorkArea * twa = d.currentTabWorkArea();
1750 setCurrentWorkArea(twa->currentWorkArea());
1752 // No more work areas, switch to the background widget.
1753 setCurrentWorkArea(nullptr);
1759 LayoutBox * GuiView::getLayoutDialog() const
1765 void GuiView::updateLayoutList()
1768 d.layout_->updateContents(false);
1772 void GuiView::updateToolbars()
1774 if (d.current_work_area_) {
1776 if (d.current_work_area_->bufferView().cursor().inMathed()
1777 && !d.current_work_area_->bufferView().cursor().inRegexped())
1778 context |= Toolbars::MATH;
1779 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1780 context |= Toolbars::TABLE;
1781 if (currentBufferView()->buffer().areChangesPresent()
1782 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1783 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1784 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1785 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1786 context |= Toolbars::REVIEW;
1787 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1788 context |= Toolbars::MATHMACROTEMPLATE;
1789 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1790 context |= Toolbars::IPA;
1791 if (command_execute_)
1792 context |= Toolbars::MINIBUFFER;
1793 if (minibuffer_focus_) {
1794 context |= Toolbars::MINIBUFFER_FOCUS;
1795 minibuffer_focus_ = false;
1798 for (auto const & tb_p : d.toolbars_)
1799 tb_p.second->update(context);
1801 for (auto const & tb_p : d.toolbars_)
1802 tb_p.second->update();
1806 void GuiView::refillToolbars()
1808 for (auto const & tb_p : d.toolbars_)
1809 tb_p.second->refill();
1813 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1815 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1816 LASSERT(newBuffer, return);
1818 GuiWorkArea * wa = workArea(*newBuffer);
1819 if (wa == nullptr) {
1821 newBuffer->masterBuffer()->updateBuffer();
1823 wa = addWorkArea(*newBuffer);
1824 // scroll to the position when the BufferView was last closed
1825 if (lyxrc.use_lastfilepos) {
1826 LastFilePosSection::FilePos filepos =
1827 theSession().lastFilePos().load(newBuffer->fileName());
1828 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1831 //Disconnect the old buffer...there's no new one.
1834 connectBuffer(*newBuffer);
1835 connectBufferView(wa->bufferView());
1837 setCurrentWorkArea(wa);
1841 void GuiView::connectBuffer(Buffer & buf)
1843 buf.setGuiDelegate(this);
1847 void GuiView::disconnectBuffer()
1849 if (d.current_work_area_)
1850 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1854 void GuiView::connectBufferView(BufferView & bv)
1856 bv.setGuiDelegate(this);
1860 void GuiView::disconnectBufferView()
1862 if (d.current_work_area_)
1863 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1867 void GuiView::errors(string const & error_type, bool from_master)
1869 BufferView const * const bv = currentBufferView();
1873 ErrorList const & el = from_master ?
1874 bv->buffer().masterBuffer()->errorList(error_type) :
1875 bv->buffer().errorList(error_type);
1880 string err = error_type;
1882 err = "from_master|" + error_type;
1883 showDialog("errorlist", err);
1887 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1889 d.toc_models_.updateItem(toqstr(type), dit);
1893 void GuiView::structureChanged()
1895 // This is called from the Buffer, which has no way to ensure that cursors
1896 // in BufferView remain valid.
1897 if (documentBufferView())
1898 documentBufferView()->cursor().sanitize();
1899 // FIXME: This is slightly expensive, though less than the tocBackend update
1900 // (#9880). This also resets the view in the Toc Widget (#6675).
1901 d.toc_models_.reset(documentBufferView());
1902 // Navigator needs more than a simple update in this case. It needs to be
1904 updateDialog("toc", "");
1908 void GuiView::updateDialog(string const & name, string const & sdata)
1910 if (!isDialogVisible(name))
1913 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1914 if (it == d.dialogs_.end())
1917 Dialog * const dialog = it->second.get();
1918 if (dialog->isVisibleView())
1919 dialog->initialiseParams(sdata);
1923 BufferView * GuiView::documentBufferView()
1925 return currentMainWorkArea()
1926 ? ¤tMainWorkArea()->bufferView()
1931 BufferView const * GuiView::documentBufferView() const
1933 return currentMainWorkArea()
1934 ? ¤tMainWorkArea()->bufferView()
1939 BufferView * GuiView::currentBufferView()
1941 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1945 BufferView const * GuiView::currentBufferView() const
1947 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1951 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1952 Buffer const * orig, Buffer * clone)
1954 bool const success = clone->autoSave();
1956 busyBuffers.remove(orig);
1958 ? _("Automatic save done.")
1959 : _("Automatic save failed!");
1963 void GuiView::autoSave()
1965 LYXERR(Debug::INFO, "Running autoSave()");
1967 Buffer * buffer = documentBufferView()
1968 ? &documentBufferView()->buffer() : nullptr;
1970 resetAutosaveTimers();
1974 GuiViewPrivate::busyBuffers.insert(buffer);
1975 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1976 buffer, buffer->cloneBufferOnly());
1977 d.autosave_watcher_.setFuture(f);
1978 resetAutosaveTimers();
1982 void GuiView::resetAutosaveTimers()
1985 d.autosave_timeout_.restart();
1989 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1992 Buffer * buf = currentBufferView()
1993 ? ¤tBufferView()->buffer() : nullptr;
1994 Buffer * doc_buffer = documentBufferView()
1995 ? &(documentBufferView()->buffer()) : nullptr;
1998 /* In LyX/Mac, when a dialog is open, the menus of the
1999 application can still be accessed without giving focus to
2000 the main window. In this case, we want to disable the menu
2001 entries that are buffer-related.
2002 This code must not be used on Linux and Windows, since it
2003 would disable buffer-related entries when hovering over the
2004 menu (see bug #9574).
2006 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2012 // Check whether we need a buffer
2013 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2014 // no, exit directly
2015 flag.message(from_utf8(N_("Command not allowed with"
2016 "out any document open")));
2017 flag.setEnabled(false);
2021 if (cmd.origin() == FuncRequest::TOC) {
2022 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2023 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2024 flag.setEnabled(false);
2028 switch(cmd.action()) {
2029 case LFUN_BUFFER_IMPORT:
2032 case LFUN_MASTER_BUFFER_EXPORT:
2034 && (doc_buffer->parent() != nullptr
2035 || doc_buffer->hasChildren())
2036 && !d.processing_thread_watcher_.isRunning()
2037 // this launches a dialog, which would be in the wrong Buffer
2038 && !(::lyx::operator==(cmd.argument(), "custom"));
2041 case LFUN_MASTER_BUFFER_UPDATE:
2042 case LFUN_MASTER_BUFFER_VIEW:
2044 && (doc_buffer->parent() != nullptr
2045 || doc_buffer->hasChildren())
2046 && !d.processing_thread_watcher_.isRunning();
2049 case LFUN_BUFFER_UPDATE:
2050 case LFUN_BUFFER_VIEW: {
2051 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2055 string format = to_utf8(cmd.argument());
2056 if (cmd.argument().empty())
2057 format = doc_buffer->params().getDefaultOutputFormat();
2058 enable = doc_buffer->params().isExportable(format, true);
2062 case LFUN_BUFFER_RELOAD:
2063 enable = doc_buffer && !doc_buffer->isUnnamed()
2064 && doc_buffer->fileName().exists()
2065 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2068 case LFUN_BUFFER_RESET_EXPORT:
2069 enable = doc_buffer != nullptr;
2072 case LFUN_BUFFER_CHILD_OPEN:
2073 enable = doc_buffer != nullptr;
2076 case LFUN_MASTER_BUFFER_FORALL: {
2077 if (doc_buffer == nullptr) {
2078 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2082 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2083 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2084 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2089 for (Buffer * buf : doc_buffer->allRelatives()) {
2090 GuiWorkArea * wa = workArea(*buf);
2093 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2094 enable = flag.enabled();
2101 case LFUN_BUFFER_WRITE:
2102 enable = doc_buffer && (doc_buffer->isUnnamed()
2103 || (!doc_buffer->isClean()
2104 || cmd.argument() == "force"));
2107 //FIXME: This LFUN should be moved to GuiApplication.
2108 case LFUN_BUFFER_WRITE_ALL: {
2109 // We enable the command only if there are some modified buffers
2110 Buffer * first = theBufferList().first();
2115 // We cannot use a for loop as the buffer list is a cycle.
2117 if (!b->isClean()) {
2121 b = theBufferList().next(b);
2122 } while (b != first);
2126 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2127 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2130 case LFUN_BUFFER_EXPORT: {
2131 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2135 return doc_buffer->getStatus(cmd, flag);
2138 case LFUN_BUFFER_EXPORT_AS:
2139 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2144 case LFUN_BUFFER_WRITE_AS:
2145 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2146 enable = doc_buffer != nullptr;
2149 case LFUN_EXPORT_CANCEL:
2150 enable = d.processing_thread_watcher_.isRunning();
2153 case LFUN_BUFFER_CLOSE:
2154 case LFUN_VIEW_CLOSE:
2155 enable = doc_buffer != nullptr;
2158 case LFUN_BUFFER_CLOSE_ALL:
2159 enable = theBufferList().last() != theBufferList().first();
2162 case LFUN_BUFFER_CHKTEX: {
2163 // hide if we have no checktex command
2164 if (lyxrc.chktex_command.empty()) {
2165 flag.setUnknown(true);
2169 if (!doc_buffer || !doc_buffer->params().isLatex()
2170 || d.processing_thread_watcher_.isRunning()) {
2171 // grey out, don't hide
2179 case LFUN_VIEW_SPLIT:
2180 if (cmd.getArg(0) == "vertical")
2181 enable = doc_buffer && (d.splitter_->count() == 1 ||
2182 d.splitter_->orientation() == Qt::Vertical);
2184 enable = doc_buffer && (d.splitter_->count() == 1 ||
2185 d.splitter_->orientation() == Qt::Horizontal);
2188 case LFUN_TAB_GROUP_CLOSE:
2189 enable = d.tabWorkAreaCount() > 1;
2192 case LFUN_DEVEL_MODE_TOGGLE:
2193 flag.setOnOff(devel_mode_);
2196 case LFUN_TOOLBAR_SET: {
2197 string const name = cmd.getArg(0);
2198 string const state = cmd.getArg(1);
2199 if (name.empty() || state.empty()) {
2201 docstring const msg =
2202 _("Function toolbar-set requires two arguments!");
2206 if (state != "on" && state != "off" && state != "auto") {
2208 docstring const msg =
2209 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2214 if (GuiToolbar * t = toolbar(name)) {
2215 bool const autovis = t->visibility() & Toolbars::AUTO;
2217 flag.setOnOff(t->isVisible() && !autovis);
2218 else if (state == "off")
2219 flag.setOnOff(!t->isVisible() && !autovis);
2220 else if (state == "auto")
2221 flag.setOnOff(autovis);
2224 docstring const msg =
2225 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2231 case LFUN_TOOLBAR_TOGGLE: {
2232 string const name = cmd.getArg(0);
2233 if (GuiToolbar * t = toolbar(name))
2234 flag.setOnOff(t->isVisible());
2237 docstring const msg =
2238 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2244 case LFUN_TOOLBAR_MOVABLE: {
2245 string const name = cmd.getArg(0);
2246 // use negation since locked == !movable
2248 // toolbar name * locks all toolbars
2249 flag.setOnOff(!toolbarsMovable_);
2250 else if (GuiToolbar * t = toolbar(name))
2251 flag.setOnOff(!(t->isMovable()));
2254 docstring const msg =
2255 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2261 case LFUN_ICON_SIZE:
2262 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2265 case LFUN_DROP_LAYOUTS_CHOICE:
2266 enable = buf != nullptr;
2269 case LFUN_UI_TOGGLE:
2270 flag.setOnOff(isFullScreen());
2273 case LFUN_DIALOG_DISCONNECT_INSET:
2276 case LFUN_DIALOG_HIDE:
2277 // FIXME: should we check if the dialog is shown?
2280 case LFUN_DIALOG_TOGGLE:
2281 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2284 case LFUN_DIALOG_SHOW: {
2285 string const name = cmd.getArg(0);
2287 enable = name == "aboutlyx"
2288 || name == "file" //FIXME: should be removed.
2289 || name == "lyxfiles"
2291 || name == "texinfo"
2292 || name == "progress"
2293 || name == "compare";
2294 else if (name == "character" || name == "symbols"
2295 || name == "mathdelimiter" || name == "mathmatrix") {
2296 if (!buf || buf->isReadonly())
2299 Cursor const & cur = currentBufferView()->cursor();
2300 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2303 else if (name == "latexlog")
2304 enable = FileName(doc_buffer->logName()).isReadableFile();
2305 else if (name == "spellchecker")
2306 enable = theSpellChecker()
2307 && !doc_buffer->text().empty();
2308 else if (name == "vclog")
2309 enable = doc_buffer->lyxvc().inUse();
2313 case LFUN_DIALOG_UPDATE: {
2314 string const name = cmd.getArg(0);
2316 enable = name == "prefs";
2320 case LFUN_COMMAND_EXECUTE:
2322 case LFUN_MENU_OPEN:
2323 // Nothing to check.
2326 case LFUN_COMPLETION_INLINE:
2327 if (!d.current_work_area_
2328 || !d.current_work_area_->completer().inlinePossible(
2329 currentBufferView()->cursor()))
2333 case LFUN_COMPLETION_POPUP:
2334 if (!d.current_work_area_
2335 || !d.current_work_area_->completer().popupPossible(
2336 currentBufferView()->cursor()))
2341 if (!d.current_work_area_
2342 || !d.current_work_area_->completer().inlinePossible(
2343 currentBufferView()->cursor()))
2347 case LFUN_COMPLETION_ACCEPT:
2348 if (!d.current_work_area_
2349 || (!d.current_work_area_->completer().popupVisible()
2350 && !d.current_work_area_->completer().inlineVisible()
2351 && !d.current_work_area_->completer().completionAvailable()))
2355 case LFUN_COMPLETION_CANCEL:
2356 if (!d.current_work_area_
2357 || (!d.current_work_area_->completer().popupVisible()
2358 && !d.current_work_area_->completer().inlineVisible()))
2362 case LFUN_BUFFER_ZOOM_OUT:
2363 case LFUN_BUFFER_ZOOM_IN: {
2364 // only diff between these two is that the default for ZOOM_OUT
2366 bool const neg_zoom =
2367 convert<int>(cmd.argument()) < 0 ||
2368 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2369 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2370 docstring const msg =
2371 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2375 enable = doc_buffer;
2379 case LFUN_BUFFER_ZOOM: {
2380 bool const less_than_min_zoom =
2381 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2382 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2383 docstring const msg =
2384 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2389 enable = doc_buffer;
2393 case LFUN_BUFFER_MOVE_NEXT:
2394 case LFUN_BUFFER_MOVE_PREVIOUS:
2395 // we do not cycle when moving
2396 case LFUN_BUFFER_NEXT:
2397 case LFUN_BUFFER_PREVIOUS:
2398 // because we cycle, it doesn't matter whether on first or last
2399 enable = (d.currentTabWorkArea()->count() > 1);
2401 case LFUN_BUFFER_SWITCH:
2402 // toggle on the current buffer, but do not toggle off
2403 // the other ones (is that a good idea?)
2405 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2406 flag.setOnOff(true);
2409 case LFUN_VC_REGISTER:
2410 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2412 case LFUN_VC_RENAME:
2413 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2416 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2418 case LFUN_VC_CHECK_IN:
2419 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2421 case LFUN_VC_CHECK_OUT:
2422 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2424 case LFUN_VC_LOCKING_TOGGLE:
2425 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2426 && doc_buffer->lyxvc().lockingToggleEnabled();
2427 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2429 case LFUN_VC_REVERT:
2430 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2431 && !doc_buffer->hasReadonlyFlag();
2433 case LFUN_VC_UNDO_LAST:
2434 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2436 case LFUN_VC_REPO_UPDATE:
2437 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2439 case LFUN_VC_COMMAND: {
2440 if (cmd.argument().empty())
2442 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2446 case LFUN_VC_COMPARE:
2447 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2450 case LFUN_SERVER_GOTO_FILE_ROW:
2451 case LFUN_LYX_ACTIVATE:
2452 case LFUN_WINDOW_RAISE:
2454 case LFUN_FORWARD_SEARCH:
2455 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2458 case LFUN_FILE_INSERT_PLAINTEXT:
2459 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2460 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2463 case LFUN_SPELLING_CONTINUOUSLY:
2464 flag.setOnOff(lyxrc.spellcheck_continuously);
2467 case LFUN_CITATION_OPEN:
2476 flag.setEnabled(false);
2482 static FileName selectTemplateFile()
2484 FileDialog dlg(qt_("Select template file"));
2485 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2486 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2488 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2489 QStringList(qt_("LyX Documents (*.lyx)")));
2491 if (result.first == FileDialog::Later)
2493 if (result.second.isEmpty())
2495 return FileName(fromqstr(result.second));
2499 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2503 Buffer * newBuffer = nullptr;
2505 newBuffer = checkAndLoadLyXFile(filename);
2506 } catch (ExceptionMessage const &) {
2513 message(_("Document not loaded."));
2517 setBuffer(newBuffer);
2518 newBuffer->errors("Parse");
2521 theSession().lastFiles().add(filename);
2522 theSession().writeFile();
2529 void GuiView::openDocument(string const & fname)
2531 string initpath = lyxrc.document_path;
2533 if (documentBufferView()) {
2534 string const trypath = documentBufferView()->buffer().filePath();
2535 // If directory is writeable, use this as default.
2536 if (FileName(trypath).isDirWritable())
2542 if (fname.empty()) {
2543 FileDialog dlg(qt_("Select document to open"));
2544 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2545 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2547 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2548 FileDialog::Result result =
2549 dlg.open(toqstr(initpath), filter);
2551 if (result.first == FileDialog::Later)
2554 filename = fromqstr(result.second);
2556 // check selected filename
2557 if (filename.empty()) {
2558 message(_("Canceled."));
2564 // get absolute path of file and add ".lyx" to the filename if
2566 FileName const fullname =
2567 fileSearch(string(), filename, "lyx", support::may_not_exist);
2568 if (!fullname.empty())
2569 filename = fullname.absFileName();
2571 if (!fullname.onlyPath().isDirectory()) {
2572 Alert::warning(_("Invalid filename"),
2573 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2574 from_utf8(fullname.absFileName())));
2578 // if the file doesn't exist and isn't already open (bug 6645),
2579 // let the user create one
2580 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2581 !LyXVC::file_not_found_hook(fullname)) {
2582 // the user specifically chose this name. Believe him.
2583 Buffer * const b = newFile(filename, string(), true);
2589 docstring const disp_fn = makeDisplayPath(filename);
2590 message(bformat(_("Opening document %1$s..."), disp_fn));
2593 Buffer * buf = loadDocument(fullname);
2595 str2 = bformat(_("Document %1$s opened."), disp_fn);
2596 if (buf->lyxvc().inUse())
2597 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2598 " " + _("Version control detected.");
2600 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2605 // FIXME: clean that
2606 static bool import(GuiView * lv, FileName const & filename,
2607 string const & format, ErrorList & errorList)
2609 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2611 string loader_format;
2612 vector<string> loaders = theConverters().loaders();
2613 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2614 for (string const & loader : loaders) {
2615 if (!theConverters().isReachable(format, loader))
2618 string const tofile =
2619 support::changeExtension(filename.absFileName(),
2620 theFormats().extension(loader));
2621 if (theConverters().convert(nullptr, filename, FileName(tofile),
2622 filename, format, loader, errorList) != Converters::SUCCESS)
2624 loader_format = loader;
2627 if (loader_format.empty()) {
2628 frontend::Alert::error(_("Couldn't import file"),
2629 bformat(_("No information for importing the format %1$s."),
2630 translateIfPossible(theFormats().prettyName(format))));
2634 loader_format = format;
2636 if (loader_format == "lyx") {
2637 Buffer * buf = lv->loadDocument(lyxfile);
2641 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2645 bool as_paragraphs = loader_format == "textparagraph";
2646 string filename2 = (loader_format == format) ? filename.absFileName()
2647 : support::changeExtension(filename.absFileName(),
2648 theFormats().extension(loader_format));
2649 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2651 guiApp->setCurrentView(lv);
2652 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2659 void GuiView::importDocument(string const & argument)
2662 string filename = split(argument, format, ' ');
2664 LYXERR(Debug::INFO, format << " file: " << filename);
2666 // need user interaction
2667 if (filename.empty()) {
2668 string initpath = lyxrc.document_path;
2669 if (documentBufferView()) {
2670 string const trypath = documentBufferView()->buffer().filePath();
2671 // If directory is writeable, use this as default.
2672 if (FileName(trypath).isDirWritable())
2676 docstring const text = bformat(_("Select %1$s file to import"),
2677 translateIfPossible(theFormats().prettyName(format)));
2679 FileDialog dlg(toqstr(text));
2680 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2681 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2683 docstring filter = translateIfPossible(theFormats().prettyName(format));
2686 filter += from_utf8(theFormats().extensions(format));
2689 FileDialog::Result result =
2690 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2692 if (result.first == FileDialog::Later)
2695 filename = fromqstr(result.second);
2697 // check selected filename
2698 if (filename.empty())
2699 message(_("Canceled."));
2702 if (filename.empty())
2705 // get absolute path of file
2706 FileName const fullname(support::makeAbsPath(filename));
2708 // Can happen if the user entered a path into the dialog
2710 if (fullname.onlyFileName().empty()) {
2711 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2712 "Aborting import."),
2713 from_utf8(fullname.absFileName()));
2714 frontend::Alert::error(_("File name error"), msg);
2715 message(_("Canceled."));
2720 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2722 // Check if the document already is open
2723 Buffer * buf = theBufferList().getBuffer(lyxfile);
2726 if (!closeBuffer()) {
2727 message(_("Canceled."));
2732 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2734 // if the file exists already, and we didn't do
2735 // -i lyx thefile.lyx, warn
2736 if (lyxfile.exists() && fullname != lyxfile) {
2738 docstring text = bformat(_("The document %1$s already exists.\n\n"
2739 "Do you want to overwrite that document?"), displaypath);
2740 int const ret = Alert::prompt(_("Overwrite document?"),
2741 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2744 message(_("Canceled."));
2749 message(bformat(_("Importing %1$s..."), displaypath));
2750 ErrorList errorList;
2751 if (import(this, fullname, format, errorList))
2752 message(_("imported."));
2754 message(_("file not imported!"));
2756 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2760 void GuiView::newDocument(string const & filename, string templatefile,
2763 FileName initpath(lyxrc.document_path);
2764 if (documentBufferView()) {
2765 FileName const trypath(documentBufferView()->buffer().filePath());
2766 // If directory is writeable, use this as default.
2767 if (trypath.isDirWritable())
2771 if (from_template) {
2772 if (templatefile.empty())
2773 templatefile = selectTemplateFile().absFileName();
2774 if (templatefile.empty())
2779 if (filename.empty())
2780 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2782 b = newFile(filename, templatefile, true);
2787 // If no new document could be created, it is unsure
2788 // whether there is a valid BufferView.
2789 if (currentBufferView())
2790 // Ensure the cursor is correctly positioned on screen.
2791 currentBufferView()->showCursor();
2795 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2797 BufferView * bv = documentBufferView();
2802 FileName filename(to_utf8(fname));
2803 if (filename.empty()) {
2804 // Launch a file browser
2806 string initpath = lyxrc.document_path;
2807 string const trypath = bv->buffer().filePath();
2808 // If directory is writeable, use this as default.
2809 if (FileName(trypath).isDirWritable())
2813 FileDialog dlg(qt_("Select LyX document to insert"));
2814 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2815 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2817 FileDialog::Result result = dlg.open(toqstr(initpath),
2818 QStringList(qt_("LyX Documents (*.lyx)")));
2820 if (result.first == FileDialog::Later)
2824 filename.set(fromqstr(result.second));
2826 // check selected filename
2827 if (filename.empty()) {
2828 // emit message signal.
2829 message(_("Canceled."));
2834 bv->insertLyXFile(filename, ignorelang);
2835 bv->buffer().errors("Parse");
2840 string const GuiView::getTemplatesPath(Buffer & b)
2842 // We start off with the user's templates path
2843 string result = addPath(package().user_support().absFileName(), "templates");
2844 // Check for the document language
2845 string const langcode = b.params().language->code();
2846 string const shortcode = langcode.substr(0, 2);
2847 if (!langcode.empty() && shortcode != "en") {
2848 string subpath = addPath(result, shortcode);
2849 string subpath_long = addPath(result, langcode);
2850 // If we have a subdirectory for the language already,
2852 FileName sp = FileName(subpath);
2853 if (sp.isDirectory())
2855 else if (FileName(subpath_long).isDirectory())
2856 result = subpath_long;
2858 // Ask whether we should create such a subdirectory
2859 docstring const text =
2860 bformat(_("It is suggested to save the template in a subdirectory\n"
2861 "appropriate to the document language (%1$s).\n"
2862 "This subdirectory does not exists yet.\n"
2863 "Do you want to create it?"),
2864 _(b.params().language->display()));
2865 if (Alert::prompt(_("Create Language Directory?"),
2866 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2867 // If the user agreed, we try to create it and report if this failed.
2868 if (!sp.createDirectory(0777))
2869 Alert::error(_("Subdirectory creation failed!"),
2870 _("Could not create subdirectory.\n"
2871 "The template will be saved in the parent directory."));
2877 // Do we have a layout category?
2878 string const cat = b.params().baseClass() ?
2879 b.params().baseClass()->category()
2882 string subpath = addPath(result, cat);
2883 // If we have a subdirectory for the category already,
2885 FileName sp = FileName(subpath);
2886 if (sp.isDirectory())
2889 // Ask whether we should create such a subdirectory
2890 docstring const text =
2891 bformat(_("It is suggested to save the template in a subdirectory\n"
2892 "appropriate to the layout category (%1$s).\n"
2893 "This subdirectory does not exists yet.\n"
2894 "Do you want to create it?"),
2896 if (Alert::prompt(_("Create Category Directory?"),
2897 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2898 // If the user agreed, we try to create it and report if this failed.
2899 if (!sp.createDirectory(0777))
2900 Alert::error(_("Subdirectory creation failed!"),
2901 _("Could not create subdirectory.\n"
2902 "The template will be saved in the parent directory."));
2912 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2914 FileName fname = b.fileName();
2915 FileName const oldname = fname;
2916 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2918 if (!newname.empty()) {
2921 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2923 fname = support::makeAbsPath(to_utf8(newname),
2924 oldname.onlyPath().absFileName());
2926 // Switch to this Buffer.
2929 // No argument? Ask user through dialog.
2931 QString const title = as_template ? qt_("Choose a filename to save template as")
2932 : qt_("Choose a filename to save document as");
2933 FileDialog dlg(title);
2934 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2935 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2937 if (!isLyXFileName(fname.absFileName()))
2938 fname.changeExtension(".lyx");
2940 string const path = as_template ?
2942 : fname.onlyPath().absFileName();
2943 FileDialog::Result result =
2944 dlg.save(toqstr(path),
2945 QStringList(qt_("LyX Documents (*.lyx)")),
2946 toqstr(fname.onlyFileName()));
2948 if (result.first == FileDialog::Later)
2951 fname.set(fromqstr(result.second));
2956 if (!isLyXFileName(fname.absFileName()))
2957 fname.changeExtension(".lyx");
2960 // fname is now the new Buffer location.
2962 // if there is already a Buffer open with this name, we do not want
2963 // to have another one. (the second test makes sure we're not just
2964 // trying to overwrite ourselves, which is fine.)
2965 if (theBufferList().exists(fname) && fname != oldname
2966 && theBufferList().getBuffer(fname) != &b) {
2967 docstring const text =
2968 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2969 "Please close it before attempting to overwrite it.\n"
2970 "Do you want to choose a new filename?"),
2971 from_utf8(fname.absFileName()));
2972 int const ret = Alert::prompt(_("Chosen File Already Open"),
2973 text, 0, 1, _("&Rename"), _("&Cancel"));
2975 case 0: return renameBuffer(b, docstring(), kind);
2976 case 1: return false;
2981 bool const existsLocal = fname.exists();
2982 bool const existsInVC = LyXVC::fileInVC(fname);
2983 if (existsLocal || existsInVC) {
2984 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2985 if (kind != LV_WRITE_AS && existsInVC) {
2986 // renaming to a name that is already in VC
2988 docstring text = bformat(_("The document %1$s "
2989 "is already registered.\n\n"
2990 "Do you want to choose a new name?"),
2992 docstring const title = (kind == LV_VC_RENAME) ?
2993 _("Rename document?") : _("Copy document?");
2994 docstring const button = (kind == LV_VC_RENAME) ?
2995 _("&Rename") : _("&Copy");
2996 int const ret = Alert::prompt(title, text, 0, 1,
2997 button, _("&Cancel"));
2999 case 0: return renameBuffer(b, docstring(), kind);
3000 case 1: return false;
3005 docstring text = bformat(_("The document %1$s "
3006 "already exists.\n\n"
3007 "Do you want to overwrite that document?"),
3009 int const ret = Alert::prompt(_("Overwrite document?"),
3010 text, 0, 2, _("&Overwrite"),
3011 _("&Rename"), _("&Cancel"));
3014 case 1: return renameBuffer(b, docstring(), kind);
3015 case 2: return false;
3021 case LV_VC_RENAME: {
3022 string msg = b.lyxvc().rename(fname);
3025 message(from_utf8(msg));
3029 string msg = b.lyxvc().copy(fname);
3032 message(from_utf8(msg));
3036 case LV_WRITE_AS_TEMPLATE:
3039 // LyXVC created the file already in case of LV_VC_RENAME or
3040 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3041 // relative paths of included stuff right if we moved e.g. from
3042 // /a/b.lyx to /a/c/b.lyx.
3044 bool const saved = saveBuffer(b, fname);
3051 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3053 FileName fname = b.fileName();
3055 FileDialog dlg(qt_("Choose a filename to export the document as"));
3056 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3059 QString const anyformat = qt_("Guess from extension (*.*)");
3062 vector<Format const *> export_formats;
3063 for (Format const & f : theFormats())
3064 if (f.documentFormat())
3065 export_formats.push_back(&f);
3066 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3067 map<QString, string> fmap;
3070 for (Format const * f : export_formats) {
3071 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3072 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3074 from_ascii(f->extension())));
3075 types << loc_filter;
3076 fmap[loc_filter] = f->name();
3077 if (from_ascii(f->name()) == iformat) {
3078 filter = loc_filter;
3079 ext = f->extension();
3082 string ofname = fname.onlyFileName();
3084 ofname = support::changeExtension(ofname, ext);
3085 FileDialog::Result result =
3086 dlg.save(toqstr(fname.onlyPath().absFileName()),
3090 if (result.first != FileDialog::Chosen)
3094 fname.set(fromqstr(result.second));
3095 if (filter == anyformat)
3096 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3098 fmt_name = fmap[filter];
3099 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3100 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3102 if (fmt_name.empty() || fname.empty())
3105 // fname is now the new Buffer location.
3106 if (FileName(fname).exists()) {
3107 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3108 docstring text = bformat(_("The document %1$s already "
3109 "exists.\n\nDo you want to "
3110 "overwrite that document?"),
3112 int const ret = Alert::prompt(_("Overwrite document?"),
3113 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3116 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3117 case 2: return false;
3121 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3124 return dr.dispatched();
3128 bool GuiView::saveBuffer(Buffer & b)
3130 return saveBuffer(b, FileName());
3134 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3136 if (workArea(b) && workArea(b)->inDialogMode())
3139 if (fn.empty() && b.isUnnamed())
3140 return renameBuffer(b, docstring());
3142 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3144 theSession().lastFiles().add(b.fileName());
3145 theSession().writeFile();
3149 // Switch to this Buffer.
3152 // FIXME: we don't tell the user *WHY* the save failed !!
3153 docstring const file = makeDisplayPath(b.absFileName(), 30);
3154 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3155 "Do you want to rename the document and "
3156 "try again?"), file);
3157 int const ret = Alert::prompt(_("Rename and save?"),
3158 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3161 if (!renameBuffer(b, docstring()))
3170 return saveBuffer(b, fn);
3174 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3176 return closeWorkArea(wa, false);
3180 // We only want to close the buffer if it is not visible in other workareas
3181 // of the same view, nor in other views, and if this is not a child
3182 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3184 Buffer & buf = wa->bufferView().buffer();
3186 bool last_wa = d.countWorkAreasOf(buf) == 1
3187 && !inOtherView(buf) && !buf.parent();
3189 bool close_buffer = last_wa;
3192 if (lyxrc.close_buffer_with_last_view == "yes")
3194 else if (lyxrc.close_buffer_with_last_view == "no")
3195 close_buffer = false;
3198 if (buf.isUnnamed())
3199 file = from_utf8(buf.fileName().onlyFileName());
3201 file = buf.fileName().displayName(30);
3202 docstring const text = bformat(
3203 _("Last view on document %1$s is being closed.\n"
3204 "Would you like to close or hide the document?\n"
3206 "Hidden documents can be displayed back through\n"
3207 "the menu: View->Hidden->...\n"
3209 "To remove this question, set your preference in:\n"
3210 " Tools->Preferences->Look&Feel->UserInterface\n"
3212 int ret = Alert::prompt(_("Close or hide document?"),
3213 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3216 close_buffer = (ret == 0);
3220 return closeWorkArea(wa, close_buffer);
3224 bool GuiView::closeBuffer()
3226 GuiWorkArea * wa = currentMainWorkArea();
3227 // coverity complained about this
3228 // it seems unnecessary, but perhaps is worth the check
3229 LASSERT(wa, return false);
3231 setCurrentWorkArea(wa);
3232 Buffer & buf = wa->bufferView().buffer();
3233 return closeWorkArea(wa, !buf.parent());
3237 void GuiView::writeSession() const {
3238 GuiWorkArea const * active_wa = currentMainWorkArea();
3239 for (int i = 0; i < d.splitter_->count(); ++i) {
3240 TabWorkArea * twa = d.tabWorkArea(i);
3241 for (int j = 0; j < twa->count(); ++j) {
3242 GuiWorkArea * wa = twa->workArea(j);
3243 Buffer & buf = wa->bufferView().buffer();
3244 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3250 bool GuiView::closeBufferAll()
3253 for (auto & buf : theBufferList()) {
3254 if (!saveBufferIfNeeded(*buf, false)) {
3255 // Closing has been cancelled, so abort.
3260 // Close the workareas in all other views
3261 QList<int> const ids = guiApp->viewIds();
3262 for (int i = 0; i != ids.size(); ++i) {
3263 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3267 // Close our own workareas
3268 if (!closeWorkAreaAll())
3275 bool GuiView::closeWorkAreaAll()
3277 setCurrentWorkArea(currentMainWorkArea());
3279 // We might be in a situation that there is still a tabWorkArea, but
3280 // there are no tabs anymore. This can happen when we get here after a
3281 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3282 // many TabWorkArea's have no documents anymore.
3285 // We have to call count() each time, because it can happen that
3286 // more than one splitter will disappear in one iteration (bug 5998).
3287 while (d.splitter_->count() > empty_twa) {
3288 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3290 if (twa->count() == 0)
3293 setCurrentWorkArea(twa->currentWorkArea());
3294 if (!closeTabWorkArea(twa))
3302 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3307 Buffer & buf = wa->bufferView().buffer();
3309 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3310 Alert::warning(_("Close document"),
3311 _("Document could not be closed because it is being processed by LyX."));
3316 return closeBuffer(buf);
3318 if (!inMultiTabs(wa))
3319 if (!saveBufferIfNeeded(buf, true))
3327 bool GuiView::closeBuffer(Buffer & buf)
3329 bool success = true;
3330 for (Buffer * child_buf : buf.getChildren()) {
3331 if (theBufferList().isOthersChild(&buf, child_buf)) {
3332 child_buf->setParent(nullptr);
3336 // FIXME: should we look in other tabworkareas?
3337 // ANSWER: I don't think so. I've tested, and if the child is
3338 // open in some other window, it closes without a problem.
3339 GuiWorkArea * child_wa = workArea(*child_buf);
3342 // If we are in a close_event all children will be closed in some time,
3343 // so no need to do it here. This will ensure that the children end up
3344 // in the session file in the correct order. If we close the master
3345 // buffer, we can close or release the child buffers here too.
3347 success = closeWorkArea(child_wa, true);
3351 // In this case the child buffer is open but hidden.
3352 // Even in this case, children can be dirty (e.g.,
3353 // after a label change in the master, see #11405).
3354 // Therefore, check this
3355 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3356 // If we are in a close_event all children will be closed in some time,
3357 // so no need to do it here. This will ensure that the children end up
3358 // in the session file in the correct order. If we close the master
3359 // buffer, we can close or release the child buffers here too.
3362 // Save dirty buffers also if closing_!
3363 if (saveBufferIfNeeded(*child_buf, false)) {
3364 child_buf->removeAutosaveFile();
3365 theBufferList().release(child_buf);
3367 // Saving of dirty children has been cancelled.
3368 // Cancel the whole process.
3375 // goto bookmark to update bookmark pit.
3376 // FIXME: we should update only the bookmarks related to this buffer!
3377 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3378 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3379 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3380 guiApp->gotoBookmark(i, false, false);
3382 if (saveBufferIfNeeded(buf, false)) {
3383 buf.removeAutosaveFile();
3384 theBufferList().release(&buf);
3388 // open all children again to avoid a crash because of dangling
3389 // pointers (bug 6603)
3395 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3397 while (twa == d.currentTabWorkArea()) {
3398 twa->setCurrentIndex(twa->count() - 1);
3400 GuiWorkArea * wa = twa->currentWorkArea();
3401 Buffer & b = wa->bufferView().buffer();
3403 // We only want to close the buffer if the same buffer is not visible
3404 // in another view, and if this is not a child and if we are closing
3405 // a view (not a tabgroup).
3406 bool const close_buffer =
3407 !inOtherView(b) && !b.parent() && closing_;
3409 if (!closeWorkArea(wa, close_buffer))
3416 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3418 if (buf.isClean() || buf.paragraphs().empty())
3421 // Switch to this Buffer.
3427 if (buf.isUnnamed()) {
3428 file = from_utf8(buf.fileName().onlyFileName());
3431 FileName filename = buf.fileName();
3433 file = filename.displayName(30);
3434 exists = filename.exists();
3437 // Bring this window to top before asking questions.
3442 if (hiding && buf.isUnnamed()) {
3443 docstring const text = bformat(_("The document %1$s has not been "
3444 "saved yet.\n\nDo you want to save "
3445 "the document?"), file);
3446 ret = Alert::prompt(_("Save new document?"),
3447 text, 0, 1, _("&Save"), _("&Cancel"));
3451 docstring const text = exists ?
3452 bformat(_("The document %1$s has unsaved changes."
3453 "\n\nDo you want to save the document or "
3454 "discard the changes?"), file) :
3455 bformat(_("The document %1$s has not been saved yet."
3456 "\n\nDo you want to save the document or "
3457 "discard it entirely?"), file);
3458 docstring const title = exists ?
3459 _("Save changed document?") : _("Save document?");
3460 ret = Alert::prompt(title, text, 0, 2,
3461 _("&Save"), _("&Discard"), _("&Cancel"));
3466 if (!saveBuffer(buf))
3470 // If we crash after this we could have no autosave file
3471 // but I guess this is really improbable (Jug).
3472 // Sometimes improbable things happen:
3473 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3474 // buf.removeAutosaveFile();
3476 // revert all changes
3487 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3489 Buffer & buf = wa->bufferView().buffer();
3491 for (int i = 0; i != d.splitter_->count(); ++i) {
3492 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3493 if (wa_ && wa_ != wa)
3496 return inOtherView(buf);
3500 bool GuiView::inOtherView(Buffer & buf)
3502 QList<int> const ids = guiApp->viewIds();
3504 for (int i = 0; i != ids.size(); ++i) {
3508 if (guiApp->view(ids[i]).workArea(buf))
3515 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3517 if (!documentBufferView())
3520 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3521 Buffer * const curbuf = &documentBufferView()->buffer();
3522 int nwa = twa->count();
3523 for (int i = 0; i < nwa; ++i) {
3524 if (&workArea(i)->bufferView().buffer() == curbuf) {
3526 if (np == NEXTBUFFER)
3527 next_index = (i == nwa - 1 ? 0 : i + 1);
3529 next_index = (i == 0 ? nwa - 1 : i - 1);
3531 twa->moveTab(i, next_index);
3533 setBuffer(&workArea(next_index)->bufferView().buffer());
3541 /// make sure the document is saved
3542 static bool ensureBufferClean(Buffer * buffer)
3544 LASSERT(buffer, return false);
3545 if (buffer->isClean() && !buffer->isUnnamed())
3548 docstring const file = buffer->fileName().displayName(30);
3551 if (!buffer->isUnnamed()) {
3552 text = bformat(_("The document %1$s has unsaved "
3553 "changes.\n\nDo you want to save "
3554 "the document?"), file);
3555 title = _("Save changed document?");
3558 text = bformat(_("The document %1$s has not been "
3559 "saved yet.\n\nDo you want to save "
3560 "the document?"), file);
3561 title = _("Save new document?");
3563 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3566 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3568 return buffer->isClean() && !buffer->isUnnamed();
3572 bool GuiView::reloadBuffer(Buffer & buf)
3574 currentBufferView()->cursor().reset();
3575 Buffer::ReadStatus status = buf.reload();
3576 return status == Buffer::ReadSuccess;
3580 void GuiView::checkExternallyModifiedBuffers()
3582 for (Buffer * buf : theBufferList()) {
3583 if (buf->fileName().exists() && buf->isChecksumModified()) {
3584 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3585 " Reload now? Any local changes will be lost."),
3586 from_utf8(buf->absFileName()));
3587 int const ret = Alert::prompt(_("Reload externally changed document?"),
3588 text, 0, 1, _("&Reload"), _("&Cancel"));
3596 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3598 Buffer * buffer = documentBufferView()
3599 ? &(documentBufferView()->buffer()) : nullptr;
3601 switch (cmd.action()) {
3602 case LFUN_VC_REGISTER:
3603 if (!buffer || !ensureBufferClean(buffer))
3605 if (!buffer->lyxvc().inUse()) {
3606 if (buffer->lyxvc().registrer()) {
3607 reloadBuffer(*buffer);
3608 dr.clearMessageUpdate();
3613 case LFUN_VC_RENAME:
3614 case LFUN_VC_COPY: {
3615 if (!buffer || !ensureBufferClean(buffer))
3617 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3618 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3619 // Some changes are not yet committed.
3620 // We test here and not in getStatus(), since
3621 // this test is expensive.
3623 LyXVC::CommandResult ret =
3624 buffer->lyxvc().checkIn(log);
3626 if (ret == LyXVC::ErrorCommand ||
3627 ret == LyXVC::VCSuccess)
3628 reloadBuffer(*buffer);
3629 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3630 frontend::Alert::error(
3631 _("Revision control error."),
3632 _("Document could not be checked in."));
3636 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3637 LV_VC_RENAME : LV_VC_COPY;
3638 renameBuffer(*buffer, cmd.argument(), kind);
3643 case LFUN_VC_CHECK_IN:
3644 if (!buffer || !ensureBufferClean(buffer))
3646 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3648 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3650 // Only skip reloading if the checkin was cancelled or
3651 // an error occurred before the real checkin VCS command
3652 // was executed, since the VCS might have changed the
3653 // file even if it could not checkin successfully.
3654 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3655 reloadBuffer(*buffer);
3659 case LFUN_VC_CHECK_OUT:
3660 if (!buffer || !ensureBufferClean(buffer))
3662 if (buffer->lyxvc().inUse()) {
3663 dr.setMessage(buffer->lyxvc().checkOut());
3664 reloadBuffer(*buffer);
3668 case LFUN_VC_LOCKING_TOGGLE:
3669 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3671 if (buffer->lyxvc().inUse()) {
3672 string res = buffer->lyxvc().lockingToggle();
3674 frontend::Alert::error(_("Revision control error."),
3675 _("Error when setting the locking property."));
3678 reloadBuffer(*buffer);
3683 case LFUN_VC_REVERT:
3686 if (buffer->lyxvc().revert()) {
3687 reloadBuffer(*buffer);
3688 dr.clearMessageUpdate();
3692 case LFUN_VC_UNDO_LAST:
3695 buffer->lyxvc().undoLast();
3696 reloadBuffer(*buffer);
3697 dr.clearMessageUpdate();
3700 case LFUN_VC_REPO_UPDATE:
3703 if (ensureBufferClean(buffer)) {
3704 dr.setMessage(buffer->lyxvc().repoUpdate());
3705 checkExternallyModifiedBuffers();
3709 case LFUN_VC_COMMAND: {
3710 string flag = cmd.getArg(0);
3711 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3714 if (contains(flag, 'M')) {
3715 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3718 string path = cmd.getArg(1);
3719 if (contains(path, "$$p") && buffer)
3720 path = subst(path, "$$p", buffer->filePath());
3721 LYXERR(Debug::LYXVC, "Directory: " << path);
3723 if (!pp.isReadableDirectory()) {
3724 lyxerr << _("Directory is not accessible.") << endl;
3727 support::PathChanger p(pp);
3729 string command = cmd.getArg(2);
3730 if (command.empty())
3733 command = subst(command, "$$i", buffer->absFileName());
3734 command = subst(command, "$$p", buffer->filePath());
3736 command = subst(command, "$$m", to_utf8(message));
3737 LYXERR(Debug::LYXVC, "Command: " << command);
3739 one.startscript(Systemcall::Wait, command);
3743 if (contains(flag, 'I'))
3744 buffer->markDirty();
3745 if (contains(flag, 'R'))
3746 reloadBuffer(*buffer);
3751 case LFUN_VC_COMPARE: {
3752 if (cmd.argument().empty()) {
3753 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3759 string rev1 = cmd.getArg(0);
3763 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3766 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3767 f2 = buffer->absFileName();
3769 string rev2 = cmd.getArg(1);
3773 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3777 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3778 f1 << "\n" << f2 << "\n" );
3779 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3780 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3790 void GuiView::openChildDocument(string const & fname)
3792 LASSERT(documentBufferView(), return);
3793 Buffer & buffer = documentBufferView()->buffer();
3794 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3795 documentBufferView()->saveBookmark(false);
3796 Buffer * child = nullptr;
3797 if (theBufferList().exists(filename)) {
3798 child = theBufferList().getBuffer(filename);
3801 message(bformat(_("Opening child document %1$s..."),
3802 makeDisplayPath(filename.absFileName())));
3803 child = loadDocument(filename, false);
3805 // Set the parent name of the child document.
3806 // This makes insertion of citations and references in the child work,
3807 // when the target is in the parent or another child document.
3809 child->setParent(&buffer);
3813 bool GuiView::goToFileRow(string const & argument)
3817 size_t i = argument.find_last_of(' ');
3818 if (i != string::npos) {
3819 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3820 istringstream is(argument.substr(i + 1));
3825 if (i == string::npos) {
3826 LYXERR0("Wrong argument: " << argument);
3829 Buffer * buf = nullptr;
3830 string const realtmp = package().temp_dir().realPath();
3831 // We have to use os::path_prefix_is() here, instead of
3832 // simply prefixIs(), because the file name comes from
3833 // an external application and may need case adjustment.
3834 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3835 buf = theBufferList().getBufferFromTmp(file_name, true);
3836 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3837 << (buf ? " success" : " failed"));
3839 // Must replace extension of the file to be .lyx
3840 // and get full path
3841 FileName const s = fileSearch(string(),
3842 support::changeExtension(file_name, ".lyx"), "lyx");
3843 // Either change buffer or load the file
3844 if (theBufferList().exists(s))
3845 buf = theBufferList().getBuffer(s);
3846 else if (s.exists()) {
3847 buf = loadDocument(s);
3852 _("File does not exist: %1$s"),
3853 makeDisplayPath(file_name)));
3859 _("No buffer for file: %1$s."),
3860 makeDisplayPath(file_name))
3865 bool success = documentBufferView()->setCursorFromRow(row);
3867 LYXERR(Debug::LATEX,
3868 "setCursorFromRow: invalid position for row " << row);
3869 frontend::Alert::error(_("Inverse Search Failed"),
3870 _("Invalid position requested by inverse search.\n"
3871 "You may need to update the viewed document."));
3877 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3879 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3880 menu->exec(QCursor::pos());
3885 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3886 Buffer const * orig, Buffer * clone, string const & format)
3888 Buffer::ExportStatus const status = func(format);
3890 // the cloning operation will have produced a clone of the entire set of
3891 // documents, starting from the master. so we must delete those.
3892 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3894 busyBuffers.remove(orig);
3899 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3900 Buffer const * orig, Buffer * clone, string const & format)
3902 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3904 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3908 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3909 Buffer const * orig, Buffer * clone, string const & format)
3911 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3913 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3917 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3918 Buffer const * orig, Buffer * clone, string const & format)
3920 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3922 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3926 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3927 Buffer const * used_buffer,
3928 docstring const & msg,
3929 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3930 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3931 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3932 bool allow_async, bool use_tmpdir)
3937 string format = argument;
3939 format = used_buffer->params().getDefaultOutputFormat();
3940 processing_format = format;
3942 progress_->clearMessages();
3945 #if EXPORT_in_THREAD
3947 GuiViewPrivate::busyBuffers.insert(used_buffer);
3948 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3949 if (!cloned_buffer) {
3950 Alert::error(_("Export Error"),
3951 _("Error cloning the Buffer."));
3954 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3959 setPreviewFuture(f);
3960 last_export_format = used_buffer->params().bufferFormat();
3963 // We are asynchronous, so we don't know here anything about the success
3966 Buffer::ExportStatus status;
3968 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3969 } else if (previewFunc) {
3970 status = (used_buffer->*previewFunc)(format);
3973 handleExportStatus(gv_, status, format);
3975 return (status == Buffer::ExportSuccess
3976 || status == Buffer::PreviewSuccess);
3980 Buffer::ExportStatus status;
3982 status = (used_buffer->*syncFunc)(format, true);
3983 } else if (previewFunc) {
3984 status = (used_buffer->*previewFunc)(format);
3987 handleExportStatus(gv_, status, format);
3989 return (status == Buffer::ExportSuccess
3990 || status == Buffer::PreviewSuccess);
3994 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3996 BufferView * bv = currentBufferView();
3997 LASSERT(bv, return);
3999 // Let the current BufferView dispatch its own actions.
4000 bv->dispatch(cmd, dr);
4001 if (dr.dispatched()) {
4002 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4003 updateDialog("document", "");
4007 // Try with the document BufferView dispatch if any.
4008 BufferView * doc_bv = documentBufferView();
4009 if (doc_bv && doc_bv != bv) {
4010 doc_bv->dispatch(cmd, dr);
4011 if (dr.dispatched()) {
4012 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4013 updateDialog("document", "");
4018 // Then let the current Cursor dispatch its own actions.
4019 bv->cursor().dispatch(cmd);
4021 // update completion. We do it here and not in
4022 // processKeySym to avoid another redraw just for a
4023 // changed inline completion
4024 if (cmd.origin() == FuncRequest::KEYBOARD) {
4025 if (cmd.action() == LFUN_SELF_INSERT
4026 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4027 updateCompletion(bv->cursor(), true, true);
4028 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4029 updateCompletion(bv->cursor(), false, true);
4031 updateCompletion(bv->cursor(), false, false);
4034 dr = bv->cursor().result();
4038 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4040 BufferView * bv = currentBufferView();
4041 // By default we won't need any update.
4042 dr.screenUpdate(Update::None);
4043 // assume cmd will be dispatched
4044 dr.dispatched(true);
4046 Buffer * doc_buffer = documentBufferView()
4047 ? &(documentBufferView()->buffer()) : nullptr;
4049 if (cmd.origin() == FuncRequest::TOC) {
4050 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4051 toc->doDispatch(bv->cursor(), cmd, dr);
4055 string const argument = to_utf8(cmd.argument());
4057 switch(cmd.action()) {
4058 case LFUN_BUFFER_CHILD_OPEN:
4059 openChildDocument(to_utf8(cmd.argument()));
4062 case LFUN_BUFFER_IMPORT:
4063 importDocument(to_utf8(cmd.argument()));
4066 case LFUN_MASTER_BUFFER_EXPORT:
4068 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4070 case LFUN_BUFFER_EXPORT: {
4073 // GCC only sees strfwd.h when building merged
4074 if (::lyx::operator==(cmd.argument(), "custom")) {
4075 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4076 // so the following test should not be needed.
4077 // In principle, we could try to switch to such a view...
4078 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4079 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4083 string const dest = cmd.getArg(1);
4084 FileName target_dir;
4085 if (!dest.empty() && FileName::isAbsolute(dest))
4086 target_dir = FileName(support::onlyPath(dest));
4088 target_dir = doc_buffer->fileName().onlyPath();
4090 string const format = (argument.empty() || argument == "default") ?
4091 doc_buffer->params().getDefaultOutputFormat() : argument;
4093 if ((dest.empty() && doc_buffer->isUnnamed())
4094 || !target_dir.isDirWritable()) {
4095 exportBufferAs(*doc_buffer, from_utf8(format));
4098 /* TODO/Review: Is it a problem to also export the children?
4099 See the update_unincluded flag */
4100 d.asyncBufferProcessing(format,
4103 &GuiViewPrivate::exportAndDestroy,
4105 nullptr, cmd.allowAsync());
4106 // TODO Inform user about success
4110 case LFUN_BUFFER_EXPORT_AS: {
4111 LASSERT(doc_buffer, break);
4112 docstring f = cmd.argument();
4114 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4115 exportBufferAs(*doc_buffer, f);
4119 case LFUN_BUFFER_UPDATE: {
4120 d.asyncBufferProcessing(argument,
4123 &GuiViewPrivate::compileAndDestroy,
4125 nullptr, cmd.allowAsync(), true);
4128 case LFUN_BUFFER_VIEW: {
4129 d.asyncBufferProcessing(argument,
4131 _("Previewing ..."),
4132 &GuiViewPrivate::previewAndDestroy,
4134 &Buffer::preview, cmd.allowAsync());
4137 case LFUN_MASTER_BUFFER_UPDATE: {
4138 d.asyncBufferProcessing(argument,
4139 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4141 &GuiViewPrivate::compileAndDestroy,
4143 nullptr, cmd.allowAsync(), true);
4146 case LFUN_MASTER_BUFFER_VIEW: {
4147 d.asyncBufferProcessing(argument,
4148 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4150 &GuiViewPrivate::previewAndDestroy,
4151 nullptr, &Buffer::preview, cmd.allowAsync());
4154 case LFUN_EXPORT_CANCEL: {
4155 Systemcall::killscript();
4158 case LFUN_BUFFER_SWITCH: {
4159 string const file_name = to_utf8(cmd.argument());
4160 if (!FileName::isAbsolute(file_name)) {
4162 dr.setMessage(_("Absolute filename expected."));
4166 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4169 dr.setMessage(_("Document not loaded"));
4173 // Do we open or switch to the buffer in this view ?
4174 if (workArea(*buffer)
4175 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4180 // Look for the buffer in other views
4181 QList<int> const ids = guiApp->viewIds();
4183 for (; i != ids.size(); ++i) {
4184 GuiView & gv = guiApp->view(ids[i]);
4185 if (gv.workArea(*buffer)) {
4187 gv.activateWindow();
4189 gv.setBuffer(buffer);
4194 // If necessary, open a new window as a last resort
4195 if (i == ids.size()) {
4196 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4202 case LFUN_BUFFER_NEXT:
4203 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4206 case LFUN_BUFFER_MOVE_NEXT:
4207 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4210 case LFUN_BUFFER_PREVIOUS:
4211 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4214 case LFUN_BUFFER_MOVE_PREVIOUS:
4215 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4218 case LFUN_BUFFER_CHKTEX:
4219 LASSERT(doc_buffer, break);
4220 doc_buffer->runChktex();
4223 case LFUN_COMMAND_EXECUTE: {
4224 command_execute_ = true;
4225 minibuffer_focus_ = true;
4228 case LFUN_DROP_LAYOUTS_CHOICE:
4229 d.layout_->showPopup();
4232 case LFUN_MENU_OPEN:
4233 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4234 menu->exec(QCursor::pos());
4237 case LFUN_FILE_INSERT: {
4238 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4239 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4240 dr.forceBufferUpdate();
4241 dr.screenUpdate(Update::Force);
4246 case LFUN_FILE_INSERT_PLAINTEXT:
4247 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4248 string const fname = to_utf8(cmd.argument());
4249 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4250 dr.setMessage(_("Absolute filename expected."));
4254 FileName filename(fname);
4255 if (fname.empty()) {
4256 FileDialog dlg(qt_("Select file to insert"));
4258 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4259 QStringList(qt_("All Files (*)")));
4261 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4262 dr.setMessage(_("Canceled."));
4266 filename.set(fromqstr(result.second));
4270 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4271 bv->dispatch(new_cmd, dr);
4276 case LFUN_BUFFER_RELOAD: {
4277 LASSERT(doc_buffer, break);
4280 bool drop = (cmd.argument() == "dump");
4283 if (!drop && !doc_buffer->isClean()) {
4284 docstring const file =
4285 makeDisplayPath(doc_buffer->absFileName(), 20);
4286 if (doc_buffer->notifiesExternalModification()) {
4287 docstring text = _("The current version will be lost. "
4288 "Are you sure you want to load the version on disk "
4289 "of the document %1$s?");
4290 ret = Alert::prompt(_("Reload saved document?"),
4291 bformat(text, file), 1, 1,
4292 _("&Reload"), _("&Cancel"));
4294 docstring text = _("Any changes will be lost. "
4295 "Are you sure you want to revert to the saved version "
4296 "of the document %1$s?");
4297 ret = Alert::prompt(_("Revert to saved document?"),
4298 bformat(text, file), 1, 1,
4299 _("&Revert"), _("&Cancel"));
4304 doc_buffer->markClean();
4305 reloadBuffer(*doc_buffer);
4306 dr.forceBufferUpdate();
4311 case LFUN_BUFFER_RESET_EXPORT:
4312 LASSERT(doc_buffer, break);
4313 doc_buffer->requireFreshStart(true);
4314 dr.setMessage(_("Buffer export reset."));
4317 case LFUN_BUFFER_WRITE:
4318 LASSERT(doc_buffer, break);
4319 saveBuffer(*doc_buffer);
4322 case LFUN_BUFFER_WRITE_AS:
4323 LASSERT(doc_buffer, break);
4324 renameBuffer(*doc_buffer, cmd.argument());
4327 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4328 LASSERT(doc_buffer, break);
4329 renameBuffer(*doc_buffer, cmd.argument(),
4330 LV_WRITE_AS_TEMPLATE);
4333 case LFUN_BUFFER_WRITE_ALL: {
4334 Buffer * first = theBufferList().first();
4337 message(_("Saving all documents..."));
4338 // We cannot use a for loop as the buffer list cycles.
4341 if (!b->isClean()) {
4343 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4345 b = theBufferList().next(b);
4346 } while (b != first);
4347 dr.setMessage(_("All documents saved."));
4351 case LFUN_MASTER_BUFFER_FORALL: {
4355 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4356 funcToRun.allowAsync(false);
4358 for (Buffer const * buf : doc_buffer->allRelatives()) {
4359 // Switch to other buffer view and resend cmd
4360 lyx::dispatch(FuncRequest(
4361 LFUN_BUFFER_SWITCH, buf->absFileName()));
4362 lyx::dispatch(funcToRun);
4365 lyx::dispatch(FuncRequest(
4366 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4370 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4371 LASSERT(doc_buffer, break);
4372 doc_buffer->clearExternalModification();
4375 case LFUN_BUFFER_CLOSE:
4379 case LFUN_BUFFER_CLOSE_ALL:
4383 case LFUN_DEVEL_MODE_TOGGLE:
4384 devel_mode_ = !devel_mode_;
4386 dr.setMessage(_("Developer mode is now enabled."));
4388 dr.setMessage(_("Developer mode is now disabled."));
4391 case LFUN_TOOLBAR_SET: {
4392 string const name = cmd.getArg(0);
4393 string const state = cmd.getArg(1);
4394 if (GuiToolbar * t = toolbar(name))
4399 case LFUN_TOOLBAR_TOGGLE: {
4400 string const name = cmd.getArg(0);
4401 if (GuiToolbar * t = toolbar(name))
4406 case LFUN_TOOLBAR_MOVABLE: {
4407 string const name = cmd.getArg(0);
4409 // toggle (all) toolbars movablility
4410 toolbarsMovable_ = !toolbarsMovable_;
4411 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4412 GuiToolbar * tb = toolbar(ti.name);
4413 if (tb && tb->isMovable() != toolbarsMovable_)
4414 // toggle toolbar movablity if it does not fit lock
4415 // (all) toolbars positions state silent = true, since
4416 // status bar notifications are slow
4419 if (toolbarsMovable_)
4420 dr.setMessage(_("Toolbars unlocked."));
4422 dr.setMessage(_("Toolbars locked."));
4423 } else if (GuiToolbar * t = toolbar(name)) {
4424 // toggle current toolbar movablity
4426 // update lock (all) toolbars positions
4427 updateLockToolbars();
4432 case LFUN_ICON_SIZE: {
4433 QSize size = d.iconSize(cmd.argument());
4435 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4436 size.width(), size.height()));
4440 case LFUN_DIALOG_UPDATE: {
4441 string const name = to_utf8(cmd.argument());
4442 if (name == "prefs" || name == "document")
4443 updateDialog(name, string());
4444 else if (name == "paragraph")
4445 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4446 else if (currentBufferView()) {
4447 Inset * inset = currentBufferView()->editedInset(name);
4448 // Can only update a dialog connected to an existing inset
4450 // FIXME: get rid of this indirection; GuiView ask the inset
4451 // if he is kind enough to update itself...
4452 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4453 //FIXME: pass DispatchResult here?
4454 inset->dispatch(currentBufferView()->cursor(), fr);
4460 case LFUN_DIALOG_TOGGLE: {
4461 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4462 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4463 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4467 case LFUN_DIALOG_DISCONNECT_INSET:
4468 disconnectDialog(to_utf8(cmd.argument()));
4471 case LFUN_DIALOG_HIDE: {
4472 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4476 case LFUN_DIALOG_SHOW: {
4477 string const name = cmd.getArg(0);
4478 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4480 if (name == "latexlog") {
4481 // getStatus checks that
4482 LASSERT(doc_buffer, break);
4483 Buffer::LogType type;
4484 string const logfile = doc_buffer->logName(&type);
4486 case Buffer::latexlog:
4489 case Buffer::buildlog:
4490 sdata = "literate ";
4493 sdata += Lexer::quoteString(logfile);
4494 showDialog("log", sdata);
4495 } else if (name == "vclog") {
4496 // getStatus checks that
4497 LASSERT(doc_buffer, break);
4498 string const sdata2 = "vc " +
4499 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4500 showDialog("log", sdata2);
4501 } else if (name == "symbols") {
4502 sdata = bv->cursor().getEncoding()->name();
4504 showDialog("symbols", sdata);
4505 } else if (name == "findreplace") {
4506 sdata = to_utf8(bv->cursor().selectionAsString(false));
4507 showDialog(name, sdata);
4509 } else if (name == "prefs" && isFullScreen()) {
4510 lfunUiToggle("fullscreen");
4511 showDialog("prefs", sdata);
4513 showDialog(name, sdata);
4518 dr.setMessage(cmd.argument());
4521 case LFUN_UI_TOGGLE: {
4522 string arg = cmd.getArg(0);
4523 if (!lfunUiToggle(arg)) {
4524 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4525 dr.setMessage(bformat(msg, from_utf8(arg)));
4527 // Make sure the keyboard focus stays in the work area.
4532 case LFUN_VIEW_SPLIT: {
4533 LASSERT(doc_buffer, break);
4534 string const orientation = cmd.getArg(0);
4535 d.splitter_->setOrientation(orientation == "vertical"
4536 ? Qt::Vertical : Qt::Horizontal);
4537 TabWorkArea * twa = addTabWorkArea();
4538 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4539 setCurrentWorkArea(wa);
4542 case LFUN_TAB_GROUP_CLOSE:
4543 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4544 closeTabWorkArea(twa);
4545 d.current_work_area_ = nullptr;
4546 twa = d.currentTabWorkArea();
4547 // Switch to the next GuiWorkArea in the found TabWorkArea.
4549 // Make sure the work area is up to date.
4550 setCurrentWorkArea(twa->currentWorkArea());
4552 setCurrentWorkArea(nullptr);
4557 case LFUN_VIEW_CLOSE:
4558 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4559 closeWorkArea(twa->currentWorkArea());
4560 d.current_work_area_ = nullptr;
4561 twa = d.currentTabWorkArea();
4562 // Switch to the next GuiWorkArea in the found TabWorkArea.
4564 // Make sure the work area is up to date.
4565 setCurrentWorkArea(twa->currentWorkArea());
4567 setCurrentWorkArea(nullptr);
4572 case LFUN_COMPLETION_INLINE:
4573 if (d.current_work_area_)
4574 d.current_work_area_->completer().showInline();
4577 case LFUN_COMPLETION_POPUP:
4578 if (d.current_work_area_)
4579 d.current_work_area_->completer().showPopup();
4584 if (d.current_work_area_)
4585 d.current_work_area_->completer().tab();
4588 case LFUN_COMPLETION_CANCEL:
4589 if (d.current_work_area_) {
4590 if (d.current_work_area_->completer().popupVisible())
4591 d.current_work_area_->completer().hidePopup();
4593 d.current_work_area_->completer().hideInline();
4597 case LFUN_COMPLETION_ACCEPT:
4598 if (d.current_work_area_)
4599 d.current_work_area_->completer().activate();
4602 case LFUN_BUFFER_ZOOM_IN:
4603 case LFUN_BUFFER_ZOOM_OUT:
4604 case LFUN_BUFFER_ZOOM: {
4605 if (cmd.argument().empty()) {
4606 if (cmd.action() == LFUN_BUFFER_ZOOM)
4608 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4613 if (cmd.action() == LFUN_BUFFER_ZOOM)
4614 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4615 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4616 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4618 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4621 // Actual zoom value: default zoom + fractional extra value
4622 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4623 if (zoom < static_cast<int>(zoom_min_))
4626 setCurrentZoom(zoom);
4628 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4629 lyxrc.currentZoom, lyxrc.defaultZoom));
4631 guiApp->fontLoader().update();
4632 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4636 case LFUN_VC_REGISTER:
4637 case LFUN_VC_RENAME:
4639 case LFUN_VC_CHECK_IN:
4640 case LFUN_VC_CHECK_OUT:
4641 case LFUN_VC_REPO_UPDATE:
4642 case LFUN_VC_LOCKING_TOGGLE:
4643 case LFUN_VC_REVERT:
4644 case LFUN_VC_UNDO_LAST:
4645 case LFUN_VC_COMMAND:
4646 case LFUN_VC_COMPARE:
4647 dispatchVC(cmd, dr);
4650 case LFUN_SERVER_GOTO_FILE_ROW:
4651 if(goToFileRow(to_utf8(cmd.argument())))
4652 dr.screenUpdate(Update::Force | Update::FitCursor);
4655 case LFUN_LYX_ACTIVATE:
4659 case LFUN_WINDOW_RAISE:
4665 case LFUN_FORWARD_SEARCH: {
4666 // it seems safe to assume we have a document buffer, since
4667 // getStatus wants one.
4668 LASSERT(doc_buffer, break);
4669 Buffer const * doc_master = doc_buffer->masterBuffer();
4670 FileName const path(doc_master->temppath());
4671 string const texname = doc_master->isChild(doc_buffer)
4672 ? DocFileName(changeExtension(
4673 doc_buffer->absFileName(),
4674 "tex")).mangledFileName()
4675 : doc_buffer->latexName();
4676 string const fulltexname =
4677 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4678 string const mastername =
4679 removeExtension(doc_master->latexName());
4680 FileName const dviname(addName(path.absFileName(),
4681 addExtension(mastername, "dvi")));
4682 FileName const pdfname(addName(path.absFileName(),
4683 addExtension(mastername, "pdf")));
4684 bool const have_dvi = dviname.exists();
4685 bool const have_pdf = pdfname.exists();
4686 if (!have_dvi && !have_pdf) {
4687 dr.setMessage(_("Please, preview the document first."));
4690 string outname = dviname.onlyFileName();
4691 string command = lyxrc.forward_search_dvi;
4692 if (!have_dvi || (have_pdf &&
4693 pdfname.lastModified() > dviname.lastModified())) {
4694 outname = pdfname.onlyFileName();
4695 command = lyxrc.forward_search_pdf;
4698 DocIterator cur = bv->cursor();
4699 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4700 LYXERR(Debug::ACTION, "Forward search: row:" << row
4702 if (row == -1 || command.empty()) {
4703 dr.setMessage(_("Couldn't proceed."));
4706 string texrow = convert<string>(row);
4708 command = subst(command, "$$n", texrow);
4709 command = subst(command, "$$f", fulltexname);
4710 command = subst(command, "$$t", texname);
4711 command = subst(command, "$$o", outname);
4713 volatile PathChanger p(path);
4715 one.startscript(Systemcall::DontWait, command);
4719 case LFUN_SPELLING_CONTINUOUSLY:
4720 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4721 dr.screenUpdate(Update::Force);
4724 case LFUN_CITATION_OPEN: {
4726 if (theFormats().getFormat("pdf"))
4727 pdfv = theFormats().getFormat("pdf")->viewer();
4728 if (theFormats().getFormat("ps"))
4729 psv = theFormats().getFormat("ps")->viewer();
4730 frontend::showTarget(argument, pdfv, psv);
4735 // The LFUN must be for one of BufferView, Buffer or Cursor;
4737 dispatchToBufferView(cmd, dr);
4741 // Need to update bv because many LFUNs here might have destroyed it
4742 bv = currentBufferView();
4744 // Clear non-empty selections
4745 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4747 Cursor & cur = bv->cursor();
4748 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4749 cur.clearSelection();
4755 bool GuiView::lfunUiToggle(string const & ui_component)
4757 if (ui_component == "scrollbar") {
4758 // hide() is of no help
4759 if (d.current_work_area_->verticalScrollBarPolicy() ==
4760 Qt::ScrollBarAlwaysOff)
4762 d.current_work_area_->setVerticalScrollBarPolicy(
4763 Qt::ScrollBarAsNeeded);
4765 d.current_work_area_->setVerticalScrollBarPolicy(
4766 Qt::ScrollBarAlwaysOff);
4767 } else if (ui_component == "statusbar") {
4768 statusBar()->setVisible(!statusBar()->isVisible());
4769 } else if (ui_component == "menubar") {
4770 menuBar()->setVisible(!menuBar()->isVisible());
4772 if (ui_component == "frame") {
4773 int const l = contentsMargins().left();
4775 //are the frames in default state?
4776 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4778 #if QT_VERSION > 0x050903
4779 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4781 setContentsMargins(-2, -2, -2, -2);
4783 #if QT_VERSION > 0x050903
4784 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4786 setContentsMargins(0, 0, 0, 0);
4789 if (ui_component == "fullscreen") {
4797 void GuiView::toggleFullScreen()
4799 setWindowState(windowState() ^ Qt::WindowFullScreen);
4803 Buffer const * GuiView::updateInset(Inset const * inset)
4808 Buffer const * inset_buffer = &(inset->buffer());
4810 for (int i = 0; i != d.splitter_->count(); ++i) {
4811 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4814 Buffer const * buffer = &(wa->bufferView().buffer());
4815 if (inset_buffer == buffer)
4816 wa->scheduleRedraw(true);
4818 return inset_buffer;
4822 void GuiView::restartCaret()
4824 /* When we move around, or type, it's nice to be able to see
4825 * the caret immediately after the keypress.
4827 if (d.current_work_area_)
4828 d.current_work_area_->startBlinkingCaret();
4830 // Take this occasion to update the other GUI elements.
4836 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4838 if (d.current_work_area_)
4839 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4844 // This list should be kept in sync with the list of insets in
4845 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4846 // dialog should have the same name as the inset.
4847 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4848 // docs in LyXAction.cpp.
4850 char const * const dialognames[] = {
4852 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4853 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4854 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4855 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4856 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4857 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4858 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4859 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4861 char const * const * const end_dialognames =
4862 dialognames + (sizeof(dialognames) / sizeof(char *));
4866 cmpCStr(char const * name) : name_(name) {}
4867 bool operator()(char const * other) {
4868 return strcmp(other, name_) == 0;
4875 bool isValidName(string const & name)
4877 return find_if(dialognames, end_dialognames,
4878 cmpCStr(name.c_str())) != end_dialognames;
4884 void GuiView::resetDialogs()
4886 // Make sure that no LFUN uses any GuiView.
4887 guiApp->setCurrentView(nullptr);
4891 constructToolbars();
4892 guiApp->menus().fillMenuBar(menuBar(), this, false);
4893 d.layout_->updateContents(true);
4894 // Now update controls with current buffer.
4895 guiApp->setCurrentView(this);
4901 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4903 for (QObject * child: widget->children()) {
4904 if (child->inherits("QGroupBox")) {
4905 QGroupBox * box = (QGroupBox*) child;
4908 flatGroupBoxes(child, flag);
4914 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4916 if (!isValidName(name))
4919 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4921 if (it != d.dialogs_.end()) {
4923 it->second->hideView();
4924 return it->second.get();
4927 Dialog * dialog = build(name);
4928 d.dialogs_[name].reset(dialog);
4929 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4930 // Force a uniform style for group boxes
4931 // On Mac non-flat works better, on Linux flat is standard
4932 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4934 if (lyxrc.allow_geometry_session)
4935 dialog->restoreSession();
4942 void GuiView::showDialog(string const & name, string const & sdata,
4945 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4949 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4955 const string name = fromqstr(qname);
4956 const string sdata = fromqstr(qdata);
4960 Dialog * dialog = findOrBuild(name, false);
4962 bool const visible = dialog->isVisibleView();
4963 dialog->showData(sdata);
4964 if (currentBufferView())
4965 currentBufferView()->editInset(name, inset);
4966 // We only set the focus to the new dialog if it was not yet
4967 // visible in order not to change the existing previous behaviour
4969 // activateWindow is needed for floating dockviews
4970 dialog->asQWidget()->raise();
4971 dialog->asQWidget()->activateWindow();
4972 if (dialog->wantInitialFocus())
4973 dialog->asQWidget()->setFocus();
4977 catch (ExceptionMessage const &) {
4985 bool GuiView::isDialogVisible(string const & name) const
4987 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4988 if (it == d.dialogs_.end())
4990 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4994 void GuiView::hideDialog(string const & name, Inset * inset)
4996 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4997 if (it == d.dialogs_.end())
5001 if (!currentBufferView())
5003 if (inset != currentBufferView()->editedInset(name))
5007 Dialog * const dialog = it->second.get();
5008 if (dialog->isVisibleView())
5010 if (currentBufferView())
5011 currentBufferView()->editInset(name, nullptr);
5015 void GuiView::disconnectDialog(string const & name)
5017 if (!isValidName(name))
5019 if (currentBufferView())
5020 currentBufferView()->editInset(name, nullptr);
5024 void GuiView::hideAll() const
5026 for(auto const & dlg_p : d.dialogs_)
5027 dlg_p.second->hideView();
5031 void GuiView::updateDialogs()
5033 for(auto const & dlg_p : d.dialogs_) {
5034 Dialog * dialog = dlg_p.second.get();
5036 if (dialog->needBufferOpen() && !documentBufferView())
5037 hideDialog(fromqstr(dialog->name()), nullptr);
5038 else if (dialog->isVisibleView())
5039 dialog->checkStatus();
5047 Dialog * GuiView::build(string const & name)
5049 return createDialog(*this, name);
5053 SEMenu::SEMenu(QWidget * parent)
5055 QAction * action = addAction(qt_("Disable Shell Escape"));
5056 connect(action, SIGNAL(triggered()),
5057 parent, SLOT(disableShellEscape()));
5060 } // namespace frontend
5063 #include "moc_GuiView.cpp"