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 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
661 zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
663 zoom_value_->setMinimumWidth(fm.width("000%"));
665 statusBar()->addPermanentWidget(zoom_value_);
666 zoom_value_->setEnabled(currentBufferView());
668 int const iconheight = max(int(d.normalIconSize), fm.height());
669 QSize const iconsize(iconheight, iconheight);
671 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
672 shell_escape_ = new QLabel(statusBar());
673 shell_escape_->setPixmap(shellescape);
674 shell_escape_->setScaledContents(true);
675 shell_escape_->setAlignment(Qt::AlignCenter);
676 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
677 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
678 "external commands for this document. "
679 "Right click to change."));
680 SEMenu * menu = new SEMenu(this);
681 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
682 menu, SLOT(showMenu(QPoint)));
683 shell_escape_->hide();
684 statusBar()->addPermanentWidget(shell_escape_);
686 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
687 read_only_ = new QLabel(statusBar());
688 read_only_->setPixmap(readonly);
689 read_only_->setScaledContents(true);
690 read_only_->setAlignment(Qt::AlignCenter);
692 statusBar()->addPermanentWidget(read_only_);
694 version_control_ = new QLabel(statusBar());
695 version_control_->setAlignment(Qt::AlignCenter);
696 version_control_->setFrameStyle(QFrame::StyledPanel);
697 version_control_->hide();
698 statusBar()->addPermanentWidget(version_control_);
700 statusBar()->setSizeGripEnabled(true);
703 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
704 SLOT(autoSaveThreadFinished()));
706 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
707 SLOT(processingThreadStarted()));
708 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
709 SLOT(processingThreadFinished()));
711 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
712 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
714 // set custom application bars context menu, e.g. tool bar and menu bar
715 setContextMenuPolicy(Qt::CustomContextMenu);
716 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
717 SLOT(toolBarPopup(const QPoint &)));
719 // Forbid too small unresizable window because it can happen
720 // with some window manager under X11.
721 setMinimumSize(300, 200);
723 if (lyxrc.allow_geometry_session) {
724 // Now take care of session management.
729 // no session handling, default to a sane size.
730 setGeometry(50, 50, 690, 510);
733 // clear session data if any.
734 settings.remove("views");
744 void GuiView::disableShellEscape()
746 BufferView * bv = documentBufferView();
749 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
750 bv->buffer().params().shell_escape = false;
751 bv->processUpdateFlags(Update::Force);
755 void GuiView::checkCancelBackground()
757 docstring const ttl = _("Cancel Export?");
758 docstring const msg = _("Do you want to cancel the background export process?");
760 Alert::prompt(ttl, msg, 1, 1,
761 _("&Cancel export"), _("Co&ntinue"));
763 Systemcall::killscript();
767 void GuiView::zoomSliderMoved(int value)
770 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
771 currentWorkArea()->scheduleRedraw(true);
772 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
776 void GuiView::zoomValueChanged(int value)
778 if (value != lyxrc.currentZoom)
779 zoomSliderMoved(value);
783 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
785 QVector<GuiWorkArea*> areas;
786 for (int i = 0; i < tabWorkAreaCount(); i++) {
787 TabWorkArea* ta = tabWorkArea(i);
788 for (int u = 0; u < ta->count(); u++) {
789 areas << ta->workArea(u);
795 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
796 string const & format)
798 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
801 case Buffer::ExportSuccess:
802 msg = bformat(_("Successful export to format: %1$s"), fmt);
804 case Buffer::ExportCancel:
805 msg = _("Document export cancelled.");
807 case Buffer::ExportError:
808 case Buffer::ExportNoPathToFormat:
809 case Buffer::ExportTexPathHasSpaces:
810 case Buffer::ExportConverterError:
811 msg = bformat(_("Error while exporting format: %1$s"), fmt);
813 case Buffer::PreviewSuccess:
814 msg = bformat(_("Successful preview of format: %1$s"), fmt);
816 case Buffer::PreviewError:
817 msg = bformat(_("Error while previewing format: %1$s"), fmt);
819 case Buffer::ExportKilled:
820 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
827 void GuiView::processingThreadStarted()
832 void GuiView::processingThreadFinished()
834 QFutureWatcher<Buffer::ExportStatus> const * watcher =
835 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
837 Buffer::ExportStatus const status = watcher->result();
838 handleExportStatus(this, status, d.processing_format);
841 BufferView const * const bv = currentBufferView();
842 if (bv && !bv->buffer().errorList("Export").empty()) {
847 bool const error = (status != Buffer::ExportSuccess &&
848 status != Buffer::PreviewSuccess &&
849 status != Buffer::ExportCancel);
851 ErrorList & el = bv->buffer().errorList(d.last_export_format);
852 // at this point, we do not know if buffer-view or
853 // master-buffer-view was called. If there was an export error,
854 // and the current buffer's error log is empty, we guess that
855 // it must be master-buffer-view that was called so we set
857 errors(d.last_export_format, el.empty());
862 void GuiView::autoSaveThreadFinished()
864 QFutureWatcher<docstring> const * watcher =
865 static_cast<QFutureWatcher<docstring> const *>(sender());
866 message(watcher->result());
871 void GuiView::saveLayout() const
874 settings.setValue("zoom_ratio", zoom_ratio_);
875 settings.setValue("devel_mode", devel_mode_);
876 settings.beginGroup("views");
877 settings.beginGroup(QString::number(id_));
878 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
879 settings.setValue("pos", pos());
880 settings.setValue("size", size());
882 settings.setValue("geometry", saveGeometry());
883 settings.setValue("layout", saveState(0));
884 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
888 void GuiView::saveUISettings() const
892 // Save the toolbar private states
893 for (auto const & tb_p : d.toolbars_)
894 tb_p.second->saveSession(settings);
895 // Now take care of all other dialogs
896 for (auto const & dlg_p : d.dialogs_)
897 dlg_p.second->saveSession(settings);
901 void GuiView::setCurrentZoom(const int v)
903 lyxrc.currentZoom = v;
904 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
905 Q_EMIT currentZoomChanged(v);
909 bool GuiView::restoreLayout()
912 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
913 // Actual zoom value: default zoom + fractional offset
914 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
915 if (zoom < static_cast<int>(zoom_min_))
917 setCurrentZoom(zoom);
918 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
919 settings.beginGroup("views");
920 settings.beginGroup(QString::number(id_));
921 QString const icon_key = "icon_size";
922 if (!settings.contains(icon_key))
925 //code below is skipped when when ~/.config/LyX is (re)created
926 setIconSize(d.iconSize(settings.value(icon_key).toString()));
928 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
929 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
930 QSize size = settings.value("size", QSize(690, 510)).toSize();
934 // Work-around for bug #6034: the window ends up in an undetermined
935 // state when trying to restore a maximized window when it is
936 // already maximized.
937 if (!(windowState() & Qt::WindowMaximized))
938 if (!restoreGeometry(settings.value("geometry").toByteArray()))
939 setGeometry(50, 50, 690, 510);
942 // Make sure layout is correctly oriented.
943 setLayoutDirection(qApp->layoutDirection());
945 // Allow the toc and view-source dock widget to be restored if needed.
947 if ((dialog = findOrBuild("toc", true)))
948 // see bug 5082. At least setup title and enabled state.
949 // Visibility will be adjusted by restoreState below.
950 dialog->prepareView();
951 if ((dialog = findOrBuild("view-source", true)))
952 dialog->prepareView();
953 if ((dialog = findOrBuild("progress", true)))
954 dialog->prepareView();
956 if (!restoreState(settings.value("layout").toByteArray(), 0))
959 // init the toolbars that have not been restored
960 for (auto const & tb_p : guiApp->toolbars()) {
961 GuiToolbar * tb = toolbar(tb_p.name);
962 if (tb && !tb->isRestored())
963 initToolbar(tb_p.name);
966 // update lock (all) toolbars positions
967 updateLockToolbars();
974 GuiToolbar * GuiView::toolbar(string const & name)
976 ToolbarMap::iterator it = d.toolbars_.find(name);
977 if (it != d.toolbars_.end())
980 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
985 void GuiView::updateLockToolbars()
987 toolbarsMovable_ = false;
988 for (ToolbarInfo const & info : guiApp->toolbars()) {
989 GuiToolbar * tb = toolbar(info.name);
990 if (tb && tb->isMovable())
991 toolbarsMovable_ = true;
996 void GuiView::constructToolbars()
998 for (auto const & tb_p : d.toolbars_)
1000 d.toolbars_.clear();
1002 // I don't like doing this here, but the standard toolbar
1003 // destroys this object when it's destroyed itself (vfr)
1004 d.layout_ = new LayoutBox(*this);
1005 d.stack_widget_->addWidget(d.layout_);
1006 d.layout_->move(0,0);
1008 // extracts the toolbars from the backend
1009 for (ToolbarInfo const & inf : guiApp->toolbars())
1010 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1014 void GuiView::initToolbars()
1016 // extracts the toolbars from the backend
1017 for (ToolbarInfo const & inf : guiApp->toolbars())
1018 initToolbar(inf.name);
1022 void GuiView::initToolbar(string const & name)
1024 GuiToolbar * tb = toolbar(name);
1027 int const visibility = guiApp->toolbars().defaultVisibility(name);
1028 bool newline = !(visibility & Toolbars::SAMEROW);
1029 tb->setVisible(false);
1030 tb->setVisibility(visibility);
1032 if (visibility & Toolbars::TOP) {
1034 addToolBarBreak(Qt::TopToolBarArea);
1035 addToolBar(Qt::TopToolBarArea, tb);
1038 if (visibility & Toolbars::BOTTOM) {
1040 addToolBarBreak(Qt::BottomToolBarArea);
1041 addToolBar(Qt::BottomToolBarArea, tb);
1044 if (visibility & Toolbars::LEFT) {
1046 addToolBarBreak(Qt::LeftToolBarArea);
1047 addToolBar(Qt::LeftToolBarArea, tb);
1050 if (visibility & Toolbars::RIGHT) {
1052 addToolBarBreak(Qt::RightToolBarArea);
1053 addToolBar(Qt::RightToolBarArea, tb);
1056 if (visibility & Toolbars::ON)
1057 tb->setVisible(true);
1059 tb->setMovable(true);
1063 TocModels & GuiView::tocModels()
1065 return d.toc_models_;
1069 void GuiView::setFocus()
1071 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1072 QMainWindow::setFocus();
1076 bool GuiView::hasFocus() const
1078 if (currentWorkArea())
1079 return currentWorkArea()->hasFocus();
1080 if (currentMainWorkArea())
1081 return currentMainWorkArea()->hasFocus();
1082 return d.bg_widget_->hasFocus();
1086 void GuiView::focusInEvent(QFocusEvent * e)
1088 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1089 QMainWindow::focusInEvent(e);
1090 // Make sure guiApp points to the correct view.
1091 guiApp->setCurrentView(this);
1092 if (currentWorkArea())
1093 currentWorkArea()->setFocus();
1094 else if (currentMainWorkArea())
1095 currentMainWorkArea()->setFocus();
1097 d.bg_widget_->setFocus();
1101 void GuiView::showEvent(QShowEvent * e)
1103 LYXERR(Debug::GUI, "Passed Geometry "
1104 << size().height() << "x" << size().width()
1105 << "+" << pos().x() << "+" << pos().y());
1107 if (d.splitter_->count() == 0)
1108 // No work area, switch to the background widget.
1112 QMainWindow::showEvent(e);
1116 bool GuiView::closeScheduled()
1123 bool GuiView::prepareAllBuffersForLogout()
1125 Buffer * first = theBufferList().first();
1129 // First, iterate over all buffers and ask the users if unsaved
1130 // changes should be saved.
1131 // We cannot use a for loop as the buffer list cycles.
1134 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1136 b = theBufferList().next(b);
1137 } while (b != first);
1139 // Next, save session state
1140 // When a view/window was closed before without quitting LyX, there
1141 // are already entries in the lastOpened list.
1142 theSession().lastOpened().clear();
1149 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1150 ** is responsibility of the container (e.g., dialog)
1152 void GuiView::closeEvent(QCloseEvent * close_event)
1154 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1156 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1157 Alert::warning(_("Exit LyX"),
1158 _("LyX could not be closed because documents are being processed by LyX."));
1159 close_event->setAccepted(false);
1163 // If the user pressed the x (so we didn't call closeView
1164 // programmatically), we want to clear all existing entries.
1166 theSession().lastOpened().clear();
1171 // it can happen that this event arrives without selecting the view,
1172 // e.g. when clicking the close button on a background window.
1174 if (!closeWorkAreaAll()) {
1176 close_event->ignore();
1180 // Make sure that nothing will use this to be closed View.
1181 guiApp->unregisterView(this);
1183 if (isFullScreen()) {
1184 // Switch off fullscreen before closing.
1189 // Make sure the timer time out will not trigger a statusbar update.
1190 d.statusbar_timer_.stop();
1192 // Saving fullscreen requires additional tweaks in the toolbar code.
1193 // It wouldn't also work under linux natively.
1194 if (lyxrc.allow_geometry_session) {
1199 close_event->accept();
1203 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1205 if (event->mimeData()->hasUrls())
1207 /// \todo Ask lyx-devel is this is enough:
1208 /// if (event->mimeData()->hasFormat("text/plain"))
1209 /// event->acceptProposedAction();
1213 void GuiView::dropEvent(QDropEvent * event)
1215 QList<QUrl> files = event->mimeData()->urls();
1216 if (files.isEmpty())
1219 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1220 for (int i = 0; i != files.size(); ++i) {
1221 string const file = os::internal_path(fromqstr(
1222 files.at(i).toLocalFile()));
1226 string const ext = support::getExtension(file);
1227 vector<const Format *> found_formats;
1229 // Find all formats that have the correct extension.
1230 for (const Format * fmt : theConverters().importableFormats())
1231 if (fmt->hasExtension(ext))
1232 found_formats.push_back(fmt);
1235 if (!found_formats.empty()) {
1236 if (found_formats.size() > 1) {
1237 //FIXME: show a dialog to choose the correct importable format
1238 LYXERR(Debug::FILES,
1239 "Multiple importable formats found, selecting first");
1241 string const arg = found_formats[0]->name() + " " + file;
1242 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1245 //FIXME: do we have to explicitly check whether it's a lyx file?
1246 LYXERR(Debug::FILES,
1247 "No formats found, trying to open it as a lyx file");
1248 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1250 // add the functions to the queue
1251 guiApp->addToFuncRequestQueue(cmd);
1254 // now process the collected functions. We perform the events
1255 // asynchronously. This prevents potential problems in case the
1256 // BufferView is closed within an event.
1257 guiApp->processFuncRequestQueueAsync();
1261 void GuiView::message(docstring const & str)
1263 if (ForkedProcess::iAmAChild())
1266 // call is moved to GUI-thread by GuiProgress
1267 d.progress_->appendMessage(toqstr(str));
1271 void GuiView::clearMessageText()
1273 message(docstring());
1277 void GuiView::updateStatusBarMessage(QString const & str)
1279 statusBar()->showMessage(str);
1280 d.statusbar_timer_.stop();
1281 d.statusbar_timer_.start(3000);
1285 void GuiView::clearMessage()
1287 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1288 // the hasFocus function mostly returns false, even if the focus is on
1289 // a workarea in this view.
1293 d.statusbar_timer_.stop();
1297 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1299 if (wa != d.current_work_area_
1300 || wa->bufferView().buffer().isInternal())
1302 Buffer const & buf = wa->bufferView().buffer();
1303 // Set the windows title
1304 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1305 if (buf.notifiesExternalModification()) {
1306 title = bformat(_("%1$s (modified externally)"), title);
1307 // If the external modification status has changed, then maybe the status of
1308 // buffer-save has changed too.
1312 title += from_ascii(" - LyX");
1314 setWindowTitle(toqstr(title));
1315 // Sets the path for the window: this is used by OSX to
1316 // allow a context click on the title bar showing a menu
1317 // with the path up to the file
1318 setWindowFilePath(toqstr(buf.absFileName()));
1319 // Tell Qt whether the current document is changed
1320 setWindowModified(!buf.isClean());
1322 if (buf.params().shell_escape)
1323 shell_escape_->show();
1325 shell_escape_->hide();
1327 if (buf.hasReadonlyFlag())
1332 if (buf.lyxvc().inUse()) {
1333 version_control_->show();
1334 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1336 version_control_->hide();
1340 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1342 if (d.current_work_area_)
1343 // disconnect the current work area from all slots
1344 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1346 disconnectBufferView();
1347 connectBufferView(wa->bufferView());
1348 connectBuffer(wa->bufferView().buffer());
1349 d.current_work_area_ = wa;
1350 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1351 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1352 QObject::connect(wa, SIGNAL(busy(bool)),
1353 this, SLOT(setBusy(bool)));
1354 // connection of a signal to a signal
1355 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1356 this, SIGNAL(bufferViewChanged()));
1357 Q_EMIT updateWindowTitle(wa);
1358 Q_EMIT bufferViewChanged();
1362 void GuiView::onBufferViewChanged()
1365 // Buffer-dependent dialogs must be updated. This is done here because
1366 // some dialogs require buffer()->text.
1368 zoom_slider_->setEnabled(currentBufferView());
1369 zoom_value_->setEnabled(currentBufferView());
1373 void GuiView::on_lastWorkAreaRemoved()
1376 // We already are in a close event. Nothing more to do.
1379 if (d.splitter_->count() > 1)
1380 // We have a splitter so don't close anything.
1383 // Reset and updates the dialogs.
1384 Q_EMIT bufferViewChanged();
1389 if (lyxrc.open_buffers_in_tabs)
1390 // Nothing more to do, the window should stay open.
1393 if (guiApp->viewIds().size() > 1) {
1399 // On Mac we also close the last window because the application stay
1400 // resident in memory. On other platforms we don't close the last
1401 // window because this would quit the application.
1407 void GuiView::updateStatusBar()
1409 // let the user see the explicit message
1410 if (d.statusbar_timer_.isActive())
1417 void GuiView::showMessage()
1421 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1422 if (msg.isEmpty()) {
1423 BufferView const * bv = currentBufferView();
1425 msg = toqstr(bv->cursor().currentState(devel_mode_));
1427 msg = qt_("Welcome to LyX!");
1429 statusBar()->showMessage(msg);
1433 bool GuiView::event(QEvent * e)
1437 // Useful debug code:
1438 //case QEvent::ActivationChange:
1439 //case QEvent::WindowDeactivate:
1440 //case QEvent::Paint:
1441 //case QEvent::Enter:
1442 //case QEvent::Leave:
1443 //case QEvent::HoverEnter:
1444 //case QEvent::HoverLeave:
1445 //case QEvent::HoverMove:
1446 //case QEvent::StatusTip:
1447 //case QEvent::DragEnter:
1448 //case QEvent::DragLeave:
1449 //case QEvent::Drop:
1452 case QEvent::WindowStateChange: {
1453 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1454 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1455 bool result = QMainWindow::event(e);
1456 bool nfstate = (windowState() & Qt::WindowFullScreen);
1457 if (!ofstate && nfstate) {
1458 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1459 // switch to full-screen state
1460 if (lyxrc.full_screen_statusbar)
1461 statusBar()->hide();
1462 if (lyxrc.full_screen_menubar)
1464 if (lyxrc.full_screen_toolbars) {
1465 for (auto const & tb_p : d.toolbars_)
1466 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1467 tb_p.second->hide();
1469 for (int i = 0; i != d.splitter_->count(); ++i)
1470 d.tabWorkArea(i)->setFullScreen(true);
1471 #if QT_VERSION > 0x050903
1472 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1473 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1475 setContentsMargins(-2, -2, -2, -2);
1477 hideDialogs("prefs", nullptr);
1478 } else if (ofstate && !nfstate) {
1479 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1480 // switch back from full-screen state
1481 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1482 statusBar()->show();
1483 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1485 if (lyxrc.full_screen_toolbars) {
1486 for (auto const & tb_p : d.toolbars_)
1487 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1488 tb_p.second->show();
1491 for (int i = 0; i != d.splitter_->count(); ++i)
1492 d.tabWorkArea(i)->setFullScreen(false);
1493 #if QT_VERSION > 0x050903
1494 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1496 setContentsMargins(0, 0, 0, 0);
1500 case QEvent::WindowActivate: {
1501 GuiView * old_view = guiApp->currentView();
1502 if (this == old_view) {
1504 return QMainWindow::event(e);
1506 if (old_view && old_view->currentBufferView()) {
1507 // save current selection to the selection buffer to allow
1508 // middle-button paste in this window.
1509 cap::saveSelection(old_view->currentBufferView()->cursor());
1511 guiApp->setCurrentView(this);
1512 if (d.current_work_area_)
1513 on_currentWorkAreaChanged(d.current_work_area_);
1517 return QMainWindow::event(e);
1520 case QEvent::ShortcutOverride: {
1522 if (isFullScreen() && menuBar()->isHidden()) {
1523 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1524 // FIXME: we should also try to detect special LyX shortcut such as
1525 // Alt-P and Alt-M. Right now there is a hack in
1526 // GuiWorkArea::processKeySym() that hides again the menubar for
1528 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1530 return QMainWindow::event(e);
1533 return QMainWindow::event(e);
1536 case QEvent::ApplicationPaletteChange: {
1537 // runtime switch from/to dark mode
1539 return QMainWindow::event(e);
1543 return QMainWindow::event(e);
1547 void GuiView::resetWindowTitle()
1549 setWindowTitle(qt_("LyX"));
1552 bool GuiView::focusNextPrevChild(bool /*next*/)
1559 bool GuiView::busy() const
1565 void GuiView::setBusy(bool busy)
1567 bool const busy_before = busy_ > 0;
1568 busy ? ++busy_ : --busy_;
1569 if ((busy_ > 0) == busy_before)
1570 // busy state didn't change
1574 QApplication::setOverrideCursor(Qt::WaitCursor);
1577 QApplication::restoreOverrideCursor();
1582 void GuiView::resetCommandExecute()
1584 command_execute_ = false;
1589 double GuiView::pixelRatio() const
1591 #if QT_VERSION >= 0x050000
1592 return qt_scale_factor * devicePixelRatio();
1599 GuiWorkArea * GuiView::workArea(int index)
1601 if (TabWorkArea * twa = d.currentTabWorkArea())
1602 if (index < twa->count())
1603 return twa->workArea(index);
1608 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1610 if (currentWorkArea()
1611 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1612 return currentWorkArea();
1613 if (TabWorkArea * twa = d.currentTabWorkArea())
1614 return twa->workArea(buffer);
1619 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1621 // Automatically create a TabWorkArea if there are none yet.
1622 TabWorkArea * tab_widget = d.splitter_->count()
1623 ? d.currentTabWorkArea() : addTabWorkArea();
1624 return tab_widget->addWorkArea(buffer, *this);
1628 TabWorkArea * GuiView::addTabWorkArea()
1630 TabWorkArea * twa = new TabWorkArea;
1631 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1632 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1633 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1634 this, SLOT(on_lastWorkAreaRemoved()));
1636 d.splitter_->addWidget(twa);
1637 d.stack_widget_->setCurrentWidget(d.splitter_);
1642 GuiWorkArea const * GuiView::currentWorkArea() const
1644 return d.current_work_area_;
1648 GuiWorkArea * GuiView::currentWorkArea()
1650 return d.current_work_area_;
1654 GuiWorkArea const * GuiView::currentMainWorkArea() const
1656 if (!d.currentTabWorkArea())
1658 return d.currentTabWorkArea()->currentWorkArea();
1662 GuiWorkArea * GuiView::currentMainWorkArea()
1664 if (!d.currentTabWorkArea())
1666 return d.currentTabWorkArea()->currentWorkArea();
1670 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1672 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1674 d.current_work_area_ = nullptr;
1676 Q_EMIT bufferViewChanged();
1680 // FIXME: I've no clue why this is here and why it accesses
1681 // theGuiApp()->currentView, which might be 0 (bug 6464).
1682 // See also 27525 (vfr).
1683 if (theGuiApp()->currentView() == this
1684 && theGuiApp()->currentView()->currentWorkArea() == wa)
1687 if (currentBufferView())
1688 cap::saveSelection(currentBufferView()->cursor());
1690 theGuiApp()->setCurrentView(this);
1691 d.current_work_area_ = wa;
1693 // We need to reset this now, because it will need to be
1694 // right if the tabWorkArea gets reset in the for loop. We
1695 // will change it back if we aren't in that case.
1696 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1697 d.current_main_work_area_ = wa;
1699 for (int i = 0; i != d.splitter_->count(); ++i) {
1700 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1701 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1702 << ", Current main wa: " << currentMainWorkArea());
1707 d.current_main_work_area_ = old_cmwa;
1709 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1710 on_currentWorkAreaChanged(wa);
1711 BufferView & bv = wa->bufferView();
1712 bv.cursor().fixIfBroken();
1714 wa->setUpdatesEnabled(true);
1715 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1719 void GuiView::removeWorkArea(GuiWorkArea * wa)
1721 LASSERT(wa, return);
1722 if (wa == d.current_work_area_) {
1724 disconnectBufferView();
1725 d.current_work_area_ = nullptr;
1726 d.current_main_work_area_ = nullptr;
1729 bool found_twa = false;
1730 for (int i = 0; i != d.splitter_->count(); ++i) {
1731 TabWorkArea * twa = d.tabWorkArea(i);
1732 if (twa->removeWorkArea(wa)) {
1733 // Found in this tab group, and deleted the GuiWorkArea.
1735 if (twa->count() != 0) {
1736 if (d.current_work_area_ == nullptr)
1737 // This means that we are closing the current GuiWorkArea, so
1738 // switch to the next GuiWorkArea in the found TabWorkArea.
1739 setCurrentWorkArea(twa->currentWorkArea());
1741 // No more WorkAreas in this tab group, so delete it.
1748 // It is not a tabbed work area (i.e., the search work area), so it
1749 // should be deleted by other means.
1750 LASSERT(found_twa, return);
1752 if (d.current_work_area_ == nullptr) {
1753 if (d.splitter_->count() != 0) {
1754 TabWorkArea * twa = d.currentTabWorkArea();
1755 setCurrentWorkArea(twa->currentWorkArea());
1757 // No more work areas, switch to the background widget.
1758 setCurrentWorkArea(nullptr);
1764 LayoutBox * GuiView::getLayoutDialog() const
1770 void GuiView::updateLayoutList()
1773 d.layout_->updateContents(false);
1777 void GuiView::updateToolbars()
1779 if (d.current_work_area_) {
1781 if (d.current_work_area_->bufferView().cursor().inMathed()
1782 && !d.current_work_area_->bufferView().cursor().inRegexped())
1783 context |= Toolbars::MATH;
1784 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1785 context |= Toolbars::TABLE;
1786 if (currentBufferView()->buffer().areChangesPresent()
1787 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1788 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1789 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1790 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1791 context |= Toolbars::REVIEW;
1792 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1793 context |= Toolbars::MATHMACROTEMPLATE;
1794 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1795 context |= Toolbars::IPA;
1796 if (command_execute_)
1797 context |= Toolbars::MINIBUFFER;
1798 if (minibuffer_focus_) {
1799 context |= Toolbars::MINIBUFFER_FOCUS;
1800 minibuffer_focus_ = false;
1803 for (auto const & tb_p : d.toolbars_)
1804 tb_p.second->update(context);
1806 for (auto const & tb_p : d.toolbars_)
1807 tb_p.second->update();
1811 void GuiView::refillToolbars()
1813 for (auto const & tb_p : d.toolbars_)
1814 tb_p.second->refill();
1818 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1820 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1821 LASSERT(newBuffer, return);
1823 GuiWorkArea * wa = workArea(*newBuffer);
1824 if (wa == nullptr) {
1826 newBuffer->masterBuffer()->updateBuffer();
1828 wa = addWorkArea(*newBuffer);
1829 // scroll to the position when the BufferView was last closed
1830 if (lyxrc.use_lastfilepos) {
1831 LastFilePosSection::FilePos filepos =
1832 theSession().lastFilePos().load(newBuffer->fileName());
1833 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1836 //Disconnect the old buffer...there's no new one.
1839 connectBuffer(*newBuffer);
1840 connectBufferView(wa->bufferView());
1842 setCurrentWorkArea(wa);
1846 void GuiView::connectBuffer(Buffer & buf)
1848 buf.setGuiDelegate(this);
1852 void GuiView::disconnectBuffer()
1854 if (d.current_work_area_)
1855 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1859 void GuiView::connectBufferView(BufferView & bv)
1861 bv.setGuiDelegate(this);
1865 void GuiView::disconnectBufferView()
1867 if (d.current_work_area_)
1868 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1872 void GuiView::errors(string const & error_type, bool from_master)
1874 BufferView const * const bv = currentBufferView();
1878 ErrorList const & el = from_master ?
1879 bv->buffer().masterBuffer()->errorList(error_type) :
1880 bv->buffer().errorList(error_type);
1885 string err = error_type;
1887 err = "from_master|" + error_type;
1888 showDialog("errorlist", err);
1892 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1894 d.toc_models_.updateItem(toqstr(type), dit);
1898 void GuiView::structureChanged()
1900 // This is called from the Buffer, which has no way to ensure that cursors
1901 // in BufferView remain valid.
1902 if (documentBufferView())
1903 documentBufferView()->cursor().sanitize();
1904 // FIXME: This is slightly expensive, though less than the tocBackend update
1905 // (#9880). This also resets the view in the Toc Widget (#6675).
1906 d.toc_models_.reset(documentBufferView());
1907 // Navigator needs more than a simple update in this case. It needs to be
1909 updateDialog("toc", "");
1913 void GuiView::updateDialog(string const & name, string const & sdata)
1915 if (!isDialogVisible(name))
1918 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1919 if (it == d.dialogs_.end())
1922 Dialog * const dialog = it->second.get();
1923 if (dialog->isVisibleView())
1924 dialog->initialiseParams(sdata);
1928 BufferView * GuiView::documentBufferView()
1930 return currentMainWorkArea()
1931 ? ¤tMainWorkArea()->bufferView()
1936 BufferView const * GuiView::documentBufferView() const
1938 return currentMainWorkArea()
1939 ? ¤tMainWorkArea()->bufferView()
1944 BufferView * GuiView::currentBufferView()
1946 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1950 BufferView const * GuiView::currentBufferView() const
1952 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1956 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1957 Buffer const * orig, Buffer * clone)
1959 bool const success = clone->autoSave();
1961 busyBuffers.remove(orig);
1963 ? _("Automatic save done.")
1964 : _("Automatic save failed!");
1968 void GuiView::autoSave()
1970 LYXERR(Debug::INFO, "Running autoSave()");
1972 Buffer * buffer = documentBufferView()
1973 ? &documentBufferView()->buffer() : nullptr;
1975 resetAutosaveTimers();
1979 GuiViewPrivate::busyBuffers.insert(buffer);
1980 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1981 buffer, buffer->cloneBufferOnly());
1982 d.autosave_watcher_.setFuture(f);
1983 resetAutosaveTimers();
1987 void GuiView::resetAutosaveTimers()
1990 d.autosave_timeout_.restart();
1994 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1997 Buffer * buf = currentBufferView()
1998 ? ¤tBufferView()->buffer() : nullptr;
1999 Buffer * doc_buffer = documentBufferView()
2000 ? &(documentBufferView()->buffer()) : nullptr;
2003 /* In LyX/Mac, when a dialog is open, the menus of the
2004 application can still be accessed without giving focus to
2005 the main window. In this case, we want to disable the menu
2006 entries that are buffer-related.
2007 This code must not be used on Linux and Windows, since it
2008 would disable buffer-related entries when hovering over the
2009 menu (see bug #9574).
2011 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2017 // Check whether we need a buffer
2018 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2019 // no, exit directly
2020 flag.message(from_utf8(N_("Command not allowed with"
2021 "out any document open")));
2022 flag.setEnabled(false);
2026 if (cmd.origin() == FuncRequest::TOC) {
2027 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2028 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2029 flag.setEnabled(false);
2033 switch(cmd.action()) {
2034 case LFUN_BUFFER_IMPORT:
2037 case LFUN_MASTER_BUFFER_EXPORT:
2039 && (doc_buffer->parent() != nullptr
2040 || doc_buffer->hasChildren())
2041 && !d.processing_thread_watcher_.isRunning()
2042 // this launches a dialog, which would be in the wrong Buffer
2043 && !(::lyx::operator==(cmd.argument(), "custom"));
2046 case LFUN_MASTER_BUFFER_UPDATE:
2047 case LFUN_MASTER_BUFFER_VIEW:
2049 && (doc_buffer->parent() != nullptr
2050 || doc_buffer->hasChildren())
2051 && !d.processing_thread_watcher_.isRunning();
2054 case LFUN_BUFFER_UPDATE:
2055 case LFUN_BUFFER_VIEW: {
2056 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2060 string format = to_utf8(cmd.argument());
2061 if (cmd.argument().empty())
2062 format = doc_buffer->params().getDefaultOutputFormat();
2063 enable = doc_buffer->params().isExportable(format, true);
2067 case LFUN_BUFFER_RELOAD:
2068 enable = doc_buffer && !doc_buffer->isUnnamed()
2069 && doc_buffer->fileName().exists()
2070 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2073 case LFUN_BUFFER_RESET_EXPORT:
2074 enable = doc_buffer != nullptr;
2077 case LFUN_BUFFER_CHILD_OPEN:
2078 enable = doc_buffer != nullptr;
2081 case LFUN_MASTER_BUFFER_FORALL: {
2082 if (doc_buffer == nullptr) {
2083 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2087 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2088 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2089 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2094 for (Buffer * buf : doc_buffer->allRelatives()) {
2095 GuiWorkArea * wa = workArea(*buf);
2098 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2099 enable = flag.enabled();
2106 case LFUN_BUFFER_WRITE:
2107 enable = doc_buffer && (doc_buffer->isUnnamed()
2108 || (!doc_buffer->isClean()
2109 || cmd.argument() == "force"));
2112 //FIXME: This LFUN should be moved to GuiApplication.
2113 case LFUN_BUFFER_WRITE_ALL: {
2114 // We enable the command only if there are some modified buffers
2115 Buffer * first = theBufferList().first();
2120 // We cannot use a for loop as the buffer list is a cycle.
2122 if (!b->isClean()) {
2126 b = theBufferList().next(b);
2127 } while (b != first);
2131 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2132 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2135 case LFUN_BUFFER_EXPORT: {
2136 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2140 return doc_buffer->getStatus(cmd, flag);
2143 case LFUN_BUFFER_EXPORT_AS:
2144 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2149 case LFUN_BUFFER_WRITE_AS:
2150 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2151 enable = doc_buffer != nullptr;
2154 case LFUN_EXPORT_CANCEL:
2155 enable = d.processing_thread_watcher_.isRunning();
2158 case LFUN_BUFFER_CLOSE:
2159 case LFUN_VIEW_CLOSE:
2160 enable = doc_buffer != nullptr;
2163 case LFUN_BUFFER_CLOSE_ALL:
2164 enable = theBufferList().last() != theBufferList().first();
2167 case LFUN_BUFFER_CHKTEX: {
2168 // hide if we have no checktex command
2169 if (lyxrc.chktex_command.empty()) {
2170 flag.setUnknown(true);
2174 if (!doc_buffer || !doc_buffer->params().isLatex()
2175 || d.processing_thread_watcher_.isRunning()) {
2176 // grey out, don't hide
2184 case LFUN_VIEW_SPLIT:
2185 if (cmd.getArg(0) == "vertical")
2186 enable = doc_buffer && (d.splitter_->count() == 1 ||
2187 d.splitter_->orientation() == Qt::Vertical);
2189 enable = doc_buffer && (d.splitter_->count() == 1 ||
2190 d.splitter_->orientation() == Qt::Horizontal);
2193 case LFUN_TAB_GROUP_CLOSE:
2194 enable = d.tabWorkAreaCount() > 1;
2197 case LFUN_DEVEL_MODE_TOGGLE:
2198 flag.setOnOff(devel_mode_);
2201 case LFUN_TOOLBAR_SET: {
2202 string const name = cmd.getArg(0);
2203 string const state = cmd.getArg(1);
2204 if (name.empty() || state.empty()) {
2206 docstring const msg =
2207 _("Function toolbar-set requires two arguments!");
2211 if (state != "on" && state != "off" && state != "auto") {
2213 docstring const msg =
2214 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2219 if (GuiToolbar * t = toolbar(name)) {
2220 bool const autovis = t->visibility() & Toolbars::AUTO;
2222 flag.setOnOff(t->isVisible() && !autovis);
2223 else if (state == "off")
2224 flag.setOnOff(!t->isVisible() && !autovis);
2225 else if (state == "auto")
2226 flag.setOnOff(autovis);
2229 docstring const msg =
2230 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2236 case LFUN_TOOLBAR_TOGGLE: {
2237 string const name = cmd.getArg(0);
2238 if (GuiToolbar * t = toolbar(name))
2239 flag.setOnOff(t->isVisible());
2242 docstring const msg =
2243 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2249 case LFUN_TOOLBAR_MOVABLE: {
2250 string const name = cmd.getArg(0);
2251 // use negation since locked == !movable
2253 // toolbar name * locks all toolbars
2254 flag.setOnOff(!toolbarsMovable_);
2255 else if (GuiToolbar * t = toolbar(name))
2256 flag.setOnOff(!(t->isMovable()));
2259 docstring const msg =
2260 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2266 case LFUN_ICON_SIZE:
2267 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2270 case LFUN_DROP_LAYOUTS_CHOICE:
2271 enable = buf != nullptr;
2274 case LFUN_UI_TOGGLE:
2275 flag.setOnOff(isFullScreen());
2278 case LFUN_DIALOG_DISCONNECT_INSET:
2281 case LFUN_DIALOG_HIDE:
2282 // FIXME: should we check if the dialog is shown?
2285 case LFUN_DIALOG_TOGGLE:
2286 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2289 case LFUN_DIALOG_SHOW: {
2290 string const name = cmd.getArg(0);
2292 enable = name == "aboutlyx"
2293 || name == "file" //FIXME: should be removed.
2294 || name == "lyxfiles"
2296 || name == "texinfo"
2297 || name == "progress"
2298 || name == "compare";
2299 else if (name == "character" || name == "symbols"
2300 || name == "mathdelimiter" || name == "mathmatrix") {
2301 if (!buf || buf->isReadonly())
2304 Cursor const & cur = currentBufferView()->cursor();
2305 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2308 else if (name == "latexlog")
2309 enable = FileName(doc_buffer->logName()).isReadableFile();
2310 else if (name == "spellchecker")
2311 enable = theSpellChecker()
2312 && !doc_buffer->text().empty();
2313 else if (name == "vclog")
2314 enable = doc_buffer->lyxvc().inUse();
2318 case LFUN_DIALOG_UPDATE: {
2319 string const name = cmd.getArg(0);
2321 enable = name == "prefs";
2325 case LFUN_COMMAND_EXECUTE:
2327 case LFUN_MENU_OPEN:
2328 // Nothing to check.
2331 case LFUN_COMPLETION_INLINE:
2332 if (!d.current_work_area_
2333 || !d.current_work_area_->completer().inlinePossible(
2334 currentBufferView()->cursor()))
2338 case LFUN_COMPLETION_POPUP:
2339 if (!d.current_work_area_
2340 || !d.current_work_area_->completer().popupPossible(
2341 currentBufferView()->cursor()))
2346 if (!d.current_work_area_
2347 || !d.current_work_area_->completer().inlinePossible(
2348 currentBufferView()->cursor()))
2352 case LFUN_COMPLETION_ACCEPT:
2353 if (!d.current_work_area_
2354 || (!d.current_work_area_->completer().popupVisible()
2355 && !d.current_work_area_->completer().inlineVisible()
2356 && !d.current_work_area_->completer().completionAvailable()))
2360 case LFUN_COMPLETION_CANCEL:
2361 if (!d.current_work_area_
2362 || (!d.current_work_area_->completer().popupVisible()
2363 && !d.current_work_area_->completer().inlineVisible()))
2367 case LFUN_BUFFER_ZOOM_OUT:
2368 case LFUN_BUFFER_ZOOM_IN: {
2369 // only diff between these two is that the default for ZOOM_OUT
2371 bool const neg_zoom =
2372 convert<int>(cmd.argument()) < 0 ||
2373 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2374 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2375 docstring const msg =
2376 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2380 enable = doc_buffer;
2384 case LFUN_BUFFER_ZOOM: {
2385 bool const less_than_min_zoom =
2386 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2387 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2388 docstring const msg =
2389 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2394 enable = doc_buffer;
2398 case LFUN_BUFFER_MOVE_NEXT:
2399 case LFUN_BUFFER_MOVE_PREVIOUS:
2400 // we do not cycle when moving
2401 case LFUN_BUFFER_NEXT:
2402 case LFUN_BUFFER_PREVIOUS:
2403 // because we cycle, it doesn't matter whether on first or last
2404 enable = (d.currentTabWorkArea()->count() > 1);
2406 case LFUN_BUFFER_SWITCH:
2407 // toggle on the current buffer, but do not toggle off
2408 // the other ones (is that a good idea?)
2410 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2411 flag.setOnOff(true);
2414 case LFUN_VC_REGISTER:
2415 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2417 case LFUN_VC_RENAME:
2418 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2421 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2423 case LFUN_VC_CHECK_IN:
2424 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2426 case LFUN_VC_CHECK_OUT:
2427 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2429 case LFUN_VC_LOCKING_TOGGLE:
2430 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2431 && doc_buffer->lyxvc().lockingToggleEnabled();
2432 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2434 case LFUN_VC_REVERT:
2435 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2436 && !doc_buffer->hasReadonlyFlag();
2438 case LFUN_VC_UNDO_LAST:
2439 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2441 case LFUN_VC_REPO_UPDATE:
2442 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2444 case LFUN_VC_COMMAND: {
2445 if (cmd.argument().empty())
2447 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2451 case LFUN_VC_COMPARE:
2452 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2455 case LFUN_SERVER_GOTO_FILE_ROW:
2456 case LFUN_LYX_ACTIVATE:
2457 case LFUN_WINDOW_RAISE:
2459 case LFUN_FORWARD_SEARCH:
2460 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2463 case LFUN_FILE_INSERT_PLAINTEXT:
2464 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2465 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2468 case LFUN_SPELLING_CONTINUOUSLY:
2469 flag.setOnOff(lyxrc.spellcheck_continuously);
2472 case LFUN_CITATION_OPEN:
2481 flag.setEnabled(false);
2487 static FileName selectTemplateFile()
2489 FileDialog dlg(qt_("Select template file"));
2490 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2491 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2493 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2494 QStringList(qt_("LyX Documents (*.lyx)")));
2496 if (result.first == FileDialog::Later)
2498 if (result.second.isEmpty())
2500 return FileName(fromqstr(result.second));
2504 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2508 Buffer * newBuffer = nullptr;
2510 newBuffer = checkAndLoadLyXFile(filename);
2511 } catch (ExceptionMessage const &) {
2518 message(_("Document not loaded."));
2522 setBuffer(newBuffer);
2523 newBuffer->errors("Parse");
2526 theSession().lastFiles().add(filename);
2527 theSession().writeFile();
2534 void GuiView::openDocument(string const & fname)
2536 string initpath = lyxrc.document_path;
2538 if (documentBufferView()) {
2539 string const trypath = documentBufferView()->buffer().filePath();
2540 // If directory is writeable, use this as default.
2541 if (FileName(trypath).isDirWritable())
2547 if (fname.empty()) {
2548 FileDialog dlg(qt_("Select document to open"));
2549 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2550 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2552 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2553 FileDialog::Result result =
2554 dlg.open(toqstr(initpath), filter);
2556 if (result.first == FileDialog::Later)
2559 filename = fromqstr(result.second);
2561 // check selected filename
2562 if (filename.empty()) {
2563 message(_("Canceled."));
2569 // get absolute path of file and add ".lyx" to the filename if
2571 FileName const fullname =
2572 fileSearch(string(), filename, "lyx", support::may_not_exist);
2573 if (!fullname.empty())
2574 filename = fullname.absFileName();
2576 if (!fullname.onlyPath().isDirectory()) {
2577 Alert::warning(_("Invalid filename"),
2578 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2579 from_utf8(fullname.absFileName())));
2583 // if the file doesn't exist and isn't already open (bug 6645),
2584 // let the user create one
2585 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2586 !LyXVC::file_not_found_hook(fullname)) {
2587 // the user specifically chose this name. Believe him.
2588 Buffer * const b = newFile(filename, string(), true);
2594 docstring const disp_fn = makeDisplayPath(filename);
2595 message(bformat(_("Opening document %1$s..."), disp_fn));
2598 Buffer * buf = loadDocument(fullname);
2600 str2 = bformat(_("Document %1$s opened."), disp_fn);
2601 if (buf->lyxvc().inUse())
2602 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2603 " " + _("Version control detected.");
2605 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2610 // FIXME: clean that
2611 static bool import(GuiView * lv, FileName const & filename,
2612 string const & format, ErrorList & errorList)
2614 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2616 string loader_format;
2617 vector<string> loaders = theConverters().loaders();
2618 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2619 for (string const & loader : loaders) {
2620 if (!theConverters().isReachable(format, loader))
2623 string const tofile =
2624 support::changeExtension(filename.absFileName(),
2625 theFormats().extension(loader));
2626 if (theConverters().convert(nullptr, filename, FileName(tofile),
2627 filename, format, loader, errorList) != Converters::SUCCESS)
2629 loader_format = loader;
2632 if (loader_format.empty()) {
2633 frontend::Alert::error(_("Couldn't import file"),
2634 bformat(_("No information for importing the format %1$s."),
2635 translateIfPossible(theFormats().prettyName(format))));
2639 loader_format = format;
2641 if (loader_format == "lyx") {
2642 Buffer * buf = lv->loadDocument(lyxfile);
2646 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2650 bool as_paragraphs = loader_format == "textparagraph";
2651 string filename2 = (loader_format == format) ? filename.absFileName()
2652 : support::changeExtension(filename.absFileName(),
2653 theFormats().extension(loader_format));
2654 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2656 guiApp->setCurrentView(lv);
2657 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2664 void GuiView::importDocument(string const & argument)
2667 string filename = split(argument, format, ' ');
2669 LYXERR(Debug::INFO, format << " file: " << filename);
2671 // need user interaction
2672 if (filename.empty()) {
2673 string initpath = lyxrc.document_path;
2674 if (documentBufferView()) {
2675 string const trypath = documentBufferView()->buffer().filePath();
2676 // If directory is writeable, use this as default.
2677 if (FileName(trypath).isDirWritable())
2681 docstring const text = bformat(_("Select %1$s file to import"),
2682 translateIfPossible(theFormats().prettyName(format)));
2684 FileDialog dlg(toqstr(text));
2685 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2686 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2688 docstring filter = translateIfPossible(theFormats().prettyName(format));
2691 filter += from_utf8(theFormats().extensions(format));
2694 FileDialog::Result result =
2695 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2697 if (result.first == FileDialog::Later)
2700 filename = fromqstr(result.second);
2702 // check selected filename
2703 if (filename.empty())
2704 message(_("Canceled."));
2707 if (filename.empty())
2710 // get absolute path of file
2711 FileName const fullname(support::makeAbsPath(filename));
2713 // Can happen if the user entered a path into the dialog
2715 if (fullname.onlyFileName().empty()) {
2716 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2717 "Aborting import."),
2718 from_utf8(fullname.absFileName()));
2719 frontend::Alert::error(_("File name error"), msg);
2720 message(_("Canceled."));
2725 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2727 // Check if the document already is open
2728 Buffer * buf = theBufferList().getBuffer(lyxfile);
2731 if (!closeBuffer()) {
2732 message(_("Canceled."));
2737 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2739 // if the file exists already, and we didn't do
2740 // -i lyx thefile.lyx, warn
2741 if (lyxfile.exists() && fullname != lyxfile) {
2743 docstring text = bformat(_("The document %1$s already exists.\n\n"
2744 "Do you want to overwrite that document?"), displaypath);
2745 int const ret = Alert::prompt(_("Overwrite document?"),
2746 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2749 message(_("Canceled."));
2754 message(bformat(_("Importing %1$s..."), displaypath));
2755 ErrorList errorList;
2756 if (import(this, fullname, format, errorList))
2757 message(_("imported."));
2759 message(_("file not imported!"));
2761 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2765 void GuiView::newDocument(string const & filename, string templatefile,
2768 FileName initpath(lyxrc.document_path);
2769 if (documentBufferView()) {
2770 FileName const trypath(documentBufferView()->buffer().filePath());
2771 // If directory is writeable, use this as default.
2772 if (trypath.isDirWritable())
2776 if (from_template) {
2777 if (templatefile.empty())
2778 templatefile = selectTemplateFile().absFileName();
2779 if (templatefile.empty())
2784 if (filename.empty())
2785 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2787 b = newFile(filename, templatefile, true);
2792 // If no new document could be created, it is unsure
2793 // whether there is a valid BufferView.
2794 if (currentBufferView())
2795 // Ensure the cursor is correctly positioned on screen.
2796 currentBufferView()->showCursor();
2800 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2802 BufferView * bv = documentBufferView();
2807 FileName filename(to_utf8(fname));
2808 if (filename.empty()) {
2809 // Launch a file browser
2811 string initpath = lyxrc.document_path;
2812 string const trypath = bv->buffer().filePath();
2813 // If directory is writeable, use this as default.
2814 if (FileName(trypath).isDirWritable())
2818 FileDialog dlg(qt_("Select LyX document to insert"));
2819 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2820 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2822 FileDialog::Result result = dlg.open(toqstr(initpath),
2823 QStringList(qt_("LyX Documents (*.lyx)")));
2825 if (result.first == FileDialog::Later)
2829 filename.set(fromqstr(result.second));
2831 // check selected filename
2832 if (filename.empty()) {
2833 // emit message signal.
2834 message(_("Canceled."));
2839 bv->insertLyXFile(filename, ignorelang);
2840 bv->buffer().errors("Parse");
2845 string const GuiView::getTemplatesPath(Buffer & b)
2847 // We start off with the user's templates path
2848 string result = addPath(package().user_support().absFileName(), "templates");
2849 // Check for the document language
2850 string const langcode = b.params().language->code();
2851 string const shortcode = langcode.substr(0, 2);
2852 if (!langcode.empty() && shortcode != "en") {
2853 string subpath = addPath(result, shortcode);
2854 string subpath_long = addPath(result, langcode);
2855 // If we have a subdirectory for the language already,
2857 FileName sp = FileName(subpath);
2858 if (sp.isDirectory())
2860 else if (FileName(subpath_long).isDirectory())
2861 result = subpath_long;
2863 // Ask whether we should create such a subdirectory
2864 docstring const text =
2865 bformat(_("It is suggested to save the template in a subdirectory\n"
2866 "appropriate to the document language (%1$s).\n"
2867 "This subdirectory does not exists yet.\n"
2868 "Do you want to create it?"),
2869 _(b.params().language->display()));
2870 if (Alert::prompt(_("Create Language Directory?"),
2871 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2872 // If the user agreed, we try to create it and report if this failed.
2873 if (!sp.createDirectory(0777))
2874 Alert::error(_("Subdirectory creation failed!"),
2875 _("Could not create subdirectory.\n"
2876 "The template will be saved in the parent directory."));
2882 // Do we have a layout category?
2883 string const cat = b.params().baseClass() ?
2884 b.params().baseClass()->category()
2887 string subpath = addPath(result, cat);
2888 // If we have a subdirectory for the category already,
2890 FileName sp = FileName(subpath);
2891 if (sp.isDirectory())
2894 // Ask whether we should create such a subdirectory
2895 docstring const text =
2896 bformat(_("It is suggested to save the template in a subdirectory\n"
2897 "appropriate to the layout category (%1$s).\n"
2898 "This subdirectory does not exists yet.\n"
2899 "Do you want to create it?"),
2901 if (Alert::prompt(_("Create Category Directory?"),
2902 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2903 // If the user agreed, we try to create it and report if this failed.
2904 if (!sp.createDirectory(0777))
2905 Alert::error(_("Subdirectory creation failed!"),
2906 _("Could not create subdirectory.\n"
2907 "The template will be saved in the parent directory."));
2917 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2919 FileName fname = b.fileName();
2920 FileName const oldname = fname;
2921 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2923 if (!newname.empty()) {
2926 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2928 fname = support::makeAbsPath(to_utf8(newname),
2929 oldname.onlyPath().absFileName());
2931 // Switch to this Buffer.
2934 // No argument? Ask user through dialog.
2936 QString const title = as_template ? qt_("Choose a filename to save template as")
2937 : qt_("Choose a filename to save document as");
2938 FileDialog dlg(title);
2939 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2940 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2942 if (!isLyXFileName(fname.absFileName()))
2943 fname.changeExtension(".lyx");
2945 string const path = as_template ?
2947 : fname.onlyPath().absFileName();
2948 FileDialog::Result result =
2949 dlg.save(toqstr(path),
2950 QStringList(qt_("LyX Documents (*.lyx)")),
2951 toqstr(fname.onlyFileName()));
2953 if (result.first == FileDialog::Later)
2956 fname.set(fromqstr(result.second));
2961 if (!isLyXFileName(fname.absFileName()))
2962 fname.changeExtension(".lyx");
2965 // fname is now the new Buffer location.
2967 // if there is already a Buffer open with this name, we do not want
2968 // to have another one. (the second test makes sure we're not just
2969 // trying to overwrite ourselves, which is fine.)
2970 if (theBufferList().exists(fname) && fname != oldname
2971 && theBufferList().getBuffer(fname) != &b) {
2972 docstring const text =
2973 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2974 "Please close it before attempting to overwrite it.\n"
2975 "Do you want to choose a new filename?"),
2976 from_utf8(fname.absFileName()));
2977 int const ret = Alert::prompt(_("Chosen File Already Open"),
2978 text, 0, 1, _("&Rename"), _("&Cancel"));
2980 case 0: return renameBuffer(b, docstring(), kind);
2981 case 1: return false;
2986 bool const existsLocal = fname.exists();
2987 bool const existsInVC = LyXVC::fileInVC(fname);
2988 if (existsLocal || existsInVC) {
2989 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2990 if (kind != LV_WRITE_AS && existsInVC) {
2991 // renaming to a name that is already in VC
2993 docstring text = bformat(_("The document %1$s "
2994 "is already registered.\n\n"
2995 "Do you want to choose a new name?"),
2997 docstring const title = (kind == LV_VC_RENAME) ?
2998 _("Rename document?") : _("Copy document?");
2999 docstring const button = (kind == LV_VC_RENAME) ?
3000 _("&Rename") : _("&Copy");
3001 int const ret = Alert::prompt(title, text, 0, 1,
3002 button, _("&Cancel"));
3004 case 0: return renameBuffer(b, docstring(), kind);
3005 case 1: return false;
3010 docstring text = bformat(_("The document %1$s "
3011 "already exists.\n\n"
3012 "Do you want to overwrite that document?"),
3014 int const ret = Alert::prompt(_("Overwrite document?"),
3015 text, 0, 2, _("&Overwrite"),
3016 _("&Rename"), _("&Cancel"));
3019 case 1: return renameBuffer(b, docstring(), kind);
3020 case 2: return false;
3026 case LV_VC_RENAME: {
3027 string msg = b.lyxvc().rename(fname);
3030 message(from_utf8(msg));
3034 string msg = b.lyxvc().copy(fname);
3037 message(from_utf8(msg));
3041 case LV_WRITE_AS_TEMPLATE:
3044 // LyXVC created the file already in case of LV_VC_RENAME or
3045 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3046 // relative paths of included stuff right if we moved e.g. from
3047 // /a/b.lyx to /a/c/b.lyx.
3049 bool const saved = saveBuffer(b, fname);
3056 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3058 FileName fname = b.fileName();
3060 FileDialog dlg(qt_("Choose a filename to export the document as"));
3061 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3064 QString const anyformat = qt_("Guess from extension (*.*)");
3067 vector<Format const *> export_formats;
3068 for (Format const & f : theFormats())
3069 if (f.documentFormat())
3070 export_formats.push_back(&f);
3071 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3072 map<QString, string> fmap;
3075 for (Format const * f : export_formats) {
3076 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3077 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3079 from_ascii(f->extension())));
3080 types << loc_filter;
3081 fmap[loc_filter] = f->name();
3082 if (from_ascii(f->name()) == iformat) {
3083 filter = loc_filter;
3084 ext = f->extension();
3087 string ofname = fname.onlyFileName();
3089 ofname = support::changeExtension(ofname, ext);
3090 FileDialog::Result result =
3091 dlg.save(toqstr(fname.onlyPath().absFileName()),
3095 if (result.first != FileDialog::Chosen)
3099 fname.set(fromqstr(result.second));
3100 if (filter == anyformat)
3101 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3103 fmt_name = fmap[filter];
3104 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3105 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3107 if (fmt_name.empty() || fname.empty())
3110 // fname is now the new Buffer location.
3111 if (FileName(fname).exists()) {
3112 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3113 docstring text = bformat(_("The document %1$s already "
3114 "exists.\n\nDo you want to "
3115 "overwrite that document?"),
3117 int const ret = Alert::prompt(_("Overwrite document?"),
3118 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3121 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3122 case 2: return false;
3126 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3129 return dr.dispatched();
3133 bool GuiView::saveBuffer(Buffer & b)
3135 return saveBuffer(b, FileName());
3139 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3141 if (workArea(b) && workArea(b)->inDialogMode())
3144 if (fn.empty() && b.isUnnamed())
3145 return renameBuffer(b, docstring());
3147 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3149 theSession().lastFiles().add(b.fileName());
3150 theSession().writeFile();
3154 // Switch to this Buffer.
3157 // FIXME: we don't tell the user *WHY* the save failed !!
3158 docstring const file = makeDisplayPath(b.absFileName(), 30);
3159 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3160 "Do you want to rename the document and "
3161 "try again?"), file);
3162 int const ret = Alert::prompt(_("Rename and save?"),
3163 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3166 if (!renameBuffer(b, docstring()))
3175 return saveBuffer(b, fn);
3179 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3181 return closeWorkArea(wa, false);
3185 // We only want to close the buffer if it is not visible in other workareas
3186 // of the same view, nor in other views, and if this is not a child
3187 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3189 Buffer & buf = wa->bufferView().buffer();
3191 bool last_wa = d.countWorkAreasOf(buf) == 1
3192 && !inOtherView(buf) && !buf.parent();
3194 bool close_buffer = last_wa;
3197 if (lyxrc.close_buffer_with_last_view == "yes")
3199 else if (lyxrc.close_buffer_with_last_view == "no")
3200 close_buffer = false;
3203 if (buf.isUnnamed())
3204 file = from_utf8(buf.fileName().onlyFileName());
3206 file = buf.fileName().displayName(30);
3207 docstring const text = bformat(
3208 _("Last view on document %1$s is being closed.\n"
3209 "Would you like to close or hide the document?\n"
3211 "Hidden documents can be displayed back through\n"
3212 "the menu: View->Hidden->...\n"
3214 "To remove this question, set your preference in:\n"
3215 " Tools->Preferences->Look&Feel->UserInterface\n"
3217 int ret = Alert::prompt(_("Close or hide document?"),
3218 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3221 close_buffer = (ret == 0);
3225 return closeWorkArea(wa, close_buffer);
3229 bool GuiView::closeBuffer()
3231 GuiWorkArea * wa = currentMainWorkArea();
3232 // coverity complained about this
3233 // it seems unnecessary, but perhaps is worth the check
3234 LASSERT(wa, return false);
3236 setCurrentWorkArea(wa);
3237 Buffer & buf = wa->bufferView().buffer();
3238 return closeWorkArea(wa, !buf.parent());
3242 void GuiView::writeSession() const {
3243 GuiWorkArea const * active_wa = currentMainWorkArea();
3244 for (int i = 0; i < d.splitter_->count(); ++i) {
3245 TabWorkArea * twa = d.tabWorkArea(i);
3246 for (int j = 0; j < twa->count(); ++j) {
3247 GuiWorkArea * wa = twa->workArea(j);
3248 Buffer & buf = wa->bufferView().buffer();
3249 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3255 bool GuiView::closeBufferAll()
3258 for (auto & buf : theBufferList()) {
3259 if (!saveBufferIfNeeded(*buf, false)) {
3260 // Closing has been cancelled, so abort.
3265 // Close the workareas in all other views
3266 QList<int> const ids = guiApp->viewIds();
3267 for (int i = 0; i != ids.size(); ++i) {
3268 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3272 // Close our own workareas
3273 if (!closeWorkAreaAll())
3280 bool GuiView::closeWorkAreaAll()
3282 setCurrentWorkArea(currentMainWorkArea());
3284 // We might be in a situation that there is still a tabWorkArea, but
3285 // there are no tabs anymore. This can happen when we get here after a
3286 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3287 // many TabWorkArea's have no documents anymore.
3290 // We have to call count() each time, because it can happen that
3291 // more than one splitter will disappear in one iteration (bug 5998).
3292 while (d.splitter_->count() > empty_twa) {
3293 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3295 if (twa->count() == 0)
3298 setCurrentWorkArea(twa->currentWorkArea());
3299 if (!closeTabWorkArea(twa))
3307 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3312 Buffer & buf = wa->bufferView().buffer();
3314 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3315 Alert::warning(_("Close document"),
3316 _("Document could not be closed because it is being processed by LyX."));
3321 return closeBuffer(buf);
3323 if (!inMultiTabs(wa))
3324 if (!saveBufferIfNeeded(buf, true))
3332 bool GuiView::closeBuffer(Buffer & buf)
3334 bool success = true;
3335 for (Buffer * child_buf : buf.getChildren()) {
3336 if (theBufferList().isOthersChild(&buf, child_buf)) {
3337 child_buf->setParent(nullptr);
3341 // FIXME: should we look in other tabworkareas?
3342 // ANSWER: I don't think so. I've tested, and if the child is
3343 // open in some other window, it closes without a problem.
3344 GuiWorkArea * child_wa = workArea(*child_buf);
3347 // If we are in a close_event all children will be closed in some time,
3348 // so no need to do it here. This will ensure that the children end up
3349 // in the session file in the correct order. If we close the master
3350 // buffer, we can close or release the child buffers here too.
3352 success = closeWorkArea(child_wa, true);
3356 // In this case the child buffer is open but hidden.
3357 // Even in this case, children can be dirty (e.g.,
3358 // after a label change in the master, see #11405).
3359 // Therefore, check this
3360 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3361 // If we are in a close_event all children will be closed in some time,
3362 // so no need to do it here. This will ensure that the children end up
3363 // in the session file in the correct order. If we close the master
3364 // buffer, we can close or release the child buffers here too.
3367 // Save dirty buffers also if closing_!
3368 if (saveBufferIfNeeded(*child_buf, false)) {
3369 child_buf->removeAutosaveFile();
3370 theBufferList().release(child_buf);
3372 // Saving of dirty children has been cancelled.
3373 // Cancel the whole process.
3380 // goto bookmark to update bookmark pit.
3381 // FIXME: we should update only the bookmarks related to this buffer!
3382 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3383 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3384 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3385 guiApp->gotoBookmark(i, false, false);
3387 if (saveBufferIfNeeded(buf, false)) {
3388 buf.removeAutosaveFile();
3389 theBufferList().release(&buf);
3393 // open all children again to avoid a crash because of dangling
3394 // pointers (bug 6603)
3400 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3402 while (twa == d.currentTabWorkArea()) {
3403 twa->setCurrentIndex(twa->count() - 1);
3405 GuiWorkArea * wa = twa->currentWorkArea();
3406 Buffer & b = wa->bufferView().buffer();
3408 // We only want to close the buffer if the same buffer is not visible
3409 // in another view, and if this is not a child and if we are closing
3410 // a view (not a tabgroup).
3411 bool const close_buffer =
3412 !inOtherView(b) && !b.parent() && closing_;
3414 if (!closeWorkArea(wa, close_buffer))
3421 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3423 if (buf.isClean() || buf.paragraphs().empty())
3426 // Switch to this Buffer.
3432 if (buf.isUnnamed()) {
3433 file = from_utf8(buf.fileName().onlyFileName());
3436 FileName filename = buf.fileName();
3438 file = filename.displayName(30);
3439 exists = filename.exists();
3442 // Bring this window to top before asking questions.
3447 if (hiding && buf.isUnnamed()) {
3448 docstring const text = bformat(_("The document %1$s has not been "
3449 "saved yet.\n\nDo you want to save "
3450 "the document?"), file);
3451 ret = Alert::prompt(_("Save new document?"),
3452 text, 0, 1, _("&Save"), _("&Cancel"));
3456 docstring const text = exists ?
3457 bformat(_("The document %1$s has unsaved changes."
3458 "\n\nDo you want to save the document or "
3459 "discard the changes?"), file) :
3460 bformat(_("The document %1$s has not been saved yet."
3461 "\n\nDo you want to save the document or "
3462 "discard it entirely?"), file);
3463 docstring const title = exists ?
3464 _("Save changed document?") : _("Save document?");
3465 ret = Alert::prompt(title, text, 0, 2,
3466 _("&Save"), _("&Discard"), _("&Cancel"));
3471 if (!saveBuffer(buf))
3475 // If we crash after this we could have no autosave file
3476 // but I guess this is really improbable (Jug).
3477 // Sometimes improbable things happen:
3478 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3479 // buf.removeAutosaveFile();
3481 // revert all changes
3492 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3494 Buffer & buf = wa->bufferView().buffer();
3496 for (int i = 0; i != d.splitter_->count(); ++i) {
3497 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3498 if (wa_ && wa_ != wa)
3501 return inOtherView(buf);
3505 bool GuiView::inOtherView(Buffer & buf)
3507 QList<int> const ids = guiApp->viewIds();
3509 for (int i = 0; i != ids.size(); ++i) {
3513 if (guiApp->view(ids[i]).workArea(buf))
3520 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3522 if (!documentBufferView())
3525 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3526 Buffer * const curbuf = &documentBufferView()->buffer();
3527 int nwa = twa->count();
3528 for (int i = 0; i < nwa; ++i) {
3529 if (&workArea(i)->bufferView().buffer() == curbuf) {
3531 if (np == NEXTBUFFER)
3532 next_index = (i == nwa - 1 ? 0 : i + 1);
3534 next_index = (i == 0 ? nwa - 1 : i - 1);
3536 twa->moveTab(i, next_index);
3538 setBuffer(&workArea(next_index)->bufferView().buffer());
3546 /// make sure the document is saved
3547 static bool ensureBufferClean(Buffer * buffer)
3549 LASSERT(buffer, return false);
3550 if (buffer->isClean() && !buffer->isUnnamed())
3553 docstring const file = buffer->fileName().displayName(30);
3556 if (!buffer->isUnnamed()) {
3557 text = bformat(_("The document %1$s has unsaved "
3558 "changes.\n\nDo you want to save "
3559 "the document?"), file);
3560 title = _("Save changed document?");
3563 text = bformat(_("The document %1$s has not been "
3564 "saved yet.\n\nDo you want to save "
3565 "the document?"), file);
3566 title = _("Save new document?");
3568 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3571 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3573 return buffer->isClean() && !buffer->isUnnamed();
3577 bool GuiView::reloadBuffer(Buffer & buf)
3579 currentBufferView()->cursor().reset();
3580 Buffer::ReadStatus status = buf.reload();
3581 return status == Buffer::ReadSuccess;
3585 void GuiView::checkExternallyModifiedBuffers()
3587 for (Buffer * buf : theBufferList()) {
3588 if (buf->fileName().exists() && buf->isChecksumModified()) {
3589 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3590 " Reload now? Any local changes will be lost."),
3591 from_utf8(buf->absFileName()));
3592 int const ret = Alert::prompt(_("Reload externally changed document?"),
3593 text, 0, 1, _("&Reload"), _("&Cancel"));
3601 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3603 Buffer * buffer = documentBufferView()
3604 ? &(documentBufferView()->buffer()) : nullptr;
3606 switch (cmd.action()) {
3607 case LFUN_VC_REGISTER:
3608 if (!buffer || !ensureBufferClean(buffer))
3610 if (!buffer->lyxvc().inUse()) {
3611 if (buffer->lyxvc().registrer()) {
3612 reloadBuffer(*buffer);
3613 dr.clearMessageUpdate();
3618 case LFUN_VC_RENAME:
3619 case LFUN_VC_COPY: {
3620 if (!buffer || !ensureBufferClean(buffer))
3622 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3623 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3624 // Some changes are not yet committed.
3625 // We test here and not in getStatus(), since
3626 // this test is expensive.
3628 LyXVC::CommandResult ret =
3629 buffer->lyxvc().checkIn(log);
3631 if (ret == LyXVC::ErrorCommand ||
3632 ret == LyXVC::VCSuccess)
3633 reloadBuffer(*buffer);
3634 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3635 frontend::Alert::error(
3636 _("Revision control error."),
3637 _("Document could not be checked in."));
3641 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3642 LV_VC_RENAME : LV_VC_COPY;
3643 renameBuffer(*buffer, cmd.argument(), kind);
3648 case LFUN_VC_CHECK_IN:
3649 if (!buffer || !ensureBufferClean(buffer))
3651 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3653 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3655 // Only skip reloading if the checkin was cancelled or
3656 // an error occurred before the real checkin VCS command
3657 // was executed, since the VCS might have changed the
3658 // file even if it could not checkin successfully.
3659 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3660 reloadBuffer(*buffer);
3664 case LFUN_VC_CHECK_OUT:
3665 if (!buffer || !ensureBufferClean(buffer))
3667 if (buffer->lyxvc().inUse()) {
3668 dr.setMessage(buffer->lyxvc().checkOut());
3669 reloadBuffer(*buffer);
3673 case LFUN_VC_LOCKING_TOGGLE:
3674 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3676 if (buffer->lyxvc().inUse()) {
3677 string res = buffer->lyxvc().lockingToggle();
3679 frontend::Alert::error(_("Revision control error."),
3680 _("Error when setting the locking property."));
3683 reloadBuffer(*buffer);
3688 case LFUN_VC_REVERT:
3691 if (buffer->lyxvc().revert()) {
3692 reloadBuffer(*buffer);
3693 dr.clearMessageUpdate();
3697 case LFUN_VC_UNDO_LAST:
3700 buffer->lyxvc().undoLast();
3701 reloadBuffer(*buffer);
3702 dr.clearMessageUpdate();
3705 case LFUN_VC_REPO_UPDATE:
3708 if (ensureBufferClean(buffer)) {
3709 dr.setMessage(buffer->lyxvc().repoUpdate());
3710 checkExternallyModifiedBuffers();
3714 case LFUN_VC_COMMAND: {
3715 string flag = cmd.getArg(0);
3716 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3719 if (contains(flag, 'M')) {
3720 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3723 string path = cmd.getArg(1);
3724 if (contains(path, "$$p") && buffer)
3725 path = subst(path, "$$p", buffer->filePath());
3726 LYXERR(Debug::LYXVC, "Directory: " << path);
3728 if (!pp.isReadableDirectory()) {
3729 lyxerr << _("Directory is not accessible.") << endl;
3732 support::PathChanger p(pp);
3734 string command = cmd.getArg(2);
3735 if (command.empty())
3738 command = subst(command, "$$i", buffer->absFileName());
3739 command = subst(command, "$$p", buffer->filePath());
3741 command = subst(command, "$$m", to_utf8(message));
3742 LYXERR(Debug::LYXVC, "Command: " << command);
3744 one.startscript(Systemcall::Wait, command);
3748 if (contains(flag, 'I'))
3749 buffer->markDirty();
3750 if (contains(flag, 'R'))
3751 reloadBuffer(*buffer);
3756 case LFUN_VC_COMPARE: {
3757 if (cmd.argument().empty()) {
3758 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3764 string rev1 = cmd.getArg(0);
3768 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3771 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3772 f2 = buffer->absFileName();
3774 string rev2 = cmd.getArg(1);
3778 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3782 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3783 f1 << "\n" << f2 << "\n" );
3784 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3785 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3795 void GuiView::openChildDocument(string const & fname)
3797 LASSERT(documentBufferView(), return);
3798 Buffer & buffer = documentBufferView()->buffer();
3799 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3800 documentBufferView()->saveBookmark(false);
3801 Buffer * child = nullptr;
3802 if (theBufferList().exists(filename)) {
3803 child = theBufferList().getBuffer(filename);
3806 message(bformat(_("Opening child document %1$s..."),
3807 makeDisplayPath(filename.absFileName())));
3808 child = loadDocument(filename, false);
3810 // Set the parent name of the child document.
3811 // This makes insertion of citations and references in the child work,
3812 // when the target is in the parent or another child document.
3814 child->setParent(&buffer);
3818 bool GuiView::goToFileRow(string const & argument)
3822 size_t i = argument.find_last_of(' ');
3823 if (i != string::npos) {
3824 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3825 istringstream is(argument.substr(i + 1));
3830 if (i == string::npos) {
3831 LYXERR0("Wrong argument: " << argument);
3834 Buffer * buf = nullptr;
3835 string const realtmp = package().temp_dir().realPath();
3836 // We have to use os::path_prefix_is() here, instead of
3837 // simply prefixIs(), because the file name comes from
3838 // an external application and may need case adjustment.
3839 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3840 buf = theBufferList().getBufferFromTmp(file_name, true);
3841 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3842 << (buf ? " success" : " failed"));
3844 // Must replace extension of the file to be .lyx
3845 // and get full path
3846 FileName const s = fileSearch(string(),
3847 support::changeExtension(file_name, ".lyx"), "lyx");
3848 // Either change buffer or load the file
3849 if (theBufferList().exists(s))
3850 buf = theBufferList().getBuffer(s);
3851 else if (s.exists()) {
3852 buf = loadDocument(s);
3857 _("File does not exist: %1$s"),
3858 makeDisplayPath(file_name)));
3864 _("No buffer for file: %1$s."),
3865 makeDisplayPath(file_name))
3870 bool success = documentBufferView()->setCursorFromRow(row);
3872 LYXERR(Debug::LATEX,
3873 "setCursorFromRow: invalid position for row " << row);
3874 frontend::Alert::error(_("Inverse Search Failed"),
3875 _("Invalid position requested by inverse search.\n"
3876 "You may need to update the viewed document."));
3882 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3884 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3885 menu->exec(QCursor::pos());
3890 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3891 Buffer const * orig, Buffer * clone, string const & format)
3893 Buffer::ExportStatus const status = func(format);
3895 // the cloning operation will have produced a clone of the entire set of
3896 // documents, starting from the master. so we must delete those.
3897 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3899 busyBuffers.remove(orig);
3904 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3905 Buffer const * orig, Buffer * clone, string const & format)
3907 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3909 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3913 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3914 Buffer const * orig, Buffer * clone, string const & format)
3916 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3918 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3922 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3923 Buffer const * orig, Buffer * clone, string const & format)
3925 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3927 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3931 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3932 Buffer const * used_buffer,
3933 docstring const & msg,
3934 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3935 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3936 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3937 bool allow_async, bool use_tmpdir)
3942 string format = argument;
3944 format = used_buffer->params().getDefaultOutputFormat();
3945 processing_format = format;
3947 progress_->clearMessages();
3950 #if EXPORT_in_THREAD
3952 GuiViewPrivate::busyBuffers.insert(used_buffer);
3953 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3954 if (!cloned_buffer) {
3955 Alert::error(_("Export Error"),
3956 _("Error cloning the Buffer."));
3959 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3964 setPreviewFuture(f);
3965 last_export_format = used_buffer->params().bufferFormat();
3968 // We are asynchronous, so we don't know here anything about the success
3971 Buffer::ExportStatus status;
3973 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3974 } else if (previewFunc) {
3975 status = (used_buffer->*previewFunc)(format);
3978 handleExportStatus(gv_, status, format);
3980 return (status == Buffer::ExportSuccess
3981 || status == Buffer::PreviewSuccess);
3985 Buffer::ExportStatus status;
3987 status = (used_buffer->*syncFunc)(format, true);
3988 } else if (previewFunc) {
3989 status = (used_buffer->*previewFunc)(format);
3992 handleExportStatus(gv_, status, format);
3994 return (status == Buffer::ExportSuccess
3995 || status == Buffer::PreviewSuccess);
3999 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4001 BufferView * bv = currentBufferView();
4002 LASSERT(bv, return);
4004 // Let the current BufferView dispatch its own actions.
4005 bv->dispatch(cmd, dr);
4006 if (dr.dispatched()) {
4007 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4008 updateDialog("document", "");
4012 // Try with the document BufferView dispatch if any.
4013 BufferView * doc_bv = documentBufferView();
4014 if (doc_bv && doc_bv != bv) {
4015 doc_bv->dispatch(cmd, dr);
4016 if (dr.dispatched()) {
4017 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4018 updateDialog("document", "");
4023 // Then let the current Cursor dispatch its own actions.
4024 bv->cursor().dispatch(cmd);
4026 // update completion. We do it here and not in
4027 // processKeySym to avoid another redraw just for a
4028 // changed inline completion
4029 if (cmd.origin() == FuncRequest::KEYBOARD) {
4030 if (cmd.action() == LFUN_SELF_INSERT
4031 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4032 updateCompletion(bv->cursor(), true, true);
4033 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4034 updateCompletion(bv->cursor(), false, true);
4036 updateCompletion(bv->cursor(), false, false);
4039 dr = bv->cursor().result();
4043 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4045 BufferView * bv = currentBufferView();
4046 // By default we won't need any update.
4047 dr.screenUpdate(Update::None);
4048 // assume cmd will be dispatched
4049 dr.dispatched(true);
4051 Buffer * doc_buffer = documentBufferView()
4052 ? &(documentBufferView()->buffer()) : nullptr;
4054 if (cmd.origin() == FuncRequest::TOC) {
4055 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4056 toc->doDispatch(bv->cursor(), cmd, dr);
4060 string const argument = to_utf8(cmd.argument());
4062 switch(cmd.action()) {
4063 case LFUN_BUFFER_CHILD_OPEN:
4064 openChildDocument(to_utf8(cmd.argument()));
4067 case LFUN_BUFFER_IMPORT:
4068 importDocument(to_utf8(cmd.argument()));
4071 case LFUN_MASTER_BUFFER_EXPORT:
4073 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4075 case LFUN_BUFFER_EXPORT: {
4078 // GCC only sees strfwd.h when building merged
4079 if (::lyx::operator==(cmd.argument(), "custom")) {
4080 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4081 // so the following test should not be needed.
4082 // In principle, we could try to switch to such a view...
4083 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4084 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4088 string const dest = cmd.getArg(1);
4089 FileName target_dir;
4090 if (!dest.empty() && FileName::isAbsolute(dest))
4091 target_dir = FileName(support::onlyPath(dest));
4093 target_dir = doc_buffer->fileName().onlyPath();
4095 string const format = (argument.empty() || argument == "default") ?
4096 doc_buffer->params().getDefaultOutputFormat() : argument;
4098 if ((dest.empty() && doc_buffer->isUnnamed())
4099 || !target_dir.isDirWritable()) {
4100 exportBufferAs(*doc_buffer, from_utf8(format));
4103 /* TODO/Review: Is it a problem to also export the children?
4104 See the update_unincluded flag */
4105 d.asyncBufferProcessing(format,
4108 &GuiViewPrivate::exportAndDestroy,
4110 nullptr, cmd.allowAsync());
4111 // TODO Inform user about success
4115 case LFUN_BUFFER_EXPORT_AS: {
4116 LASSERT(doc_buffer, break);
4117 docstring f = cmd.argument();
4119 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4120 exportBufferAs(*doc_buffer, f);
4124 case LFUN_BUFFER_UPDATE: {
4125 d.asyncBufferProcessing(argument,
4128 &GuiViewPrivate::compileAndDestroy,
4130 nullptr, cmd.allowAsync(), true);
4133 case LFUN_BUFFER_VIEW: {
4134 d.asyncBufferProcessing(argument,
4136 _("Previewing ..."),
4137 &GuiViewPrivate::previewAndDestroy,
4139 &Buffer::preview, cmd.allowAsync());
4142 case LFUN_MASTER_BUFFER_UPDATE: {
4143 d.asyncBufferProcessing(argument,
4144 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4146 &GuiViewPrivate::compileAndDestroy,
4148 nullptr, cmd.allowAsync(), true);
4151 case LFUN_MASTER_BUFFER_VIEW: {
4152 d.asyncBufferProcessing(argument,
4153 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4155 &GuiViewPrivate::previewAndDestroy,
4156 nullptr, &Buffer::preview, cmd.allowAsync());
4159 case LFUN_EXPORT_CANCEL: {
4160 Systemcall::killscript();
4163 case LFUN_BUFFER_SWITCH: {
4164 string const file_name = to_utf8(cmd.argument());
4165 if (!FileName::isAbsolute(file_name)) {
4167 dr.setMessage(_("Absolute filename expected."));
4171 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4174 dr.setMessage(_("Document not loaded"));
4178 // Do we open or switch to the buffer in this view ?
4179 if (workArea(*buffer)
4180 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4185 // Look for the buffer in other views
4186 QList<int> const ids = guiApp->viewIds();
4188 for (; i != ids.size(); ++i) {
4189 GuiView & gv = guiApp->view(ids[i]);
4190 if (gv.workArea(*buffer)) {
4192 gv.activateWindow();
4194 gv.setBuffer(buffer);
4199 // If necessary, open a new window as a last resort
4200 if (i == ids.size()) {
4201 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4207 case LFUN_BUFFER_NEXT:
4208 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4211 case LFUN_BUFFER_MOVE_NEXT:
4212 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4215 case LFUN_BUFFER_PREVIOUS:
4216 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4219 case LFUN_BUFFER_MOVE_PREVIOUS:
4220 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4223 case LFUN_BUFFER_CHKTEX:
4224 LASSERT(doc_buffer, break);
4225 doc_buffer->runChktex();
4228 case LFUN_COMMAND_EXECUTE: {
4229 command_execute_ = true;
4230 minibuffer_focus_ = true;
4233 case LFUN_DROP_LAYOUTS_CHOICE:
4234 d.layout_->showPopup();
4237 case LFUN_MENU_OPEN:
4238 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4239 menu->exec(QCursor::pos());
4242 case LFUN_FILE_INSERT: {
4243 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4244 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4245 dr.forceBufferUpdate();
4246 dr.screenUpdate(Update::Force);
4251 case LFUN_FILE_INSERT_PLAINTEXT:
4252 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4253 string const fname = to_utf8(cmd.argument());
4254 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4255 dr.setMessage(_("Absolute filename expected."));
4259 FileName filename(fname);
4260 if (fname.empty()) {
4261 FileDialog dlg(qt_("Select file to insert"));
4263 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4264 QStringList(qt_("All Files (*)")));
4266 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4267 dr.setMessage(_("Canceled."));
4271 filename.set(fromqstr(result.second));
4275 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4276 bv->dispatch(new_cmd, dr);
4281 case LFUN_BUFFER_RELOAD: {
4282 LASSERT(doc_buffer, break);
4285 bool drop = (cmd.argument() == "dump");
4288 if (!drop && !doc_buffer->isClean()) {
4289 docstring const file =
4290 makeDisplayPath(doc_buffer->absFileName(), 20);
4291 if (doc_buffer->notifiesExternalModification()) {
4292 docstring text = _("The current version will be lost. "
4293 "Are you sure you want to load the version on disk "
4294 "of the document %1$s?");
4295 ret = Alert::prompt(_("Reload saved document?"),
4296 bformat(text, file), 1, 1,
4297 _("&Reload"), _("&Cancel"));
4299 docstring text = _("Any changes will be lost. "
4300 "Are you sure you want to revert to the saved version "
4301 "of the document %1$s?");
4302 ret = Alert::prompt(_("Revert to saved document?"),
4303 bformat(text, file), 1, 1,
4304 _("&Revert"), _("&Cancel"));
4309 doc_buffer->markClean();
4310 reloadBuffer(*doc_buffer);
4311 dr.forceBufferUpdate();
4316 case LFUN_BUFFER_RESET_EXPORT:
4317 LASSERT(doc_buffer, break);
4318 doc_buffer->requireFreshStart(true);
4319 dr.setMessage(_("Buffer export reset."));
4322 case LFUN_BUFFER_WRITE:
4323 LASSERT(doc_buffer, break);
4324 saveBuffer(*doc_buffer);
4327 case LFUN_BUFFER_WRITE_AS:
4328 LASSERT(doc_buffer, break);
4329 renameBuffer(*doc_buffer, cmd.argument());
4332 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4333 LASSERT(doc_buffer, break);
4334 renameBuffer(*doc_buffer, cmd.argument(),
4335 LV_WRITE_AS_TEMPLATE);
4338 case LFUN_BUFFER_WRITE_ALL: {
4339 Buffer * first = theBufferList().first();
4342 message(_("Saving all documents..."));
4343 // We cannot use a for loop as the buffer list cycles.
4346 if (!b->isClean()) {
4348 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4350 b = theBufferList().next(b);
4351 } while (b != first);
4352 dr.setMessage(_("All documents saved."));
4356 case LFUN_MASTER_BUFFER_FORALL: {
4360 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4361 funcToRun.allowAsync(false);
4363 for (Buffer const * buf : doc_buffer->allRelatives()) {
4364 // Switch to other buffer view and resend cmd
4365 lyx::dispatch(FuncRequest(
4366 LFUN_BUFFER_SWITCH, buf->absFileName()));
4367 lyx::dispatch(funcToRun);
4370 lyx::dispatch(FuncRequest(
4371 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4375 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4376 LASSERT(doc_buffer, break);
4377 doc_buffer->clearExternalModification();
4380 case LFUN_BUFFER_CLOSE:
4384 case LFUN_BUFFER_CLOSE_ALL:
4388 case LFUN_DEVEL_MODE_TOGGLE:
4389 devel_mode_ = !devel_mode_;
4391 dr.setMessage(_("Developer mode is now enabled."));
4393 dr.setMessage(_("Developer mode is now disabled."));
4396 case LFUN_TOOLBAR_SET: {
4397 string const name = cmd.getArg(0);
4398 string const state = cmd.getArg(1);
4399 if (GuiToolbar * t = toolbar(name))
4404 case LFUN_TOOLBAR_TOGGLE: {
4405 string const name = cmd.getArg(0);
4406 if (GuiToolbar * t = toolbar(name))
4411 case LFUN_TOOLBAR_MOVABLE: {
4412 string const name = cmd.getArg(0);
4414 // toggle (all) toolbars movablility
4415 toolbarsMovable_ = !toolbarsMovable_;
4416 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4417 GuiToolbar * tb = toolbar(ti.name);
4418 if (tb && tb->isMovable() != toolbarsMovable_)
4419 // toggle toolbar movablity if it does not fit lock
4420 // (all) toolbars positions state silent = true, since
4421 // status bar notifications are slow
4424 if (toolbarsMovable_)
4425 dr.setMessage(_("Toolbars unlocked."));
4427 dr.setMessage(_("Toolbars locked."));
4428 } else if (GuiToolbar * t = toolbar(name)) {
4429 // toggle current toolbar movablity
4431 // update lock (all) toolbars positions
4432 updateLockToolbars();
4437 case LFUN_ICON_SIZE: {
4438 QSize size = d.iconSize(cmd.argument());
4440 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4441 size.width(), size.height()));
4445 case LFUN_DIALOG_UPDATE: {
4446 string const name = to_utf8(cmd.argument());
4447 if (name == "prefs" || name == "document")
4448 updateDialog(name, string());
4449 else if (name == "paragraph")
4450 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4451 else if (currentBufferView()) {
4452 Inset * inset = currentBufferView()->editedInset(name);
4453 // Can only update a dialog connected to an existing inset
4455 // FIXME: get rid of this indirection; GuiView ask the inset
4456 // if he is kind enough to update itself...
4457 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4458 //FIXME: pass DispatchResult here?
4459 inset->dispatch(currentBufferView()->cursor(), fr);
4465 case LFUN_DIALOG_TOGGLE: {
4466 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4467 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4468 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4472 case LFUN_DIALOG_DISCONNECT_INSET:
4473 disconnectDialog(to_utf8(cmd.argument()));
4476 case LFUN_DIALOG_HIDE: {
4477 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4481 case LFUN_DIALOG_SHOW: {
4482 string const name = cmd.getArg(0);
4483 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4485 if (name == "latexlog") {
4486 // getStatus checks that
4487 LASSERT(doc_buffer, break);
4488 Buffer::LogType type;
4489 string const logfile = doc_buffer->logName(&type);
4491 case Buffer::latexlog:
4494 case Buffer::buildlog:
4495 sdata = "literate ";
4498 sdata += Lexer::quoteString(logfile);
4499 showDialog("log", sdata);
4500 } else if (name == "vclog") {
4501 // getStatus checks that
4502 LASSERT(doc_buffer, break);
4503 string const sdata2 = "vc " +
4504 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4505 showDialog("log", sdata2);
4506 } else if (name == "symbols") {
4507 sdata = bv->cursor().getEncoding()->name();
4509 showDialog("symbols", sdata);
4510 } else if (name == "findreplace") {
4511 sdata = to_utf8(bv->cursor().selectionAsString(false));
4512 showDialog(name, sdata);
4514 } else if (name == "prefs" && isFullScreen()) {
4515 lfunUiToggle("fullscreen");
4516 showDialog("prefs", sdata);
4518 showDialog(name, sdata);
4523 dr.setMessage(cmd.argument());
4526 case LFUN_UI_TOGGLE: {
4527 string arg = cmd.getArg(0);
4528 if (!lfunUiToggle(arg)) {
4529 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4530 dr.setMessage(bformat(msg, from_utf8(arg)));
4532 // Make sure the keyboard focus stays in the work area.
4537 case LFUN_VIEW_SPLIT: {
4538 LASSERT(doc_buffer, break);
4539 string const orientation = cmd.getArg(0);
4540 d.splitter_->setOrientation(orientation == "vertical"
4541 ? Qt::Vertical : Qt::Horizontal);
4542 TabWorkArea * twa = addTabWorkArea();
4543 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4544 setCurrentWorkArea(wa);
4547 case LFUN_TAB_GROUP_CLOSE:
4548 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4549 closeTabWorkArea(twa);
4550 d.current_work_area_ = nullptr;
4551 twa = d.currentTabWorkArea();
4552 // Switch to the next GuiWorkArea in the found TabWorkArea.
4554 // Make sure the work area is up to date.
4555 setCurrentWorkArea(twa->currentWorkArea());
4557 setCurrentWorkArea(nullptr);
4562 case LFUN_VIEW_CLOSE:
4563 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4564 closeWorkArea(twa->currentWorkArea());
4565 d.current_work_area_ = nullptr;
4566 twa = d.currentTabWorkArea();
4567 // Switch to the next GuiWorkArea in the found TabWorkArea.
4569 // Make sure the work area is up to date.
4570 setCurrentWorkArea(twa->currentWorkArea());
4572 setCurrentWorkArea(nullptr);
4577 case LFUN_COMPLETION_INLINE:
4578 if (d.current_work_area_)
4579 d.current_work_area_->completer().showInline();
4582 case LFUN_COMPLETION_POPUP:
4583 if (d.current_work_area_)
4584 d.current_work_area_->completer().showPopup();
4589 if (d.current_work_area_)
4590 d.current_work_area_->completer().tab();
4593 case LFUN_COMPLETION_CANCEL:
4594 if (d.current_work_area_) {
4595 if (d.current_work_area_->completer().popupVisible())
4596 d.current_work_area_->completer().hidePopup();
4598 d.current_work_area_->completer().hideInline();
4602 case LFUN_COMPLETION_ACCEPT:
4603 if (d.current_work_area_)
4604 d.current_work_area_->completer().activate();
4607 case LFUN_BUFFER_ZOOM_IN:
4608 case LFUN_BUFFER_ZOOM_OUT:
4609 case LFUN_BUFFER_ZOOM: {
4610 if (cmd.argument().empty()) {
4611 if (cmd.action() == LFUN_BUFFER_ZOOM)
4613 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4618 if (cmd.action() == LFUN_BUFFER_ZOOM)
4619 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4620 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4621 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4623 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4626 // Actual zoom value: default zoom + fractional extra value
4627 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4628 if (zoom < static_cast<int>(zoom_min_))
4631 setCurrentZoom(zoom);
4633 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4634 lyxrc.currentZoom, lyxrc.defaultZoom));
4636 guiApp->fontLoader().update();
4637 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4641 case LFUN_VC_REGISTER:
4642 case LFUN_VC_RENAME:
4644 case LFUN_VC_CHECK_IN:
4645 case LFUN_VC_CHECK_OUT:
4646 case LFUN_VC_REPO_UPDATE:
4647 case LFUN_VC_LOCKING_TOGGLE:
4648 case LFUN_VC_REVERT:
4649 case LFUN_VC_UNDO_LAST:
4650 case LFUN_VC_COMMAND:
4651 case LFUN_VC_COMPARE:
4652 dispatchVC(cmd, dr);
4655 case LFUN_SERVER_GOTO_FILE_ROW:
4656 if(goToFileRow(to_utf8(cmd.argument())))
4657 dr.screenUpdate(Update::Force | Update::FitCursor);
4660 case LFUN_LYX_ACTIVATE:
4664 case LFUN_WINDOW_RAISE:
4670 case LFUN_FORWARD_SEARCH: {
4671 // it seems safe to assume we have a document buffer, since
4672 // getStatus wants one.
4673 LASSERT(doc_buffer, break);
4674 Buffer const * doc_master = doc_buffer->masterBuffer();
4675 FileName const path(doc_master->temppath());
4676 string const texname = doc_master->isChild(doc_buffer)
4677 ? DocFileName(changeExtension(
4678 doc_buffer->absFileName(),
4679 "tex")).mangledFileName()
4680 : doc_buffer->latexName();
4681 string const fulltexname =
4682 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4683 string const mastername =
4684 removeExtension(doc_master->latexName());
4685 FileName const dviname(addName(path.absFileName(),
4686 addExtension(mastername, "dvi")));
4687 FileName const pdfname(addName(path.absFileName(),
4688 addExtension(mastername, "pdf")));
4689 bool const have_dvi = dviname.exists();
4690 bool const have_pdf = pdfname.exists();
4691 if (!have_dvi && !have_pdf) {
4692 dr.setMessage(_("Please, preview the document first."));
4695 string outname = dviname.onlyFileName();
4696 string command = lyxrc.forward_search_dvi;
4697 if (!have_dvi || (have_pdf &&
4698 pdfname.lastModified() > dviname.lastModified())) {
4699 outname = pdfname.onlyFileName();
4700 command = lyxrc.forward_search_pdf;
4703 DocIterator cur = bv->cursor();
4704 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4705 LYXERR(Debug::ACTION, "Forward search: row:" << row
4707 if (row == -1 || command.empty()) {
4708 dr.setMessage(_("Couldn't proceed."));
4711 string texrow = convert<string>(row);
4713 command = subst(command, "$$n", texrow);
4714 command = subst(command, "$$f", fulltexname);
4715 command = subst(command, "$$t", texname);
4716 command = subst(command, "$$o", outname);
4718 volatile PathChanger p(path);
4720 one.startscript(Systemcall::DontWait, command);
4724 case LFUN_SPELLING_CONTINUOUSLY:
4725 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4726 dr.screenUpdate(Update::Force);
4729 case LFUN_CITATION_OPEN: {
4731 if (theFormats().getFormat("pdf"))
4732 pdfv = theFormats().getFormat("pdf")->viewer();
4733 if (theFormats().getFormat("ps"))
4734 psv = theFormats().getFormat("ps")->viewer();
4735 frontend::showTarget(argument, pdfv, psv);
4740 // The LFUN must be for one of BufferView, Buffer or Cursor;
4742 dispatchToBufferView(cmd, dr);
4746 // Need to update bv because many LFUNs here might have destroyed it
4747 bv = currentBufferView();
4749 // Clear non-empty selections
4750 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4752 Cursor & cur = bv->cursor();
4753 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4754 cur.clearSelection();
4760 bool GuiView::lfunUiToggle(string const & ui_component)
4762 if (ui_component == "scrollbar") {
4763 // hide() is of no help
4764 if (d.current_work_area_->verticalScrollBarPolicy() ==
4765 Qt::ScrollBarAlwaysOff)
4767 d.current_work_area_->setVerticalScrollBarPolicy(
4768 Qt::ScrollBarAsNeeded);
4770 d.current_work_area_->setVerticalScrollBarPolicy(
4771 Qt::ScrollBarAlwaysOff);
4772 } else if (ui_component == "statusbar") {
4773 statusBar()->setVisible(!statusBar()->isVisible());
4774 } else if (ui_component == "menubar") {
4775 menuBar()->setVisible(!menuBar()->isVisible());
4777 if (ui_component == "frame") {
4778 int const l = contentsMargins().left();
4780 //are the frames in default state?
4781 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4783 #if QT_VERSION > 0x050903
4784 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4786 setContentsMargins(-2, -2, -2, -2);
4788 #if QT_VERSION > 0x050903
4789 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4791 setContentsMargins(0, 0, 0, 0);
4794 if (ui_component == "fullscreen") {
4802 void GuiView::toggleFullScreen()
4804 setWindowState(windowState() ^ Qt::WindowFullScreen);
4808 Buffer const * GuiView::updateInset(Inset const * inset)
4813 Buffer const * inset_buffer = &(inset->buffer());
4815 for (int i = 0; i != d.splitter_->count(); ++i) {
4816 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4819 Buffer const * buffer = &(wa->bufferView().buffer());
4820 if (inset_buffer == buffer)
4821 wa->scheduleRedraw(true);
4823 return inset_buffer;
4827 void GuiView::restartCaret()
4829 /* When we move around, or type, it's nice to be able to see
4830 * the caret immediately after the keypress.
4832 if (d.current_work_area_)
4833 d.current_work_area_->startBlinkingCaret();
4835 // Take this occasion to update the other GUI elements.
4841 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4843 if (d.current_work_area_)
4844 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4849 // This list should be kept in sync with the list of insets in
4850 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4851 // dialog should have the same name as the inset.
4852 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4853 // docs in LyXAction.cpp.
4855 char const * const dialognames[] = {
4857 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4858 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4859 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4860 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4861 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4862 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4863 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4864 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4866 char const * const * const end_dialognames =
4867 dialognames + (sizeof(dialognames) / sizeof(char *));
4871 cmpCStr(char const * name) : name_(name) {}
4872 bool operator()(char const * other) {
4873 return strcmp(other, name_) == 0;
4880 bool isValidName(string const & name)
4882 return find_if(dialognames, end_dialognames,
4883 cmpCStr(name.c_str())) != end_dialognames;
4889 void GuiView::resetDialogs()
4891 // Make sure that no LFUN uses any GuiView.
4892 guiApp->setCurrentView(nullptr);
4896 constructToolbars();
4897 guiApp->menus().fillMenuBar(menuBar(), this, false);
4898 d.layout_->updateContents(true);
4899 // Now update controls with current buffer.
4900 guiApp->setCurrentView(this);
4906 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4908 for (QObject * child: widget->children()) {
4909 if (child->inherits("QGroupBox")) {
4910 QGroupBox * box = (QGroupBox*) child;
4913 flatGroupBoxes(child, flag);
4919 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4921 if (!isValidName(name))
4924 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4926 if (it != d.dialogs_.end()) {
4928 it->second->hideView();
4929 return it->second.get();
4932 Dialog * dialog = build(name);
4933 d.dialogs_[name].reset(dialog);
4934 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4935 // Force a uniform style for group boxes
4936 // On Mac non-flat works better, on Linux flat is standard
4937 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4939 if (lyxrc.allow_geometry_session)
4940 dialog->restoreSession();
4947 void GuiView::showDialog(string const & name, string const & sdata,
4950 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4954 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4960 const string name = fromqstr(qname);
4961 const string sdata = fromqstr(qdata);
4965 Dialog * dialog = findOrBuild(name, false);
4967 bool const visible = dialog->isVisibleView();
4968 dialog->showData(sdata);
4969 if (currentBufferView())
4970 currentBufferView()->editInset(name, inset);
4971 // We only set the focus to the new dialog if it was not yet
4972 // visible in order not to change the existing previous behaviour
4974 // activateWindow is needed for floating dockviews
4975 dialog->asQWidget()->raise();
4976 dialog->asQWidget()->activateWindow();
4977 if (dialog->wantInitialFocus())
4978 dialog->asQWidget()->setFocus();
4982 catch (ExceptionMessage const &) {
4990 bool GuiView::isDialogVisible(string const & name) const
4992 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4993 if (it == d.dialogs_.end())
4995 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4999 void GuiView::hideDialog(string const & name, Inset * inset)
5001 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5002 if (it == d.dialogs_.end())
5006 if (!currentBufferView())
5008 if (inset != currentBufferView()->editedInset(name))
5012 Dialog * const dialog = it->second.get();
5013 if (dialog->isVisibleView())
5015 if (currentBufferView())
5016 currentBufferView()->editInset(name, nullptr);
5020 void GuiView::disconnectDialog(string const & name)
5022 if (!isValidName(name))
5024 if (currentBufferView())
5025 currentBufferView()->editInset(name, nullptr);
5029 void GuiView::hideAll() const
5031 for(auto const & dlg_p : d.dialogs_)
5032 dlg_p.second->hideView();
5036 void GuiView::updateDialogs()
5038 for(auto const & dlg_p : d.dialogs_) {
5039 Dialog * dialog = dlg_p.second.get();
5041 if (dialog->needBufferOpen() && !documentBufferView())
5042 hideDialog(fromqstr(dialog->name()), nullptr);
5043 else if (dialog->isVisibleView())
5044 dialog->checkStatus();
5052 Dialog * GuiView::build(string const & name)
5054 return createDialog(*this, name);
5058 SEMenu::SEMenu(QWidget * parent)
5060 QAction * action = addAction(qt_("Disable Shell Escape"));
5061 connect(action, SIGNAL(triggered()),
5062 parent, SLOT(disableShellEscape()));
5065 } // namespace frontend
5068 #include "moc_GuiView.cpp"