3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
116 // sync with GuiAlert.cpp
117 #define EXPORT_in_THREAD 1
120 #include "support/bind.h"
124 #ifdef HAVE_SYS_TIME_H
125 # include <sys/time.h>
133 using namespace lyx::support;
137 using support::addExtension;
138 using support::changeExtension;
139 using support::removeExtension;
145 class BackgroundWidget : public QWidget
148 BackgroundWidget(int width, int height)
149 : width_(width), height_(height)
151 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
152 if (!lyxrc.show_banner)
154 /// The text to be written on top of the pixmap
155 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
156 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
157 /// The text to be written on top of the pixmap
158 QString const text = lyx_version ?
159 qt_("version ") + lyx_version : qt_("unknown version");
160 #if QT_VERSION >= 0x050000
161 QString imagedir = "images/";
162 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
163 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
164 if (svgRenderer.isValid()) {
165 splash_ = QPixmap(splashSize());
166 QPainter painter(&splash_);
167 svgRenderer.render(&painter);
168 splash_.setDevicePixelRatio(pixelRatio());
170 splash_ = getPixmap("images/", "banner", "png");
173 splash_ = getPixmap("images/", "banner", "svgz,png");
176 QPainter pain(&splash_);
177 pain.setPen(QColor(0, 0, 0));
178 qreal const fsize = fontSize();
181 qreal locscale = htextsize.toFloat(&ok);
184 QPointF const position = textPosition(false);
185 QPointF const hposition = textPosition(true);
186 QRectF const hrect(hposition, splashSize());
188 "widget pixel ratio: " << pixelRatio() <<
189 " splash pixel ratio: " << splashPixelRatio() <<
190 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
192 // The font used to display the version info
193 font.setStyleHint(QFont::SansSerif);
194 font.setWeight(QFont::Bold);
195 font.setPointSizeF(fsize);
197 pain.drawText(position, text);
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Normal);
201 font.setPointSizeF(hfsize);
202 // Check how long the logo gets with the current font
203 // and adapt if the font is running wider than what
205 GuiFontMetrics fm(font);
206 // Split the title into lines to measure the longest line
207 // in the current l7n.
208 QStringList titlesegs = htext.split('\n');
210 int hline = fm.maxHeight();
211 QStringList::const_iterator sit;
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
616 statusBar()->addPermanentWidget(busylabel);
617 search_mode mode = theGuiApp()->imageSearchMode();
618 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
619 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
620 busylabel->setMovie(busyanim);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busylabel, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busylabel, SLOT(hide()));
628 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 QSlider * zoomslider = new QSlider(Qt::Horizontal, statusBar());
633 zoomslider->setFixedWidth(fm.horizontalAdvance('x') * 15);
634 // Make the defaultZoom center
635 zoomslider->setRange(10, (lyxrc.defaultZoom * 2) - 10);
636 zoomslider->setValue(lyxrc.currentZoom);
637 zoomslider->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
638 zoomslider->setTickPosition(QSlider::TicksBelow);
639 zoomslider->setTickInterval(lyxrc.defaultZoom - 10);
640 statusBar()->addPermanentWidget(zoomslider);
642 connect(zoomslider, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
643 connect(this, SIGNAL(currentZoomChanged(int)), zoomslider, SLOT(setValue(int)));
645 int const iconheight = max(int(d.normalIconSize), fm.height());
646 QSize const iconsize(iconheight, iconheight);
648 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
649 shell_escape_ = new QLabel(statusBar());
650 shell_escape_->setPixmap(shellescape);
651 shell_escape_->setScaledContents(true);
652 shell_escape_->setAlignment(Qt::AlignCenter);
653 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
654 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
655 "external commands for this document. "
656 "Right click to change."));
657 SEMenu * menu = new SEMenu(this);
658 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
659 menu, SLOT(showMenu(QPoint)));
660 shell_escape_->hide();
661 statusBar()->addPermanentWidget(shell_escape_);
663 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
664 read_only_ = new QLabel(statusBar());
665 read_only_->setPixmap(readonly);
666 read_only_->setScaledContents(true);
667 read_only_->setAlignment(Qt::AlignCenter);
669 statusBar()->addPermanentWidget(read_only_);
671 version_control_ = new QLabel(statusBar());
672 version_control_->setAlignment(Qt::AlignCenter);
673 version_control_->setFrameStyle(QFrame::StyledPanel);
674 version_control_->hide();
675 statusBar()->addPermanentWidget(version_control_);
677 statusBar()->setSizeGripEnabled(true);
680 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
681 SLOT(autoSaveThreadFinished()));
683 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
684 SLOT(processingThreadStarted()));
685 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
686 SLOT(processingThreadFinished()));
688 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
689 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
691 // set custom application bars context menu, e.g. tool bar and menu bar
692 setContextMenuPolicy(Qt::CustomContextMenu);
693 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
694 SLOT(toolBarPopup(const QPoint &)));
696 // Forbid too small unresizable window because it can happen
697 // with some window manager under X11.
698 setMinimumSize(300, 200);
700 if (lyxrc.allow_geometry_session) {
701 // Now take care of session management.
706 // no session handling, default to a sane size.
707 setGeometry(50, 50, 690, 510);
710 // clear session data if any.
712 settings.remove("views");
722 void GuiView::disableShellEscape()
724 BufferView * bv = documentBufferView();
727 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
728 bv->buffer().params().shell_escape = false;
729 bv->processUpdateFlags(Update::Force);
733 void GuiView::checkCancelBackground()
735 docstring const ttl = _("Cancel Export?");
736 docstring const msg = _("Do you want to cancel the background export process?");
738 Alert::prompt(ttl, msg, 1, 1,
739 _("&Cancel export"), _("Co&ntinue"));
741 Systemcall::killscript();
745 void GuiView::zoomSliderMoved(int value)
748 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
749 currentWorkArea()->scheduleRedraw(true);
750 message(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
751 value, lyxrc.defaultZoom));
755 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
757 QVector<GuiWorkArea*> areas;
758 for (int i = 0; i < tabWorkAreaCount(); i++) {
759 TabWorkArea* ta = tabWorkArea(i);
760 for (int u = 0; u < ta->count(); u++) {
761 areas << ta->workArea(u);
767 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
768 string const & format)
770 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
773 case Buffer::ExportSuccess:
774 msg = bformat(_("Successful export to format: %1$s"), fmt);
776 case Buffer::ExportCancel:
777 msg = _("Document export cancelled.");
779 case Buffer::ExportError:
780 case Buffer::ExportNoPathToFormat:
781 case Buffer::ExportTexPathHasSpaces:
782 case Buffer::ExportConverterError:
783 msg = bformat(_("Error while exporting format: %1$s"), fmt);
785 case Buffer::PreviewSuccess:
786 msg = bformat(_("Successful preview of format: %1$s"), fmt);
788 case Buffer::PreviewError:
789 msg = bformat(_("Error while previewing format: %1$s"), fmt);
791 case Buffer::ExportKilled:
792 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
799 void GuiView::processingThreadStarted()
804 void GuiView::processingThreadFinished()
806 QFutureWatcher<Buffer::ExportStatus> const * watcher =
807 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
809 Buffer::ExportStatus const status = watcher->result();
810 handleExportStatus(this, status, d.processing_format);
813 BufferView const * const bv = currentBufferView();
814 if (bv && !bv->buffer().errorList("Export").empty()) {
819 bool const error = (status != Buffer::ExportSuccess &&
820 status != Buffer::PreviewSuccess &&
821 status != Buffer::ExportCancel);
823 ErrorList & el = bv->buffer().errorList(d.last_export_format);
824 // at this point, we do not know if buffer-view or
825 // master-buffer-view was called. If there was an export error,
826 // and the current buffer's error log is empty, we guess that
827 // it must be master-buffer-view that was called so we set
829 errors(d.last_export_format, el.empty());
834 void GuiView::autoSaveThreadFinished()
836 QFutureWatcher<docstring> const * watcher =
837 static_cast<QFutureWatcher<docstring> const *>(sender());
838 message(watcher->result());
843 void GuiView::saveLayout() const
846 settings.setValue("zoom_ratio", zoom_ratio_);
847 settings.setValue("devel_mode", devel_mode_);
848 settings.beginGroup("views");
849 settings.beginGroup(QString::number(id_));
850 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
851 settings.setValue("pos", pos());
852 settings.setValue("size", size());
854 settings.setValue("geometry", saveGeometry());
855 settings.setValue("layout", saveState(0));
856 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
860 void GuiView::saveUISettings() const
864 // Save the toolbar private states
865 for (auto const & tb_p : d.toolbars_)
866 tb_p.second->saveSession(settings);
867 // Now take care of all other dialogs
868 for (auto const & dlg_p : d.dialogs_)
869 dlg_p.second->saveSession(settings);
873 void GuiView::setCurrentZoom(const int v)
875 lyxrc.currentZoom = v;
876 Q_EMIT currentZoomChanged(v);
880 bool GuiView::restoreLayout()
883 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
884 // Actual zoom value: default zoom + fractional offset
885 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
886 if (zoom < static_cast<int>(zoom_min_))
888 setCurrentZoom(zoom);
889 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
890 settings.beginGroup("views");
891 settings.beginGroup(QString::number(id_));
892 QString const icon_key = "icon_size";
893 if (!settings.contains(icon_key))
896 //code below is skipped when when ~/.config/LyX is (re)created
897 setIconSize(d.iconSize(settings.value(icon_key).toString()));
899 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
900 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
901 QSize size = settings.value("size", QSize(690, 510)).toSize();
905 // Work-around for bug #6034: the window ends up in an undetermined
906 // state when trying to restore a maximized window when it is
907 // already maximized.
908 if (!(windowState() & Qt::WindowMaximized))
909 if (!restoreGeometry(settings.value("geometry").toByteArray()))
910 setGeometry(50, 50, 690, 510);
913 // Make sure layout is correctly oriented.
914 setLayoutDirection(qApp->layoutDirection());
916 // Allow the toc and view-source dock widget to be restored if needed.
918 if ((dialog = findOrBuild("toc", true)))
919 // see bug 5082. At least setup title and enabled state.
920 // Visibility will be adjusted by restoreState below.
921 dialog->prepareView();
922 if ((dialog = findOrBuild("view-source", true)))
923 dialog->prepareView();
924 if ((dialog = findOrBuild("progress", true)))
925 dialog->prepareView();
927 if (!restoreState(settings.value("layout").toByteArray(), 0))
930 // init the toolbars that have not been restored
931 for (auto const & tb_p : guiApp->toolbars()) {
932 GuiToolbar * tb = toolbar(tb_p.name);
933 if (tb && !tb->isRestored())
934 initToolbar(tb_p.name);
937 // update lock (all) toolbars positions
938 updateLockToolbars();
945 GuiToolbar * GuiView::toolbar(string const & name)
947 ToolbarMap::iterator it = d.toolbars_.find(name);
948 if (it != d.toolbars_.end())
951 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
956 void GuiView::updateLockToolbars()
958 toolbarsMovable_ = false;
959 for (ToolbarInfo const & info : guiApp->toolbars()) {
960 GuiToolbar * tb = toolbar(info.name);
961 if (tb && tb->isMovable())
962 toolbarsMovable_ = true;
967 void GuiView::constructToolbars()
969 for (auto const & tb_p : d.toolbars_)
973 // I don't like doing this here, but the standard toolbar
974 // destroys this object when it's destroyed itself (vfr)
975 d.layout_ = new LayoutBox(*this);
976 d.stack_widget_->addWidget(d.layout_);
977 d.layout_->move(0,0);
979 // extracts the toolbars from the backend
980 for (ToolbarInfo const & inf : guiApp->toolbars())
981 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
985 void GuiView::initToolbars()
987 // extracts the toolbars from the backend
988 for (ToolbarInfo const & inf : guiApp->toolbars())
989 initToolbar(inf.name);
993 void GuiView::initToolbar(string const & name)
995 GuiToolbar * tb = toolbar(name);
998 int const visibility = guiApp->toolbars().defaultVisibility(name);
999 bool newline = !(visibility & Toolbars::SAMEROW);
1000 tb->setVisible(false);
1001 tb->setVisibility(visibility);
1003 if (visibility & Toolbars::TOP) {
1005 addToolBarBreak(Qt::TopToolBarArea);
1006 addToolBar(Qt::TopToolBarArea, tb);
1009 if (visibility & Toolbars::BOTTOM) {
1011 addToolBarBreak(Qt::BottomToolBarArea);
1012 addToolBar(Qt::BottomToolBarArea, tb);
1015 if (visibility & Toolbars::LEFT) {
1017 addToolBarBreak(Qt::LeftToolBarArea);
1018 addToolBar(Qt::LeftToolBarArea, tb);
1021 if (visibility & Toolbars::RIGHT) {
1023 addToolBarBreak(Qt::RightToolBarArea);
1024 addToolBar(Qt::RightToolBarArea, tb);
1027 if (visibility & Toolbars::ON)
1028 tb->setVisible(true);
1030 tb->setMovable(true);
1034 TocModels & GuiView::tocModels()
1036 return d.toc_models_;
1040 void GuiView::setFocus()
1042 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1043 QMainWindow::setFocus();
1047 bool GuiView::hasFocus() const
1049 if (currentWorkArea())
1050 return currentWorkArea()->hasFocus();
1051 if (currentMainWorkArea())
1052 return currentMainWorkArea()->hasFocus();
1053 return d.bg_widget_->hasFocus();
1057 void GuiView::focusInEvent(QFocusEvent * e)
1059 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1060 QMainWindow::focusInEvent(e);
1061 // Make sure guiApp points to the correct view.
1062 guiApp->setCurrentView(this);
1063 if (currentWorkArea())
1064 currentWorkArea()->setFocus();
1065 else if (currentMainWorkArea())
1066 currentMainWorkArea()->setFocus();
1068 d.bg_widget_->setFocus();
1072 void GuiView::showEvent(QShowEvent * e)
1074 LYXERR(Debug::GUI, "Passed Geometry "
1075 << size().height() << "x" << size().width()
1076 << "+" << pos().x() << "+" << pos().y());
1078 if (d.splitter_->count() == 0)
1079 // No work area, switch to the background widget.
1083 QMainWindow::showEvent(e);
1087 bool GuiView::closeScheduled()
1094 bool GuiView::prepareAllBuffersForLogout()
1096 Buffer * first = theBufferList().first();
1100 // First, iterate over all buffers and ask the users if unsaved
1101 // changes should be saved.
1102 // We cannot use a for loop as the buffer list cycles.
1105 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1107 b = theBufferList().next(b);
1108 } while (b != first);
1110 // Next, save session state
1111 // When a view/window was closed before without quitting LyX, there
1112 // are already entries in the lastOpened list.
1113 theSession().lastOpened().clear();
1120 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1121 ** is responsibility of the container (e.g., dialog)
1123 void GuiView::closeEvent(QCloseEvent * close_event)
1125 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1127 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1128 Alert::warning(_("Exit LyX"),
1129 _("LyX could not be closed because documents are being processed by LyX."));
1130 close_event->setAccepted(false);
1134 // If the user pressed the x (so we didn't call closeView
1135 // programmatically), we want to clear all existing entries.
1137 theSession().lastOpened().clear();
1142 // it can happen that this event arrives without selecting the view,
1143 // e.g. when clicking the close button on a background window.
1145 if (!closeWorkAreaAll()) {
1147 close_event->ignore();
1151 // Make sure that nothing will use this to be closed View.
1152 guiApp->unregisterView(this);
1154 if (isFullScreen()) {
1155 // Switch off fullscreen before closing.
1160 // Make sure the timer time out will not trigger a statusbar update.
1161 d.statusbar_timer_.stop();
1163 // Saving fullscreen requires additional tweaks in the toolbar code.
1164 // It wouldn't also work under linux natively.
1165 if (lyxrc.allow_geometry_session) {
1170 close_event->accept();
1174 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1176 if (event->mimeData()->hasUrls())
1178 /// \todo Ask lyx-devel is this is enough:
1179 /// if (event->mimeData()->hasFormat("text/plain"))
1180 /// event->acceptProposedAction();
1184 void GuiView::dropEvent(QDropEvent * event)
1186 QList<QUrl> files = event->mimeData()->urls();
1187 if (files.isEmpty())
1190 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1191 for (int i = 0; i != files.size(); ++i) {
1192 string const file = os::internal_path(fromqstr(
1193 files.at(i).toLocalFile()));
1197 string const ext = support::getExtension(file);
1198 vector<const Format *> found_formats;
1200 // Find all formats that have the correct extension.
1201 for (const Format * fmt : theConverters().importableFormats())
1202 if (fmt->hasExtension(ext))
1203 found_formats.push_back(fmt);
1206 if (!found_formats.empty()) {
1207 if (found_formats.size() > 1) {
1208 //FIXME: show a dialog to choose the correct importable format
1209 LYXERR(Debug::FILES,
1210 "Multiple importable formats found, selecting first");
1212 string const arg = found_formats[0]->name() + " " + file;
1213 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1216 //FIXME: do we have to explicitly check whether it's a lyx file?
1217 LYXERR(Debug::FILES,
1218 "No formats found, trying to open it as a lyx file");
1219 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1221 // add the functions to the queue
1222 guiApp->addToFuncRequestQueue(cmd);
1225 // now process the collected functions. We perform the events
1226 // asynchronously. This prevents potential problems in case the
1227 // BufferView is closed within an event.
1228 guiApp->processFuncRequestQueueAsync();
1232 void GuiView::message(docstring const & str)
1234 if (ForkedProcess::iAmAChild())
1237 // call is moved to GUI-thread by GuiProgress
1238 d.progress_->appendMessage(toqstr(str));
1242 void GuiView::clearMessageText()
1244 message(docstring());
1248 void GuiView::updateStatusBarMessage(QString const & str)
1250 statusBar()->showMessage(str);
1251 d.statusbar_timer_.stop();
1252 d.statusbar_timer_.start(3000);
1256 void GuiView::clearMessage()
1258 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1259 // the hasFocus function mostly returns false, even if the focus is on
1260 // a workarea in this view.
1264 d.statusbar_timer_.stop();
1268 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1270 if (wa != d.current_work_area_
1271 || wa->bufferView().buffer().isInternal())
1273 Buffer const & buf = wa->bufferView().buffer();
1274 // Set the windows title
1275 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1276 if (buf.notifiesExternalModification()) {
1277 title = bformat(_("%1$s (modified externally)"), title);
1278 // If the external modification status has changed, then maybe the status of
1279 // buffer-save has changed too.
1283 title += from_ascii(" - LyX");
1285 setWindowTitle(toqstr(title));
1286 // Sets the path for the window: this is used by OSX to
1287 // allow a context click on the title bar showing a menu
1288 // with the path up to the file
1289 setWindowFilePath(toqstr(buf.absFileName()));
1290 // Tell Qt whether the current document is changed
1291 setWindowModified(!buf.isClean());
1293 if (buf.params().shell_escape)
1294 shell_escape_->show();
1296 shell_escape_->hide();
1298 if (buf.hasReadonlyFlag())
1303 if (buf.lyxvc().inUse()) {
1304 version_control_->show();
1305 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1307 version_control_->hide();
1311 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1313 if (d.current_work_area_)
1314 // disconnect the current work area from all slots
1315 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1317 disconnectBufferView();
1318 connectBufferView(wa->bufferView());
1319 connectBuffer(wa->bufferView().buffer());
1320 d.current_work_area_ = wa;
1321 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1322 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1323 QObject::connect(wa, SIGNAL(busy(bool)),
1324 this, SLOT(setBusy(bool)));
1325 // connection of a signal to a signal
1326 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1327 this, SIGNAL(bufferViewChanged()));
1328 Q_EMIT updateWindowTitle(wa);
1329 Q_EMIT bufferViewChanged();
1333 void GuiView::onBufferViewChanged()
1336 // Buffer-dependent dialogs must be updated. This is done here because
1337 // some dialogs require buffer()->text.
1342 void GuiView::on_lastWorkAreaRemoved()
1345 // We already are in a close event. Nothing more to do.
1348 if (d.splitter_->count() > 1)
1349 // We have a splitter so don't close anything.
1352 // Reset and updates the dialogs.
1353 Q_EMIT bufferViewChanged();
1358 if (lyxrc.open_buffers_in_tabs)
1359 // Nothing more to do, the window should stay open.
1362 if (guiApp->viewIds().size() > 1) {
1368 // On Mac we also close the last window because the application stay
1369 // resident in memory. On other platforms we don't close the last
1370 // window because this would quit the application.
1376 void GuiView::updateStatusBar()
1378 // let the user see the explicit message
1379 if (d.statusbar_timer_.isActive())
1386 void GuiView::showMessage()
1390 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1391 if (msg.isEmpty()) {
1392 BufferView const * bv = currentBufferView();
1394 msg = toqstr(bv->cursor().currentState(devel_mode_));
1396 msg = qt_("Welcome to LyX!");
1398 statusBar()->showMessage(msg);
1402 bool GuiView::event(QEvent * e)
1406 // Useful debug code:
1407 //case QEvent::ActivationChange:
1408 //case QEvent::WindowDeactivate:
1409 //case QEvent::Paint:
1410 //case QEvent::Enter:
1411 //case QEvent::Leave:
1412 //case QEvent::HoverEnter:
1413 //case QEvent::HoverLeave:
1414 //case QEvent::HoverMove:
1415 //case QEvent::StatusTip:
1416 //case QEvent::DragEnter:
1417 //case QEvent::DragLeave:
1418 //case QEvent::Drop:
1421 case QEvent::WindowStateChange: {
1422 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1423 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1424 bool result = QMainWindow::event(e);
1425 bool nfstate = (windowState() & Qt::WindowFullScreen);
1426 if (!ofstate && nfstate) {
1427 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1428 // switch to full-screen state
1429 if (lyxrc.full_screen_statusbar)
1430 statusBar()->hide();
1431 if (lyxrc.full_screen_menubar)
1433 if (lyxrc.full_screen_toolbars) {
1434 for (auto const & tb_p : d.toolbars_)
1435 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1436 tb_p.second->hide();
1438 for (int i = 0; i != d.splitter_->count(); ++i)
1439 d.tabWorkArea(i)->setFullScreen(true);
1440 #if QT_VERSION > 0x050903
1441 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1442 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1444 setContentsMargins(-2, -2, -2, -2);
1446 hideDialogs("prefs", nullptr);
1447 } else if (ofstate && !nfstate) {
1448 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1449 // switch back from full-screen state
1450 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1451 statusBar()->show();
1452 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1454 if (lyxrc.full_screen_toolbars) {
1455 for (auto const & tb_p : d.toolbars_)
1456 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1457 tb_p.second->show();
1460 for (int i = 0; i != d.splitter_->count(); ++i)
1461 d.tabWorkArea(i)->setFullScreen(false);
1462 #if QT_VERSION > 0x050903
1463 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1465 setContentsMargins(0, 0, 0, 0);
1469 case QEvent::WindowActivate: {
1470 GuiView * old_view = guiApp->currentView();
1471 if (this == old_view) {
1473 return QMainWindow::event(e);
1475 if (old_view && old_view->currentBufferView()) {
1476 // save current selection to the selection buffer to allow
1477 // middle-button paste in this window.
1478 cap::saveSelection(old_view->currentBufferView()->cursor());
1480 guiApp->setCurrentView(this);
1481 if (d.current_work_area_)
1482 on_currentWorkAreaChanged(d.current_work_area_);
1486 return QMainWindow::event(e);
1489 case QEvent::ShortcutOverride: {
1491 if (isFullScreen() && menuBar()->isHidden()) {
1492 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1493 // FIXME: we should also try to detect special LyX shortcut such as
1494 // Alt-P and Alt-M. Right now there is a hack in
1495 // GuiWorkArea::processKeySym() that hides again the menubar for
1497 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1499 return QMainWindow::event(e);
1502 return QMainWindow::event(e);
1505 case QEvent::ApplicationPaletteChange: {
1506 // runtime switch from/to dark mode
1508 return QMainWindow::event(e);
1512 return QMainWindow::event(e);
1516 void GuiView::resetWindowTitle()
1518 setWindowTitle(qt_("LyX"));
1521 bool GuiView::focusNextPrevChild(bool /*next*/)
1528 bool GuiView::busy() const
1534 void GuiView::setBusy(bool busy)
1536 bool const busy_before = busy_ > 0;
1537 busy ? ++busy_ : --busy_;
1538 if ((busy_ > 0) == busy_before)
1539 // busy state didn't change
1543 QApplication::setOverrideCursor(Qt::WaitCursor);
1546 QApplication::restoreOverrideCursor();
1551 void GuiView::resetCommandExecute()
1553 command_execute_ = false;
1558 double GuiView::pixelRatio() const
1560 #if QT_VERSION >= 0x050000
1561 return qt_scale_factor * devicePixelRatio();
1568 GuiWorkArea * GuiView::workArea(int index)
1570 if (TabWorkArea * twa = d.currentTabWorkArea())
1571 if (index < twa->count())
1572 return twa->workArea(index);
1577 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1579 if (currentWorkArea()
1580 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1581 return currentWorkArea();
1582 if (TabWorkArea * twa = d.currentTabWorkArea())
1583 return twa->workArea(buffer);
1588 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1590 // Automatically create a TabWorkArea if there are none yet.
1591 TabWorkArea * tab_widget = d.splitter_->count()
1592 ? d.currentTabWorkArea() : addTabWorkArea();
1593 return tab_widget->addWorkArea(buffer, *this);
1597 TabWorkArea * GuiView::addTabWorkArea()
1599 TabWorkArea * twa = new TabWorkArea;
1600 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1601 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1602 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1603 this, SLOT(on_lastWorkAreaRemoved()));
1605 d.splitter_->addWidget(twa);
1606 d.stack_widget_->setCurrentWidget(d.splitter_);
1611 GuiWorkArea const * GuiView::currentWorkArea() const
1613 return d.current_work_area_;
1617 GuiWorkArea * GuiView::currentWorkArea()
1619 return d.current_work_area_;
1623 GuiWorkArea const * GuiView::currentMainWorkArea() const
1625 if (!d.currentTabWorkArea())
1627 return d.currentTabWorkArea()->currentWorkArea();
1631 GuiWorkArea * GuiView::currentMainWorkArea()
1633 if (!d.currentTabWorkArea())
1635 return d.currentTabWorkArea()->currentWorkArea();
1639 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1641 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1643 d.current_work_area_ = nullptr;
1645 Q_EMIT bufferViewChanged();
1649 // FIXME: I've no clue why this is here and why it accesses
1650 // theGuiApp()->currentView, which might be 0 (bug 6464).
1651 // See also 27525 (vfr).
1652 if (theGuiApp()->currentView() == this
1653 && theGuiApp()->currentView()->currentWorkArea() == wa)
1656 if (currentBufferView())
1657 cap::saveSelection(currentBufferView()->cursor());
1659 theGuiApp()->setCurrentView(this);
1660 d.current_work_area_ = wa;
1662 // We need to reset this now, because it will need to be
1663 // right if the tabWorkArea gets reset in the for loop. We
1664 // will change it back if we aren't in that case.
1665 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1666 d.current_main_work_area_ = wa;
1668 for (int i = 0; i != d.splitter_->count(); ++i) {
1669 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1670 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1671 << ", Current main wa: " << currentMainWorkArea());
1676 d.current_main_work_area_ = old_cmwa;
1678 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1679 on_currentWorkAreaChanged(wa);
1680 BufferView & bv = wa->bufferView();
1681 bv.cursor().fixIfBroken();
1683 wa->setUpdatesEnabled(true);
1684 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1688 void GuiView::removeWorkArea(GuiWorkArea * wa)
1690 LASSERT(wa, return);
1691 if (wa == d.current_work_area_) {
1693 disconnectBufferView();
1694 d.current_work_area_ = nullptr;
1695 d.current_main_work_area_ = nullptr;
1698 bool found_twa = false;
1699 for (int i = 0; i != d.splitter_->count(); ++i) {
1700 TabWorkArea * twa = d.tabWorkArea(i);
1701 if (twa->removeWorkArea(wa)) {
1702 // Found in this tab group, and deleted the GuiWorkArea.
1704 if (twa->count() != 0) {
1705 if (d.current_work_area_ == nullptr)
1706 // This means that we are closing the current GuiWorkArea, so
1707 // switch to the next GuiWorkArea in the found TabWorkArea.
1708 setCurrentWorkArea(twa->currentWorkArea());
1710 // No more WorkAreas in this tab group, so delete it.
1717 // It is not a tabbed work area (i.e., the search work area), so it
1718 // should be deleted by other means.
1719 LASSERT(found_twa, return);
1721 if (d.current_work_area_ == nullptr) {
1722 if (d.splitter_->count() != 0) {
1723 TabWorkArea * twa = d.currentTabWorkArea();
1724 setCurrentWorkArea(twa->currentWorkArea());
1726 // No more work areas, switch to the background widget.
1727 setCurrentWorkArea(nullptr);
1733 LayoutBox * GuiView::getLayoutDialog() const
1739 void GuiView::updateLayoutList()
1742 d.layout_->updateContents(false);
1746 void GuiView::updateToolbars()
1748 if (d.current_work_area_) {
1750 if (d.current_work_area_->bufferView().cursor().inMathed()
1751 && !d.current_work_area_->bufferView().cursor().inRegexped())
1752 context |= Toolbars::MATH;
1753 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1754 context |= Toolbars::TABLE;
1755 if (currentBufferView()->buffer().areChangesPresent()
1756 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1757 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1758 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1759 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1760 context |= Toolbars::REVIEW;
1761 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1762 context |= Toolbars::MATHMACROTEMPLATE;
1763 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1764 context |= Toolbars::IPA;
1765 if (command_execute_)
1766 context |= Toolbars::MINIBUFFER;
1767 if (minibuffer_focus_) {
1768 context |= Toolbars::MINIBUFFER_FOCUS;
1769 minibuffer_focus_ = false;
1772 for (auto const & tb_p : d.toolbars_)
1773 tb_p.second->update(context);
1775 for (auto const & tb_p : d.toolbars_)
1776 tb_p.second->update();
1780 void GuiView::refillToolbars()
1782 for (auto const & tb_p : d.toolbars_)
1783 tb_p.second->refill();
1787 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1789 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1790 LASSERT(newBuffer, return);
1792 GuiWorkArea * wa = workArea(*newBuffer);
1793 if (wa == nullptr) {
1795 newBuffer->masterBuffer()->updateBuffer();
1797 wa = addWorkArea(*newBuffer);
1798 // scroll to the position when the BufferView was last closed
1799 if (lyxrc.use_lastfilepos) {
1800 LastFilePosSection::FilePos filepos =
1801 theSession().lastFilePos().load(newBuffer->fileName());
1802 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1805 //Disconnect the old buffer...there's no new one.
1808 connectBuffer(*newBuffer);
1809 connectBufferView(wa->bufferView());
1811 setCurrentWorkArea(wa);
1815 void GuiView::connectBuffer(Buffer & buf)
1817 buf.setGuiDelegate(this);
1821 void GuiView::disconnectBuffer()
1823 if (d.current_work_area_)
1824 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1828 void GuiView::connectBufferView(BufferView & bv)
1830 bv.setGuiDelegate(this);
1834 void GuiView::disconnectBufferView()
1836 if (d.current_work_area_)
1837 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1841 void GuiView::errors(string const & error_type, bool from_master)
1843 BufferView const * const bv = currentBufferView();
1847 ErrorList const & el = from_master ?
1848 bv->buffer().masterBuffer()->errorList(error_type) :
1849 bv->buffer().errorList(error_type);
1854 string err = error_type;
1856 err = "from_master|" + error_type;
1857 showDialog("errorlist", err);
1861 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1863 d.toc_models_.updateItem(toqstr(type), dit);
1867 void GuiView::structureChanged()
1869 // This is called from the Buffer, which has no way to ensure that cursors
1870 // in BufferView remain valid.
1871 if (documentBufferView())
1872 documentBufferView()->cursor().sanitize();
1873 // FIXME: This is slightly expensive, though less than the tocBackend update
1874 // (#9880). This also resets the view in the Toc Widget (#6675).
1875 d.toc_models_.reset(documentBufferView());
1876 // Navigator needs more than a simple update in this case. It needs to be
1878 updateDialog("toc", "");
1882 void GuiView::updateDialog(string const & name, string const & sdata)
1884 if (!isDialogVisible(name))
1887 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1888 if (it == d.dialogs_.end())
1891 Dialog * const dialog = it->second.get();
1892 if (dialog->isVisibleView())
1893 dialog->initialiseParams(sdata);
1897 BufferView * GuiView::documentBufferView()
1899 return currentMainWorkArea()
1900 ? ¤tMainWorkArea()->bufferView()
1905 BufferView const * GuiView::documentBufferView() const
1907 return currentMainWorkArea()
1908 ? ¤tMainWorkArea()->bufferView()
1913 BufferView * GuiView::currentBufferView()
1915 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1919 BufferView const * GuiView::currentBufferView() const
1921 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1925 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1926 Buffer const * orig, Buffer * clone)
1928 bool const success = clone->autoSave();
1930 busyBuffers.remove(orig);
1932 ? _("Automatic save done.")
1933 : _("Automatic save failed!");
1937 void GuiView::autoSave()
1939 LYXERR(Debug::INFO, "Running autoSave()");
1941 Buffer * buffer = documentBufferView()
1942 ? &documentBufferView()->buffer() : nullptr;
1944 resetAutosaveTimers();
1948 GuiViewPrivate::busyBuffers.insert(buffer);
1949 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1950 buffer, buffer->cloneBufferOnly());
1951 d.autosave_watcher_.setFuture(f);
1952 resetAutosaveTimers();
1956 void GuiView::resetAutosaveTimers()
1959 d.autosave_timeout_.restart();
1963 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1966 Buffer * buf = currentBufferView()
1967 ? ¤tBufferView()->buffer() : nullptr;
1968 Buffer * doc_buffer = documentBufferView()
1969 ? &(documentBufferView()->buffer()) : nullptr;
1972 /* In LyX/Mac, when a dialog is open, the menus of the
1973 application can still be accessed without giving focus to
1974 the main window. In this case, we want to disable the menu
1975 entries that are buffer-related.
1976 This code must not be used on Linux and Windows, since it
1977 would disable buffer-related entries when hovering over the
1978 menu (see bug #9574).
1980 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1986 // Check whether we need a buffer
1987 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1988 // no, exit directly
1989 flag.message(from_utf8(N_("Command not allowed with"
1990 "out any document open")));
1991 flag.setEnabled(false);
1995 if (cmd.origin() == FuncRequest::TOC) {
1996 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1997 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1998 flag.setEnabled(false);
2002 switch(cmd.action()) {
2003 case LFUN_BUFFER_IMPORT:
2006 case LFUN_MASTER_BUFFER_EXPORT:
2008 && (doc_buffer->parent() != nullptr
2009 || doc_buffer->hasChildren())
2010 && !d.processing_thread_watcher_.isRunning()
2011 // this launches a dialog, which would be in the wrong Buffer
2012 && !(::lyx::operator==(cmd.argument(), "custom"));
2015 case LFUN_MASTER_BUFFER_UPDATE:
2016 case LFUN_MASTER_BUFFER_VIEW:
2018 && (doc_buffer->parent() != nullptr
2019 || doc_buffer->hasChildren())
2020 && !d.processing_thread_watcher_.isRunning();
2023 case LFUN_BUFFER_UPDATE:
2024 case LFUN_BUFFER_VIEW: {
2025 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2029 string format = to_utf8(cmd.argument());
2030 if (cmd.argument().empty())
2031 format = doc_buffer->params().getDefaultOutputFormat();
2032 enable = doc_buffer->params().isExportable(format, true);
2036 case LFUN_BUFFER_RELOAD:
2037 enable = doc_buffer && !doc_buffer->isUnnamed()
2038 && doc_buffer->fileName().exists()
2039 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2042 case LFUN_BUFFER_RESET_EXPORT:
2043 enable = doc_buffer != nullptr;
2046 case LFUN_BUFFER_CHILD_OPEN:
2047 enable = doc_buffer != nullptr;
2050 case LFUN_MASTER_BUFFER_FORALL: {
2051 if (doc_buffer == nullptr) {
2052 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2056 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2057 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2058 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2063 for (Buffer * buf : doc_buffer->allRelatives()) {
2064 GuiWorkArea * wa = workArea(*buf);
2067 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2068 enable = flag.enabled();
2075 case LFUN_BUFFER_WRITE:
2076 enable = doc_buffer && (doc_buffer->isUnnamed()
2077 || (!doc_buffer->isClean()
2078 || cmd.argument() == "force"));
2081 //FIXME: This LFUN should be moved to GuiApplication.
2082 case LFUN_BUFFER_WRITE_ALL: {
2083 // We enable the command only if there are some modified buffers
2084 Buffer * first = theBufferList().first();
2089 // We cannot use a for loop as the buffer list is a cycle.
2091 if (!b->isClean()) {
2095 b = theBufferList().next(b);
2096 } while (b != first);
2100 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2101 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2104 case LFUN_BUFFER_EXPORT: {
2105 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2109 return doc_buffer->getStatus(cmd, flag);
2112 case LFUN_BUFFER_EXPORT_AS:
2113 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2118 case LFUN_BUFFER_WRITE_AS:
2119 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2120 enable = doc_buffer != nullptr;
2123 case LFUN_EXPORT_CANCEL:
2124 enable = d.processing_thread_watcher_.isRunning();
2127 case LFUN_BUFFER_CLOSE:
2128 case LFUN_VIEW_CLOSE:
2129 enable = doc_buffer != nullptr;
2132 case LFUN_BUFFER_CLOSE_ALL:
2133 enable = theBufferList().last() != theBufferList().first();
2136 case LFUN_BUFFER_CHKTEX: {
2137 // hide if we have no checktex command
2138 if (lyxrc.chktex_command.empty()) {
2139 flag.setUnknown(true);
2143 if (!doc_buffer || !doc_buffer->params().isLatex()
2144 || d.processing_thread_watcher_.isRunning()) {
2145 // grey out, don't hide
2153 case LFUN_VIEW_SPLIT:
2154 if (cmd.getArg(0) == "vertical")
2155 enable = doc_buffer && (d.splitter_->count() == 1 ||
2156 d.splitter_->orientation() == Qt::Vertical);
2158 enable = doc_buffer && (d.splitter_->count() == 1 ||
2159 d.splitter_->orientation() == Qt::Horizontal);
2162 case LFUN_TAB_GROUP_CLOSE:
2163 enable = d.tabWorkAreaCount() > 1;
2166 case LFUN_DEVEL_MODE_TOGGLE:
2167 flag.setOnOff(devel_mode_);
2170 case LFUN_TOOLBAR_SET: {
2171 string const name = cmd.getArg(0);
2172 string const state = cmd.getArg(1);
2173 if (name.empty() || state.empty()) {
2175 docstring const msg =
2176 _("Function toolbar-set requires two arguments!");
2180 if (state != "on" && state != "off" && state != "auto") {
2182 docstring const msg =
2183 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2188 if (GuiToolbar * t = toolbar(name)) {
2189 bool const autovis = t->visibility() & Toolbars::AUTO;
2191 flag.setOnOff(t->isVisible() && !autovis);
2192 else if (state == "off")
2193 flag.setOnOff(!t->isVisible() && !autovis);
2194 else if (state == "auto")
2195 flag.setOnOff(autovis);
2198 docstring const msg =
2199 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2205 case LFUN_TOOLBAR_TOGGLE: {
2206 string const name = cmd.getArg(0);
2207 if (GuiToolbar * t = toolbar(name))
2208 flag.setOnOff(t->isVisible());
2211 docstring const msg =
2212 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2218 case LFUN_TOOLBAR_MOVABLE: {
2219 string const name = cmd.getArg(0);
2220 // use negation since locked == !movable
2222 // toolbar name * locks all toolbars
2223 flag.setOnOff(!toolbarsMovable_);
2224 else if (GuiToolbar * t = toolbar(name))
2225 flag.setOnOff(!(t->isMovable()));
2228 docstring const msg =
2229 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2235 case LFUN_ICON_SIZE:
2236 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2239 case LFUN_DROP_LAYOUTS_CHOICE:
2240 enable = buf != nullptr;
2243 case LFUN_UI_TOGGLE:
2244 flag.setOnOff(isFullScreen());
2247 case LFUN_DIALOG_DISCONNECT_INSET:
2250 case LFUN_DIALOG_HIDE:
2251 // FIXME: should we check if the dialog is shown?
2254 case LFUN_DIALOG_TOGGLE:
2255 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2258 case LFUN_DIALOG_SHOW: {
2259 string const name = cmd.getArg(0);
2261 enable = name == "aboutlyx"
2262 || name == "file" //FIXME: should be removed.
2263 || name == "lyxfiles"
2265 || name == "texinfo"
2266 || name == "progress"
2267 || name == "compare";
2268 else if (name == "character" || name == "symbols"
2269 || name == "mathdelimiter" || name == "mathmatrix") {
2270 if (!buf || buf->isReadonly())
2273 Cursor const & cur = currentBufferView()->cursor();
2274 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2277 else if (name == "latexlog")
2278 enable = FileName(doc_buffer->logName()).isReadableFile();
2279 else if (name == "spellchecker")
2280 enable = theSpellChecker()
2281 && !doc_buffer->text().empty();
2282 else if (name == "vclog")
2283 enable = doc_buffer->lyxvc().inUse();
2287 case LFUN_DIALOG_UPDATE: {
2288 string const name = cmd.getArg(0);
2290 enable = name == "prefs";
2294 case LFUN_COMMAND_EXECUTE:
2296 case LFUN_MENU_OPEN:
2297 // Nothing to check.
2300 case LFUN_COMPLETION_INLINE:
2301 if (!d.current_work_area_
2302 || !d.current_work_area_->completer().inlinePossible(
2303 currentBufferView()->cursor()))
2307 case LFUN_COMPLETION_POPUP:
2308 if (!d.current_work_area_
2309 || !d.current_work_area_->completer().popupPossible(
2310 currentBufferView()->cursor()))
2315 if (!d.current_work_area_
2316 || !d.current_work_area_->completer().inlinePossible(
2317 currentBufferView()->cursor()))
2321 case LFUN_COMPLETION_ACCEPT:
2322 if (!d.current_work_area_
2323 || (!d.current_work_area_->completer().popupVisible()
2324 && !d.current_work_area_->completer().inlineVisible()
2325 && !d.current_work_area_->completer().completionAvailable()))
2329 case LFUN_COMPLETION_CANCEL:
2330 if (!d.current_work_area_
2331 || (!d.current_work_area_->completer().popupVisible()
2332 && !d.current_work_area_->completer().inlineVisible()))
2336 case LFUN_BUFFER_ZOOM_OUT:
2337 case LFUN_BUFFER_ZOOM_IN: {
2338 // only diff between these two is that the default for ZOOM_OUT
2340 bool const neg_zoom =
2341 convert<int>(cmd.argument()) < 0 ||
2342 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2343 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2344 docstring const msg =
2345 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2349 enable = doc_buffer;
2353 case LFUN_BUFFER_ZOOM: {
2354 bool const less_than_min_zoom =
2355 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2356 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2357 docstring const msg =
2358 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2363 enable = doc_buffer;
2367 case LFUN_BUFFER_MOVE_NEXT:
2368 case LFUN_BUFFER_MOVE_PREVIOUS:
2369 // we do not cycle when moving
2370 case LFUN_BUFFER_NEXT:
2371 case LFUN_BUFFER_PREVIOUS:
2372 // because we cycle, it doesn't matter whether on first or last
2373 enable = (d.currentTabWorkArea()->count() > 1);
2375 case LFUN_BUFFER_SWITCH:
2376 // toggle on the current buffer, but do not toggle off
2377 // the other ones (is that a good idea?)
2379 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2380 flag.setOnOff(true);
2383 case LFUN_VC_REGISTER:
2384 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2386 case LFUN_VC_RENAME:
2387 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2390 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2392 case LFUN_VC_CHECK_IN:
2393 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2395 case LFUN_VC_CHECK_OUT:
2396 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2398 case LFUN_VC_LOCKING_TOGGLE:
2399 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2400 && doc_buffer->lyxvc().lockingToggleEnabled();
2401 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2403 case LFUN_VC_REVERT:
2404 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2405 && !doc_buffer->hasReadonlyFlag();
2407 case LFUN_VC_UNDO_LAST:
2408 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2410 case LFUN_VC_REPO_UPDATE:
2411 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2413 case LFUN_VC_COMMAND: {
2414 if (cmd.argument().empty())
2416 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2420 case LFUN_VC_COMPARE:
2421 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2424 case LFUN_SERVER_GOTO_FILE_ROW:
2425 case LFUN_LYX_ACTIVATE:
2426 case LFUN_WINDOW_RAISE:
2428 case LFUN_FORWARD_SEARCH:
2429 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2432 case LFUN_FILE_INSERT_PLAINTEXT:
2433 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2434 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2437 case LFUN_SPELLING_CONTINUOUSLY:
2438 flag.setOnOff(lyxrc.spellcheck_continuously);
2441 case LFUN_CITATION_OPEN:
2450 flag.setEnabled(false);
2456 static FileName selectTemplateFile()
2458 FileDialog dlg(qt_("Select template file"));
2459 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2460 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2462 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2463 QStringList(qt_("LyX Documents (*.lyx)")));
2465 if (result.first == FileDialog::Later)
2467 if (result.second.isEmpty())
2469 return FileName(fromqstr(result.second));
2473 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2477 Buffer * newBuffer = nullptr;
2479 newBuffer = checkAndLoadLyXFile(filename);
2480 } catch (ExceptionMessage const &) {
2487 message(_("Document not loaded."));
2491 setBuffer(newBuffer);
2492 newBuffer->errors("Parse");
2495 theSession().lastFiles().add(filename);
2496 theSession().writeFile();
2503 void GuiView::openDocument(string const & fname)
2505 string initpath = lyxrc.document_path;
2507 if (documentBufferView()) {
2508 string const trypath = documentBufferView()->buffer().filePath();
2509 // If directory is writeable, use this as default.
2510 if (FileName(trypath).isDirWritable())
2516 if (fname.empty()) {
2517 FileDialog dlg(qt_("Select document to open"));
2518 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2519 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2521 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2522 FileDialog::Result result =
2523 dlg.open(toqstr(initpath), filter);
2525 if (result.first == FileDialog::Later)
2528 filename = fromqstr(result.second);
2530 // check selected filename
2531 if (filename.empty()) {
2532 message(_("Canceled."));
2538 // get absolute path of file and add ".lyx" to the filename if
2540 FileName const fullname =
2541 fileSearch(string(), filename, "lyx", support::may_not_exist);
2542 if (!fullname.empty())
2543 filename = fullname.absFileName();
2545 if (!fullname.onlyPath().isDirectory()) {
2546 Alert::warning(_("Invalid filename"),
2547 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2548 from_utf8(fullname.absFileName())));
2552 // if the file doesn't exist and isn't already open (bug 6645),
2553 // let the user create one
2554 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2555 !LyXVC::file_not_found_hook(fullname)) {
2556 // the user specifically chose this name. Believe him.
2557 Buffer * const b = newFile(filename, string(), true);
2563 docstring const disp_fn = makeDisplayPath(filename);
2564 message(bformat(_("Opening document %1$s..."), disp_fn));
2567 Buffer * buf = loadDocument(fullname);
2569 str2 = bformat(_("Document %1$s opened."), disp_fn);
2570 if (buf->lyxvc().inUse())
2571 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2572 " " + _("Version control detected.");
2574 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2579 // FIXME: clean that
2580 static bool import(GuiView * lv, FileName const & filename,
2581 string const & format, ErrorList & errorList)
2583 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2585 string loader_format;
2586 vector<string> loaders = theConverters().loaders();
2587 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2588 for (string const & loader : loaders) {
2589 if (!theConverters().isReachable(format, loader))
2592 string const tofile =
2593 support::changeExtension(filename.absFileName(),
2594 theFormats().extension(loader));
2595 if (theConverters().convert(nullptr, filename, FileName(tofile),
2596 filename, format, loader, errorList) != Converters::SUCCESS)
2598 loader_format = loader;
2601 if (loader_format.empty()) {
2602 frontend::Alert::error(_("Couldn't import file"),
2603 bformat(_("No information for importing the format %1$s."),
2604 translateIfPossible(theFormats().prettyName(format))));
2608 loader_format = format;
2610 if (loader_format == "lyx") {
2611 Buffer * buf = lv->loadDocument(lyxfile);
2615 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2619 bool as_paragraphs = loader_format == "textparagraph";
2620 string filename2 = (loader_format == format) ? filename.absFileName()
2621 : support::changeExtension(filename.absFileName(),
2622 theFormats().extension(loader_format));
2623 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2625 guiApp->setCurrentView(lv);
2626 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2633 void GuiView::importDocument(string const & argument)
2636 string filename = split(argument, format, ' ');
2638 LYXERR(Debug::INFO, format << " file: " << filename);
2640 // need user interaction
2641 if (filename.empty()) {
2642 string initpath = lyxrc.document_path;
2643 if (documentBufferView()) {
2644 string const trypath = documentBufferView()->buffer().filePath();
2645 // If directory is writeable, use this as default.
2646 if (FileName(trypath).isDirWritable())
2650 docstring const text = bformat(_("Select %1$s file to import"),
2651 translateIfPossible(theFormats().prettyName(format)));
2653 FileDialog dlg(toqstr(text));
2654 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2655 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2657 docstring filter = translateIfPossible(theFormats().prettyName(format));
2660 filter += from_utf8(theFormats().extensions(format));
2663 FileDialog::Result result =
2664 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2666 if (result.first == FileDialog::Later)
2669 filename = fromqstr(result.second);
2671 // check selected filename
2672 if (filename.empty())
2673 message(_("Canceled."));
2676 if (filename.empty())
2679 // get absolute path of file
2680 FileName const fullname(support::makeAbsPath(filename));
2682 // Can happen if the user entered a path into the dialog
2684 if (fullname.onlyFileName().empty()) {
2685 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2686 "Aborting import."),
2687 from_utf8(fullname.absFileName()));
2688 frontend::Alert::error(_("File name error"), msg);
2689 message(_("Canceled."));
2694 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2696 // Check if the document already is open
2697 Buffer * buf = theBufferList().getBuffer(lyxfile);
2700 if (!closeBuffer()) {
2701 message(_("Canceled."));
2706 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2708 // if the file exists already, and we didn't do
2709 // -i lyx thefile.lyx, warn
2710 if (lyxfile.exists() && fullname != lyxfile) {
2712 docstring text = bformat(_("The document %1$s already exists.\n\n"
2713 "Do you want to overwrite that document?"), displaypath);
2714 int const ret = Alert::prompt(_("Overwrite document?"),
2715 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2718 message(_("Canceled."));
2723 message(bformat(_("Importing %1$s..."), displaypath));
2724 ErrorList errorList;
2725 if (import(this, fullname, format, errorList))
2726 message(_("imported."));
2728 message(_("file not imported!"));
2730 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2734 void GuiView::newDocument(string const & filename, string templatefile,
2737 FileName initpath(lyxrc.document_path);
2738 if (documentBufferView()) {
2739 FileName const trypath(documentBufferView()->buffer().filePath());
2740 // If directory is writeable, use this as default.
2741 if (trypath.isDirWritable())
2745 if (from_template) {
2746 if (templatefile.empty())
2747 templatefile = selectTemplateFile().absFileName();
2748 if (templatefile.empty())
2753 if (filename.empty())
2754 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2756 b = newFile(filename, templatefile, true);
2761 // If no new document could be created, it is unsure
2762 // whether there is a valid BufferView.
2763 if (currentBufferView())
2764 // Ensure the cursor is correctly positioned on screen.
2765 currentBufferView()->showCursor();
2769 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2771 BufferView * bv = documentBufferView();
2776 FileName filename(to_utf8(fname));
2777 if (filename.empty()) {
2778 // Launch a file browser
2780 string initpath = lyxrc.document_path;
2781 string const trypath = bv->buffer().filePath();
2782 // If directory is writeable, use this as default.
2783 if (FileName(trypath).isDirWritable())
2787 FileDialog dlg(qt_("Select LyX document to insert"));
2788 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2789 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2791 FileDialog::Result result = dlg.open(toqstr(initpath),
2792 QStringList(qt_("LyX Documents (*.lyx)")));
2794 if (result.first == FileDialog::Later)
2798 filename.set(fromqstr(result.second));
2800 // check selected filename
2801 if (filename.empty()) {
2802 // emit message signal.
2803 message(_("Canceled."));
2808 bv->insertLyXFile(filename, ignorelang);
2809 bv->buffer().errors("Parse");
2814 string const GuiView::getTemplatesPath(Buffer & b)
2816 // We start off with the user's templates path
2817 string result = addPath(package().user_support().absFileName(), "templates");
2818 // Check for the document language
2819 string const langcode = b.params().language->code();
2820 string const shortcode = langcode.substr(0, 2);
2821 if (!langcode.empty() && shortcode != "en") {
2822 string subpath = addPath(result, shortcode);
2823 string subpath_long = addPath(result, langcode);
2824 // If we have a subdirectory for the language already,
2826 FileName sp = FileName(subpath);
2827 if (sp.isDirectory())
2829 else if (FileName(subpath_long).isDirectory())
2830 result = subpath_long;
2832 // Ask whether we should create such a subdirectory
2833 docstring const text =
2834 bformat(_("It is suggested to save the template in a subdirectory\n"
2835 "appropriate to the document language (%1$s).\n"
2836 "This subdirectory does not exists yet.\n"
2837 "Do you want to create it?"),
2838 _(b.params().language->display()));
2839 if (Alert::prompt(_("Create Language Directory?"),
2840 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2841 // If the user agreed, we try to create it and report if this failed.
2842 if (!sp.createDirectory(0777))
2843 Alert::error(_("Subdirectory creation failed!"),
2844 _("Could not create subdirectory.\n"
2845 "The template will be saved in the parent directory."));
2851 // Do we have a layout category?
2852 string const cat = b.params().baseClass() ?
2853 b.params().baseClass()->category()
2856 string subpath = addPath(result, cat);
2857 // If we have a subdirectory for the category already,
2859 FileName sp = FileName(subpath);
2860 if (sp.isDirectory())
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 layout category (%1$s).\n"
2867 "This subdirectory does not exists yet.\n"
2868 "Do you want to create it?"),
2870 if (Alert::prompt(_("Create Category 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."));
2886 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2888 FileName fname = b.fileName();
2889 FileName const oldname = fname;
2890 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2892 if (!newname.empty()) {
2895 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2897 fname = support::makeAbsPath(to_utf8(newname),
2898 oldname.onlyPath().absFileName());
2900 // Switch to this Buffer.
2903 // No argument? Ask user through dialog.
2905 QString const title = as_template ? qt_("Choose a filename to save template as")
2906 : qt_("Choose a filename to save document as");
2907 FileDialog dlg(title);
2908 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2909 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2911 if (!isLyXFileName(fname.absFileName()))
2912 fname.changeExtension(".lyx");
2914 string const path = as_template ?
2916 : fname.onlyPath().absFileName();
2917 FileDialog::Result result =
2918 dlg.save(toqstr(path),
2919 QStringList(qt_("LyX Documents (*.lyx)")),
2920 toqstr(fname.onlyFileName()));
2922 if (result.first == FileDialog::Later)
2925 fname.set(fromqstr(result.second));
2930 if (!isLyXFileName(fname.absFileName()))
2931 fname.changeExtension(".lyx");
2934 // fname is now the new Buffer location.
2936 // if there is already a Buffer open with this name, we do not want
2937 // to have another one. (the second test makes sure we're not just
2938 // trying to overwrite ourselves, which is fine.)
2939 if (theBufferList().exists(fname) && fname != oldname
2940 && theBufferList().getBuffer(fname) != &b) {
2941 docstring const text =
2942 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2943 "Please close it before attempting to overwrite it.\n"
2944 "Do you want to choose a new filename?"),
2945 from_utf8(fname.absFileName()));
2946 int const ret = Alert::prompt(_("Chosen File Already Open"),
2947 text, 0, 1, _("&Rename"), _("&Cancel"));
2949 case 0: return renameBuffer(b, docstring(), kind);
2950 case 1: return false;
2955 bool const existsLocal = fname.exists();
2956 bool const existsInVC = LyXVC::fileInVC(fname);
2957 if (existsLocal || existsInVC) {
2958 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2959 if (kind != LV_WRITE_AS && existsInVC) {
2960 // renaming to a name that is already in VC
2962 docstring text = bformat(_("The document %1$s "
2963 "is already registered.\n\n"
2964 "Do you want to choose a new name?"),
2966 docstring const title = (kind == LV_VC_RENAME) ?
2967 _("Rename document?") : _("Copy document?");
2968 docstring const button = (kind == LV_VC_RENAME) ?
2969 _("&Rename") : _("&Copy");
2970 int const ret = Alert::prompt(title, text, 0, 1,
2971 button, _("&Cancel"));
2973 case 0: return renameBuffer(b, docstring(), kind);
2974 case 1: return false;
2979 docstring text = bformat(_("The document %1$s "
2980 "already exists.\n\n"
2981 "Do you want to overwrite that document?"),
2983 int const ret = Alert::prompt(_("Overwrite document?"),
2984 text, 0, 2, _("&Overwrite"),
2985 _("&Rename"), _("&Cancel"));
2988 case 1: return renameBuffer(b, docstring(), kind);
2989 case 2: return false;
2995 case LV_VC_RENAME: {
2996 string msg = b.lyxvc().rename(fname);
2999 message(from_utf8(msg));
3003 string msg = b.lyxvc().copy(fname);
3006 message(from_utf8(msg));
3010 case LV_WRITE_AS_TEMPLATE:
3013 // LyXVC created the file already in case of LV_VC_RENAME or
3014 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3015 // relative paths of included stuff right if we moved e.g. from
3016 // /a/b.lyx to /a/c/b.lyx.
3018 bool const saved = saveBuffer(b, fname);
3025 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3027 FileName fname = b.fileName();
3029 FileDialog dlg(qt_("Choose a filename to export the document as"));
3030 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3033 QString const anyformat = qt_("Guess from extension (*.*)");
3036 vector<Format const *> export_formats;
3037 for (Format const & f : theFormats())
3038 if (f.documentFormat())
3039 export_formats.push_back(&f);
3040 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3041 map<QString, string> fmap;
3044 for (Format const * f : export_formats) {
3045 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3046 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3048 from_ascii(f->extension())));
3049 types << loc_filter;
3050 fmap[loc_filter] = f->name();
3051 if (from_ascii(f->name()) == iformat) {
3052 filter = loc_filter;
3053 ext = f->extension();
3056 string ofname = fname.onlyFileName();
3058 ofname = support::changeExtension(ofname, ext);
3059 FileDialog::Result result =
3060 dlg.save(toqstr(fname.onlyPath().absFileName()),
3064 if (result.first != FileDialog::Chosen)
3068 fname.set(fromqstr(result.second));
3069 if (filter == anyformat)
3070 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3072 fmt_name = fmap[filter];
3073 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3074 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3076 if (fmt_name.empty() || fname.empty())
3079 // fname is now the new Buffer location.
3080 if (FileName(fname).exists()) {
3081 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3082 docstring text = bformat(_("The document %1$s already "
3083 "exists.\n\nDo you want to "
3084 "overwrite that document?"),
3086 int const ret = Alert::prompt(_("Overwrite document?"),
3087 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3090 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3091 case 2: return false;
3095 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3098 return dr.dispatched();
3102 bool GuiView::saveBuffer(Buffer & b)
3104 return saveBuffer(b, FileName());
3108 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3110 if (workArea(b) && workArea(b)->inDialogMode())
3113 if (fn.empty() && b.isUnnamed())
3114 return renameBuffer(b, docstring());
3116 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3118 theSession().lastFiles().add(b.fileName());
3119 theSession().writeFile();
3123 // Switch to this Buffer.
3126 // FIXME: we don't tell the user *WHY* the save failed !!
3127 docstring const file = makeDisplayPath(b.absFileName(), 30);
3128 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3129 "Do you want to rename the document and "
3130 "try again?"), file);
3131 int const ret = Alert::prompt(_("Rename and save?"),
3132 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3135 if (!renameBuffer(b, docstring()))
3144 return saveBuffer(b, fn);
3148 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3150 return closeWorkArea(wa, false);
3154 // We only want to close the buffer if it is not visible in other workareas
3155 // of the same view, nor in other views, and if this is not a child
3156 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3158 Buffer & buf = wa->bufferView().buffer();
3160 bool last_wa = d.countWorkAreasOf(buf) == 1
3161 && !inOtherView(buf) && !buf.parent();
3163 bool close_buffer = last_wa;
3166 if (lyxrc.close_buffer_with_last_view == "yes")
3168 else if (lyxrc.close_buffer_with_last_view == "no")
3169 close_buffer = false;
3172 if (buf.isUnnamed())
3173 file = from_utf8(buf.fileName().onlyFileName());
3175 file = buf.fileName().displayName(30);
3176 docstring const text = bformat(
3177 _("Last view on document %1$s is being closed.\n"
3178 "Would you like to close or hide the document?\n"
3180 "Hidden documents can be displayed back through\n"
3181 "the menu: View->Hidden->...\n"
3183 "To remove this question, set your preference in:\n"
3184 " Tools->Preferences->Look&Feel->UserInterface\n"
3186 int ret = Alert::prompt(_("Close or hide document?"),
3187 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3190 close_buffer = (ret == 0);
3194 return closeWorkArea(wa, close_buffer);
3198 bool GuiView::closeBuffer()
3200 GuiWorkArea * wa = currentMainWorkArea();
3201 // coverity complained about this
3202 // it seems unnecessary, but perhaps is worth the check
3203 LASSERT(wa, return false);
3205 setCurrentWorkArea(wa);
3206 Buffer & buf = wa->bufferView().buffer();
3207 return closeWorkArea(wa, !buf.parent());
3211 void GuiView::writeSession() const {
3212 GuiWorkArea const * active_wa = currentMainWorkArea();
3213 for (int i = 0; i < d.splitter_->count(); ++i) {
3214 TabWorkArea * twa = d.tabWorkArea(i);
3215 for (int j = 0; j < twa->count(); ++j) {
3216 GuiWorkArea * wa = twa->workArea(j);
3217 Buffer & buf = wa->bufferView().buffer();
3218 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3224 bool GuiView::closeBufferAll()
3227 for (auto & buf : theBufferList()) {
3228 if (!saveBufferIfNeeded(*buf, false)) {
3229 // Closing has been cancelled, so abort.
3234 // Close the workareas in all other views
3235 QList<int> const ids = guiApp->viewIds();
3236 for (int i = 0; i != ids.size(); ++i) {
3237 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3241 // Close our own workareas
3242 if (!closeWorkAreaAll())
3249 bool GuiView::closeWorkAreaAll()
3251 setCurrentWorkArea(currentMainWorkArea());
3253 // We might be in a situation that there is still a tabWorkArea, but
3254 // there are no tabs anymore. This can happen when we get here after a
3255 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3256 // many TabWorkArea's have no documents anymore.
3259 // We have to call count() each time, because it can happen that
3260 // more than one splitter will disappear in one iteration (bug 5998).
3261 while (d.splitter_->count() > empty_twa) {
3262 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3264 if (twa->count() == 0)
3267 setCurrentWorkArea(twa->currentWorkArea());
3268 if (!closeTabWorkArea(twa))
3276 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3281 Buffer & buf = wa->bufferView().buffer();
3283 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3284 Alert::warning(_("Close document"),
3285 _("Document could not be closed because it is being processed by LyX."));
3290 return closeBuffer(buf);
3292 if (!inMultiTabs(wa))
3293 if (!saveBufferIfNeeded(buf, true))
3301 bool GuiView::closeBuffer(Buffer & buf)
3303 bool success = true;
3304 for (Buffer * child_buf : buf.getChildren()) {
3305 if (theBufferList().isOthersChild(&buf, child_buf)) {
3306 child_buf->setParent(nullptr);
3310 // FIXME: should we look in other tabworkareas?
3311 // ANSWER: I don't think so. I've tested, and if the child is
3312 // open in some other window, it closes without a problem.
3313 GuiWorkArea * child_wa = workArea(*child_buf);
3316 // If we are in a close_event all children will be closed in some time,
3317 // so no need to do it here. This will ensure that the children end up
3318 // in the session file in the correct order. If we close the master
3319 // buffer, we can close or release the child buffers here too.
3321 success = closeWorkArea(child_wa, true);
3325 // In this case the child buffer is open but hidden.
3326 // Even in this case, children can be dirty (e.g.,
3327 // after a label change in the master, see #11405).
3328 // Therefore, check this
3329 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3330 // If we are in a close_event all children will be closed in some time,
3331 // so no need to do it here. This will ensure that the children end up
3332 // in the session file in the correct order. If we close the master
3333 // buffer, we can close or release the child buffers here too.
3336 // Save dirty buffers also if closing_!
3337 if (saveBufferIfNeeded(*child_buf, false)) {
3338 child_buf->removeAutosaveFile();
3339 theBufferList().release(child_buf);
3341 // Saving of dirty children has been cancelled.
3342 // Cancel the whole process.
3349 // goto bookmark to update bookmark pit.
3350 // FIXME: we should update only the bookmarks related to this buffer!
3351 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3352 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3353 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3354 guiApp->gotoBookmark(i, false, false);
3356 if (saveBufferIfNeeded(buf, false)) {
3357 buf.removeAutosaveFile();
3358 theBufferList().release(&buf);
3362 // open all children again to avoid a crash because of dangling
3363 // pointers (bug 6603)
3369 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3371 while (twa == d.currentTabWorkArea()) {
3372 twa->setCurrentIndex(twa->count() - 1);
3374 GuiWorkArea * wa = twa->currentWorkArea();
3375 Buffer & b = wa->bufferView().buffer();
3377 // We only want to close the buffer if the same buffer is not visible
3378 // in another view, and if this is not a child and if we are closing
3379 // a view (not a tabgroup).
3380 bool const close_buffer =
3381 !inOtherView(b) && !b.parent() && closing_;
3383 if (!closeWorkArea(wa, close_buffer))
3390 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3392 if (buf.isClean() || buf.paragraphs().empty())
3395 // Switch to this Buffer.
3401 if (buf.isUnnamed()) {
3402 file = from_utf8(buf.fileName().onlyFileName());
3405 FileName filename = buf.fileName();
3407 file = filename.displayName(30);
3408 exists = filename.exists();
3411 // Bring this window to top before asking questions.
3416 if (hiding && buf.isUnnamed()) {
3417 docstring const text = bformat(_("The document %1$s has not been "
3418 "saved yet.\n\nDo you want to save "
3419 "the document?"), file);
3420 ret = Alert::prompt(_("Save new document?"),
3421 text, 0, 1, _("&Save"), _("&Cancel"));
3425 docstring const text = exists ?
3426 bformat(_("The document %1$s has unsaved changes."
3427 "\n\nDo you want to save the document or "
3428 "discard the changes?"), file) :
3429 bformat(_("The document %1$s has not been saved yet."
3430 "\n\nDo you want to save the document or "
3431 "discard it entirely?"), file);
3432 docstring const title = exists ?
3433 _("Save changed document?") : _("Save document?");
3434 ret = Alert::prompt(title, text, 0, 2,
3435 _("&Save"), _("&Discard"), _("&Cancel"));
3440 if (!saveBuffer(buf))
3444 // If we crash after this we could have no autosave file
3445 // but I guess this is really improbable (Jug).
3446 // Sometimes improbable things happen:
3447 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3448 // buf.removeAutosaveFile();
3450 // revert all changes
3461 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3463 Buffer & buf = wa->bufferView().buffer();
3465 for (int i = 0; i != d.splitter_->count(); ++i) {
3466 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3467 if (wa_ && wa_ != wa)
3470 return inOtherView(buf);
3474 bool GuiView::inOtherView(Buffer & buf)
3476 QList<int> const ids = guiApp->viewIds();
3478 for (int i = 0; i != ids.size(); ++i) {
3482 if (guiApp->view(ids[i]).workArea(buf))
3489 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3491 if (!documentBufferView())
3494 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3495 Buffer * const curbuf = &documentBufferView()->buffer();
3496 int nwa = twa->count();
3497 for (int i = 0; i < nwa; ++i) {
3498 if (&workArea(i)->bufferView().buffer() == curbuf) {
3500 if (np == NEXTBUFFER)
3501 next_index = (i == nwa - 1 ? 0 : i + 1);
3503 next_index = (i == 0 ? nwa - 1 : i - 1);
3505 twa->moveTab(i, next_index);
3507 setBuffer(&workArea(next_index)->bufferView().buffer());
3515 /// make sure the document is saved
3516 static bool ensureBufferClean(Buffer * buffer)
3518 LASSERT(buffer, return false);
3519 if (buffer->isClean() && !buffer->isUnnamed())
3522 docstring const file = buffer->fileName().displayName(30);
3525 if (!buffer->isUnnamed()) {
3526 text = bformat(_("The document %1$s has unsaved "
3527 "changes.\n\nDo you want to save "
3528 "the document?"), file);
3529 title = _("Save changed document?");
3532 text = bformat(_("The document %1$s has not been "
3533 "saved yet.\n\nDo you want to save "
3534 "the document?"), file);
3535 title = _("Save new document?");
3537 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3540 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3542 return buffer->isClean() && !buffer->isUnnamed();
3546 bool GuiView::reloadBuffer(Buffer & buf)
3548 currentBufferView()->cursor().reset();
3549 Buffer::ReadStatus status = buf.reload();
3550 return status == Buffer::ReadSuccess;
3554 void GuiView::checkExternallyModifiedBuffers()
3556 for (Buffer * buf : theBufferList()) {
3557 if (buf->fileName().exists() && buf->isChecksumModified()) {
3558 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3559 " Reload now? Any local changes will be lost."),
3560 from_utf8(buf->absFileName()));
3561 int const ret = Alert::prompt(_("Reload externally changed document?"),
3562 text, 0, 1, _("&Reload"), _("&Cancel"));
3570 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3572 Buffer * buffer = documentBufferView()
3573 ? &(documentBufferView()->buffer()) : nullptr;
3575 switch (cmd.action()) {
3576 case LFUN_VC_REGISTER:
3577 if (!buffer || !ensureBufferClean(buffer))
3579 if (!buffer->lyxvc().inUse()) {
3580 if (buffer->lyxvc().registrer()) {
3581 reloadBuffer(*buffer);
3582 dr.clearMessageUpdate();
3587 case LFUN_VC_RENAME:
3588 case LFUN_VC_COPY: {
3589 if (!buffer || !ensureBufferClean(buffer))
3591 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3592 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3593 // Some changes are not yet committed.
3594 // We test here and not in getStatus(), since
3595 // this test is expensive.
3597 LyXVC::CommandResult ret =
3598 buffer->lyxvc().checkIn(log);
3600 if (ret == LyXVC::ErrorCommand ||
3601 ret == LyXVC::VCSuccess)
3602 reloadBuffer(*buffer);
3603 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3604 frontend::Alert::error(
3605 _("Revision control error."),
3606 _("Document could not be checked in."));
3610 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3611 LV_VC_RENAME : LV_VC_COPY;
3612 renameBuffer(*buffer, cmd.argument(), kind);
3617 case LFUN_VC_CHECK_IN:
3618 if (!buffer || !ensureBufferClean(buffer))
3620 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3622 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3624 // Only skip reloading if the checkin was cancelled or
3625 // an error occurred before the real checkin VCS command
3626 // was executed, since the VCS might have changed the
3627 // file even if it could not checkin successfully.
3628 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3629 reloadBuffer(*buffer);
3633 case LFUN_VC_CHECK_OUT:
3634 if (!buffer || !ensureBufferClean(buffer))
3636 if (buffer->lyxvc().inUse()) {
3637 dr.setMessage(buffer->lyxvc().checkOut());
3638 reloadBuffer(*buffer);
3642 case LFUN_VC_LOCKING_TOGGLE:
3643 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3645 if (buffer->lyxvc().inUse()) {
3646 string res = buffer->lyxvc().lockingToggle();
3648 frontend::Alert::error(_("Revision control error."),
3649 _("Error when setting the locking property."));
3652 reloadBuffer(*buffer);
3657 case LFUN_VC_REVERT:
3660 if (buffer->lyxvc().revert()) {
3661 reloadBuffer(*buffer);
3662 dr.clearMessageUpdate();
3666 case LFUN_VC_UNDO_LAST:
3669 buffer->lyxvc().undoLast();
3670 reloadBuffer(*buffer);
3671 dr.clearMessageUpdate();
3674 case LFUN_VC_REPO_UPDATE:
3677 if (ensureBufferClean(buffer)) {
3678 dr.setMessage(buffer->lyxvc().repoUpdate());
3679 checkExternallyModifiedBuffers();
3683 case LFUN_VC_COMMAND: {
3684 string flag = cmd.getArg(0);
3685 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3688 if (contains(flag, 'M')) {
3689 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3692 string path = cmd.getArg(1);
3693 if (contains(path, "$$p") && buffer)
3694 path = subst(path, "$$p", buffer->filePath());
3695 LYXERR(Debug::LYXVC, "Directory: " << path);
3697 if (!pp.isReadableDirectory()) {
3698 lyxerr << _("Directory is not accessible.") << endl;
3701 support::PathChanger p(pp);
3703 string command = cmd.getArg(2);
3704 if (command.empty())
3707 command = subst(command, "$$i", buffer->absFileName());
3708 command = subst(command, "$$p", buffer->filePath());
3710 command = subst(command, "$$m", to_utf8(message));
3711 LYXERR(Debug::LYXVC, "Command: " << command);
3713 one.startscript(Systemcall::Wait, command);
3717 if (contains(flag, 'I'))
3718 buffer->markDirty();
3719 if (contains(flag, 'R'))
3720 reloadBuffer(*buffer);
3725 case LFUN_VC_COMPARE: {
3726 if (cmd.argument().empty()) {
3727 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3733 string rev1 = cmd.getArg(0);
3737 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3740 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3741 f2 = buffer->absFileName();
3743 string rev2 = cmd.getArg(1);
3747 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3751 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3752 f1 << "\n" << f2 << "\n" );
3753 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3754 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3764 void GuiView::openChildDocument(string const & fname)
3766 LASSERT(documentBufferView(), return);
3767 Buffer & buffer = documentBufferView()->buffer();
3768 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3769 documentBufferView()->saveBookmark(false);
3770 Buffer * child = nullptr;
3771 if (theBufferList().exists(filename)) {
3772 child = theBufferList().getBuffer(filename);
3775 message(bformat(_("Opening child document %1$s..."),
3776 makeDisplayPath(filename.absFileName())));
3777 child = loadDocument(filename, false);
3779 // Set the parent name of the child document.
3780 // This makes insertion of citations and references in the child work,
3781 // when the target is in the parent or another child document.
3783 child->setParent(&buffer);
3787 bool GuiView::goToFileRow(string const & argument)
3791 size_t i = argument.find_last_of(' ');
3792 if (i != string::npos) {
3793 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3794 istringstream is(argument.substr(i + 1));
3799 if (i == string::npos) {
3800 LYXERR0("Wrong argument: " << argument);
3803 Buffer * buf = nullptr;
3804 string const realtmp = package().temp_dir().realPath();
3805 // We have to use os::path_prefix_is() here, instead of
3806 // simply prefixIs(), because the file name comes from
3807 // an external application and may need case adjustment.
3808 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3809 buf = theBufferList().getBufferFromTmp(file_name, true);
3810 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3811 << (buf ? " success" : " failed"));
3813 // Must replace extension of the file to be .lyx
3814 // and get full path
3815 FileName const s = fileSearch(string(),
3816 support::changeExtension(file_name, ".lyx"), "lyx");
3817 // Either change buffer or load the file
3818 if (theBufferList().exists(s))
3819 buf = theBufferList().getBuffer(s);
3820 else if (s.exists()) {
3821 buf = loadDocument(s);
3826 _("File does not exist: %1$s"),
3827 makeDisplayPath(file_name)));
3833 _("No buffer for file: %1$s."),
3834 makeDisplayPath(file_name))
3839 bool success = documentBufferView()->setCursorFromRow(row);
3841 LYXERR(Debug::LATEX,
3842 "setCursorFromRow: invalid position for row " << row);
3843 frontend::Alert::error(_("Inverse Search Failed"),
3844 _("Invalid position requested by inverse search.\n"
3845 "You may need to update the viewed document."));
3851 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3853 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3854 menu->exec(QCursor::pos());
3859 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3860 Buffer const * orig, Buffer * clone, string const & format)
3862 Buffer::ExportStatus const status = func(format);
3864 // the cloning operation will have produced a clone of the entire set of
3865 // documents, starting from the master. so we must delete those.
3866 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3868 busyBuffers.remove(orig);
3873 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3874 Buffer const * orig, Buffer * clone, string const & format)
3876 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3878 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3882 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3883 Buffer const * orig, Buffer * clone, string const & format)
3885 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3887 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3891 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3892 Buffer const * orig, Buffer * clone, string const & format)
3894 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3896 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3900 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3901 Buffer const * used_buffer,
3902 docstring const & msg,
3903 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3904 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3905 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3906 bool allow_async, bool use_tmpdir)
3911 string format = argument;
3913 format = used_buffer->params().getDefaultOutputFormat();
3914 processing_format = format;
3916 progress_->clearMessages();
3919 #if EXPORT_in_THREAD
3921 GuiViewPrivate::busyBuffers.insert(used_buffer);
3922 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3923 if (!cloned_buffer) {
3924 Alert::error(_("Export Error"),
3925 _("Error cloning the Buffer."));
3928 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3933 setPreviewFuture(f);
3934 last_export_format = used_buffer->params().bufferFormat();
3937 // We are asynchronous, so we don't know here anything about the success
3940 Buffer::ExportStatus status;
3942 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3943 } else if (previewFunc) {
3944 status = (used_buffer->*previewFunc)(format);
3947 handleExportStatus(gv_, status, format);
3949 return (status == Buffer::ExportSuccess
3950 || status == Buffer::PreviewSuccess);
3954 Buffer::ExportStatus status;
3956 status = (used_buffer->*syncFunc)(format, true);
3957 } else if (previewFunc) {
3958 status = (used_buffer->*previewFunc)(format);
3961 handleExportStatus(gv_, status, format);
3963 return (status == Buffer::ExportSuccess
3964 || status == Buffer::PreviewSuccess);
3968 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3970 BufferView * bv = currentBufferView();
3971 LASSERT(bv, return);
3973 // Let the current BufferView dispatch its own actions.
3974 bv->dispatch(cmd, dr);
3975 if (dr.dispatched()) {
3976 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3977 updateDialog("document", "");
3981 // Try with the document BufferView dispatch if any.
3982 BufferView * doc_bv = documentBufferView();
3983 if (doc_bv && doc_bv != bv) {
3984 doc_bv->dispatch(cmd, dr);
3985 if (dr.dispatched()) {
3986 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3987 updateDialog("document", "");
3992 // Then let the current Cursor dispatch its own actions.
3993 bv->cursor().dispatch(cmd);
3995 // update completion. We do it here and not in
3996 // processKeySym to avoid another redraw just for a
3997 // changed inline completion
3998 if (cmd.origin() == FuncRequest::KEYBOARD) {
3999 if (cmd.action() == LFUN_SELF_INSERT
4000 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4001 updateCompletion(bv->cursor(), true, true);
4002 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4003 updateCompletion(bv->cursor(), false, true);
4005 updateCompletion(bv->cursor(), false, false);
4008 dr = bv->cursor().result();
4012 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4014 BufferView * bv = currentBufferView();
4015 // By default we won't need any update.
4016 dr.screenUpdate(Update::None);
4017 // assume cmd will be dispatched
4018 dr.dispatched(true);
4020 Buffer * doc_buffer = documentBufferView()
4021 ? &(documentBufferView()->buffer()) : nullptr;
4023 if (cmd.origin() == FuncRequest::TOC) {
4024 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4025 toc->doDispatch(bv->cursor(), cmd, dr);
4029 string const argument = to_utf8(cmd.argument());
4031 switch(cmd.action()) {
4032 case LFUN_BUFFER_CHILD_OPEN:
4033 openChildDocument(to_utf8(cmd.argument()));
4036 case LFUN_BUFFER_IMPORT:
4037 importDocument(to_utf8(cmd.argument()));
4040 case LFUN_MASTER_BUFFER_EXPORT:
4042 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4044 case LFUN_BUFFER_EXPORT: {
4047 // GCC only sees strfwd.h when building merged
4048 if (::lyx::operator==(cmd.argument(), "custom")) {
4049 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4050 // so the following test should not be needed.
4051 // In principle, we could try to switch to such a view...
4052 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4053 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4057 string const dest = cmd.getArg(1);
4058 FileName target_dir;
4059 if (!dest.empty() && FileName::isAbsolute(dest))
4060 target_dir = FileName(support::onlyPath(dest));
4062 target_dir = doc_buffer->fileName().onlyPath();
4064 string const format = (argument.empty() || argument == "default") ?
4065 doc_buffer->params().getDefaultOutputFormat() : argument;
4067 if ((dest.empty() && doc_buffer->isUnnamed())
4068 || !target_dir.isDirWritable()) {
4069 exportBufferAs(*doc_buffer, from_utf8(format));
4072 /* TODO/Review: Is it a problem to also export the children?
4073 See the update_unincluded flag */
4074 d.asyncBufferProcessing(format,
4077 &GuiViewPrivate::exportAndDestroy,
4079 nullptr, cmd.allowAsync());
4080 // TODO Inform user about success
4084 case LFUN_BUFFER_EXPORT_AS: {
4085 LASSERT(doc_buffer, break);
4086 docstring f = cmd.argument();
4088 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4089 exportBufferAs(*doc_buffer, f);
4093 case LFUN_BUFFER_UPDATE: {
4094 d.asyncBufferProcessing(argument,
4097 &GuiViewPrivate::compileAndDestroy,
4099 nullptr, cmd.allowAsync(), true);
4102 case LFUN_BUFFER_VIEW: {
4103 d.asyncBufferProcessing(argument,
4105 _("Previewing ..."),
4106 &GuiViewPrivate::previewAndDestroy,
4108 &Buffer::preview, cmd.allowAsync());
4111 case LFUN_MASTER_BUFFER_UPDATE: {
4112 d.asyncBufferProcessing(argument,
4113 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4115 &GuiViewPrivate::compileAndDestroy,
4117 nullptr, cmd.allowAsync(), true);
4120 case LFUN_MASTER_BUFFER_VIEW: {
4121 d.asyncBufferProcessing(argument,
4122 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4124 &GuiViewPrivate::previewAndDestroy,
4125 nullptr, &Buffer::preview, cmd.allowAsync());
4128 case LFUN_EXPORT_CANCEL: {
4129 Systemcall::killscript();
4132 case LFUN_BUFFER_SWITCH: {
4133 string const file_name = to_utf8(cmd.argument());
4134 if (!FileName::isAbsolute(file_name)) {
4136 dr.setMessage(_("Absolute filename expected."));
4140 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4143 dr.setMessage(_("Document not loaded"));
4147 // Do we open or switch to the buffer in this view ?
4148 if (workArea(*buffer)
4149 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4154 // Look for the buffer in other views
4155 QList<int> const ids = guiApp->viewIds();
4157 for (; i != ids.size(); ++i) {
4158 GuiView & gv = guiApp->view(ids[i]);
4159 if (gv.workArea(*buffer)) {
4161 gv.activateWindow();
4163 gv.setBuffer(buffer);
4168 // If necessary, open a new window as a last resort
4169 if (i == ids.size()) {
4170 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4176 case LFUN_BUFFER_NEXT:
4177 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4180 case LFUN_BUFFER_MOVE_NEXT:
4181 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4184 case LFUN_BUFFER_PREVIOUS:
4185 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4188 case LFUN_BUFFER_MOVE_PREVIOUS:
4189 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4192 case LFUN_BUFFER_CHKTEX:
4193 LASSERT(doc_buffer, break);
4194 doc_buffer->runChktex();
4197 case LFUN_COMMAND_EXECUTE: {
4198 command_execute_ = true;
4199 minibuffer_focus_ = true;
4202 case LFUN_DROP_LAYOUTS_CHOICE:
4203 d.layout_->showPopup();
4206 case LFUN_MENU_OPEN:
4207 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4208 menu->exec(QCursor::pos());
4211 case LFUN_FILE_INSERT: {
4212 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4213 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4214 dr.forceBufferUpdate();
4215 dr.screenUpdate(Update::Force);
4220 case LFUN_FILE_INSERT_PLAINTEXT:
4221 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4222 string const fname = to_utf8(cmd.argument());
4223 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4224 dr.setMessage(_("Absolute filename expected."));
4228 FileName filename(fname);
4229 if (fname.empty()) {
4230 FileDialog dlg(qt_("Select file to insert"));
4232 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4233 QStringList(qt_("All Files (*)")));
4235 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4236 dr.setMessage(_("Canceled."));
4240 filename.set(fromqstr(result.second));
4244 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4245 bv->dispatch(new_cmd, dr);
4250 case LFUN_BUFFER_RELOAD: {
4251 LASSERT(doc_buffer, break);
4254 bool drop = (cmd.argument() == "dump");
4257 if (!drop && !doc_buffer->isClean()) {
4258 docstring const file =
4259 makeDisplayPath(doc_buffer->absFileName(), 20);
4260 if (doc_buffer->notifiesExternalModification()) {
4261 docstring text = _("The current version will be lost. "
4262 "Are you sure you want to load the version on disk "
4263 "of the document %1$s?");
4264 ret = Alert::prompt(_("Reload saved document?"),
4265 bformat(text, file), 1, 1,
4266 _("&Reload"), _("&Cancel"));
4268 docstring text = _("Any changes will be lost. "
4269 "Are you sure you want to revert to the saved version "
4270 "of the document %1$s?");
4271 ret = Alert::prompt(_("Revert to saved document?"),
4272 bformat(text, file), 1, 1,
4273 _("&Revert"), _("&Cancel"));
4278 doc_buffer->markClean();
4279 reloadBuffer(*doc_buffer);
4280 dr.forceBufferUpdate();
4285 case LFUN_BUFFER_RESET_EXPORT:
4286 LASSERT(doc_buffer, break);
4287 doc_buffer->requireFreshStart(true);
4288 dr.setMessage(_("Buffer export reset."));
4291 case LFUN_BUFFER_WRITE:
4292 LASSERT(doc_buffer, break);
4293 saveBuffer(*doc_buffer);
4296 case LFUN_BUFFER_WRITE_AS:
4297 LASSERT(doc_buffer, break);
4298 renameBuffer(*doc_buffer, cmd.argument());
4301 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4302 LASSERT(doc_buffer, break);
4303 renameBuffer(*doc_buffer, cmd.argument(),
4304 LV_WRITE_AS_TEMPLATE);
4307 case LFUN_BUFFER_WRITE_ALL: {
4308 Buffer * first = theBufferList().first();
4311 message(_("Saving all documents..."));
4312 // We cannot use a for loop as the buffer list cycles.
4315 if (!b->isClean()) {
4317 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4319 b = theBufferList().next(b);
4320 } while (b != first);
4321 dr.setMessage(_("All documents saved."));
4325 case LFUN_MASTER_BUFFER_FORALL: {
4329 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4330 funcToRun.allowAsync(false);
4332 for (Buffer const * buf : doc_buffer->allRelatives()) {
4333 // Switch to other buffer view and resend cmd
4334 lyx::dispatch(FuncRequest(
4335 LFUN_BUFFER_SWITCH, buf->absFileName()));
4336 lyx::dispatch(funcToRun);
4339 lyx::dispatch(FuncRequest(
4340 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4344 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4345 LASSERT(doc_buffer, break);
4346 doc_buffer->clearExternalModification();
4349 case LFUN_BUFFER_CLOSE:
4353 case LFUN_BUFFER_CLOSE_ALL:
4357 case LFUN_DEVEL_MODE_TOGGLE:
4358 devel_mode_ = !devel_mode_;
4360 dr.setMessage(_("Developer mode is now enabled."));
4362 dr.setMessage(_("Developer mode is now disabled."));
4365 case LFUN_TOOLBAR_SET: {
4366 string const name = cmd.getArg(0);
4367 string const state = cmd.getArg(1);
4368 if (GuiToolbar * t = toolbar(name))
4373 case LFUN_TOOLBAR_TOGGLE: {
4374 string const name = cmd.getArg(0);
4375 if (GuiToolbar * t = toolbar(name))
4380 case LFUN_TOOLBAR_MOVABLE: {
4381 string const name = cmd.getArg(0);
4383 // toggle (all) toolbars movablility
4384 toolbarsMovable_ = !toolbarsMovable_;
4385 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4386 GuiToolbar * tb = toolbar(ti.name);
4387 if (tb && tb->isMovable() != toolbarsMovable_)
4388 // toggle toolbar movablity if it does not fit lock
4389 // (all) toolbars positions state silent = true, since
4390 // status bar notifications are slow
4393 if (toolbarsMovable_)
4394 dr.setMessage(_("Toolbars unlocked."));
4396 dr.setMessage(_("Toolbars locked."));
4397 } else if (GuiToolbar * t = toolbar(name)) {
4398 // toggle current toolbar movablity
4400 // update lock (all) toolbars positions
4401 updateLockToolbars();
4406 case LFUN_ICON_SIZE: {
4407 QSize size = d.iconSize(cmd.argument());
4409 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4410 size.width(), size.height()));
4414 case LFUN_DIALOG_UPDATE: {
4415 string const name = to_utf8(cmd.argument());
4416 if (name == "prefs" || name == "document")
4417 updateDialog(name, string());
4418 else if (name == "paragraph")
4419 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4420 else if (currentBufferView()) {
4421 Inset * inset = currentBufferView()->editedInset(name);
4422 // Can only update a dialog connected to an existing inset
4424 // FIXME: get rid of this indirection; GuiView ask the inset
4425 // if he is kind enough to update itself...
4426 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4427 //FIXME: pass DispatchResult here?
4428 inset->dispatch(currentBufferView()->cursor(), fr);
4434 case LFUN_DIALOG_TOGGLE: {
4435 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4436 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4437 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4441 case LFUN_DIALOG_DISCONNECT_INSET:
4442 disconnectDialog(to_utf8(cmd.argument()));
4445 case LFUN_DIALOG_HIDE: {
4446 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4450 case LFUN_DIALOG_SHOW: {
4451 string const name = cmd.getArg(0);
4452 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4454 if (name == "latexlog") {
4455 // getStatus checks that
4456 LASSERT(doc_buffer, break);
4457 Buffer::LogType type;
4458 string const logfile = doc_buffer->logName(&type);
4460 case Buffer::latexlog:
4463 case Buffer::buildlog:
4464 sdata = "literate ";
4467 sdata += Lexer::quoteString(logfile);
4468 showDialog("log", sdata);
4469 } else if (name == "vclog") {
4470 // getStatus checks that
4471 LASSERT(doc_buffer, break);
4472 string const sdata2 = "vc " +
4473 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4474 showDialog("log", sdata2);
4475 } else if (name == "symbols") {
4476 sdata = bv->cursor().getEncoding()->name();
4478 showDialog("symbols", sdata);
4479 } else if (name == "findreplace") {
4480 sdata = to_utf8(bv->cursor().selectionAsString(false));
4481 showDialog(name, sdata);
4483 } else if (name == "prefs" && isFullScreen()) {
4484 lfunUiToggle("fullscreen");
4485 showDialog("prefs", sdata);
4487 showDialog(name, sdata);
4492 dr.setMessage(cmd.argument());
4495 case LFUN_UI_TOGGLE: {
4496 string arg = cmd.getArg(0);
4497 if (!lfunUiToggle(arg)) {
4498 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4499 dr.setMessage(bformat(msg, from_utf8(arg)));
4501 // Make sure the keyboard focus stays in the work area.
4506 case LFUN_VIEW_SPLIT: {
4507 LASSERT(doc_buffer, break);
4508 string const orientation = cmd.getArg(0);
4509 d.splitter_->setOrientation(orientation == "vertical"
4510 ? Qt::Vertical : Qt::Horizontal);
4511 TabWorkArea * twa = addTabWorkArea();
4512 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4513 setCurrentWorkArea(wa);
4516 case LFUN_TAB_GROUP_CLOSE:
4517 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4518 closeTabWorkArea(twa);
4519 d.current_work_area_ = nullptr;
4520 twa = d.currentTabWorkArea();
4521 // Switch to the next GuiWorkArea in the found TabWorkArea.
4523 // Make sure the work area is up to date.
4524 setCurrentWorkArea(twa->currentWorkArea());
4526 setCurrentWorkArea(nullptr);
4531 case LFUN_VIEW_CLOSE:
4532 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4533 closeWorkArea(twa->currentWorkArea());
4534 d.current_work_area_ = nullptr;
4535 twa = d.currentTabWorkArea();
4536 // Switch to the next GuiWorkArea in the found TabWorkArea.
4538 // Make sure the work area is up to date.
4539 setCurrentWorkArea(twa->currentWorkArea());
4541 setCurrentWorkArea(nullptr);
4546 case LFUN_COMPLETION_INLINE:
4547 if (d.current_work_area_)
4548 d.current_work_area_->completer().showInline();
4551 case LFUN_COMPLETION_POPUP:
4552 if (d.current_work_area_)
4553 d.current_work_area_->completer().showPopup();
4558 if (d.current_work_area_)
4559 d.current_work_area_->completer().tab();
4562 case LFUN_COMPLETION_CANCEL:
4563 if (d.current_work_area_) {
4564 if (d.current_work_area_->completer().popupVisible())
4565 d.current_work_area_->completer().hidePopup();
4567 d.current_work_area_->completer().hideInline();
4571 case LFUN_COMPLETION_ACCEPT:
4572 if (d.current_work_area_)
4573 d.current_work_area_->completer().activate();
4576 case LFUN_BUFFER_ZOOM_IN:
4577 case LFUN_BUFFER_ZOOM_OUT:
4578 case LFUN_BUFFER_ZOOM: {
4579 if (cmd.argument().empty()) {
4580 if (cmd.action() == LFUN_BUFFER_ZOOM)
4582 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4587 if (cmd.action() == LFUN_BUFFER_ZOOM)
4588 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4589 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4590 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4592 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4595 // Actual zoom value: default zoom + fractional extra value
4596 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4597 if (zoom < static_cast<int>(zoom_min_))
4600 setCurrentZoom(zoom);
4602 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4603 lyxrc.currentZoom, lyxrc.defaultZoom));
4605 guiApp->fontLoader().update();
4606 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4610 case LFUN_VC_REGISTER:
4611 case LFUN_VC_RENAME:
4613 case LFUN_VC_CHECK_IN:
4614 case LFUN_VC_CHECK_OUT:
4615 case LFUN_VC_REPO_UPDATE:
4616 case LFUN_VC_LOCKING_TOGGLE:
4617 case LFUN_VC_REVERT:
4618 case LFUN_VC_UNDO_LAST:
4619 case LFUN_VC_COMMAND:
4620 case LFUN_VC_COMPARE:
4621 dispatchVC(cmd, dr);
4624 case LFUN_SERVER_GOTO_FILE_ROW:
4625 if(goToFileRow(to_utf8(cmd.argument())))
4626 dr.screenUpdate(Update::Force | Update::FitCursor);
4629 case LFUN_LYX_ACTIVATE:
4633 case LFUN_WINDOW_RAISE:
4639 case LFUN_FORWARD_SEARCH: {
4640 // it seems safe to assume we have a document buffer, since
4641 // getStatus wants one.
4642 LASSERT(doc_buffer, break);
4643 Buffer const * doc_master = doc_buffer->masterBuffer();
4644 FileName const path(doc_master->temppath());
4645 string const texname = doc_master->isChild(doc_buffer)
4646 ? DocFileName(changeExtension(
4647 doc_buffer->absFileName(),
4648 "tex")).mangledFileName()
4649 : doc_buffer->latexName();
4650 string const fulltexname =
4651 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4652 string const mastername =
4653 removeExtension(doc_master->latexName());
4654 FileName const dviname(addName(path.absFileName(),
4655 addExtension(mastername, "dvi")));
4656 FileName const pdfname(addName(path.absFileName(),
4657 addExtension(mastername, "pdf")));
4658 bool const have_dvi = dviname.exists();
4659 bool const have_pdf = pdfname.exists();
4660 if (!have_dvi && !have_pdf) {
4661 dr.setMessage(_("Please, preview the document first."));
4664 string outname = dviname.onlyFileName();
4665 string command = lyxrc.forward_search_dvi;
4666 if (!have_dvi || (have_pdf &&
4667 pdfname.lastModified() > dviname.lastModified())) {
4668 outname = pdfname.onlyFileName();
4669 command = lyxrc.forward_search_pdf;
4672 DocIterator cur = bv->cursor();
4673 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4674 LYXERR(Debug::ACTION, "Forward search: row:" << row
4676 if (row == -1 || command.empty()) {
4677 dr.setMessage(_("Couldn't proceed."));
4680 string texrow = convert<string>(row);
4682 command = subst(command, "$$n", texrow);
4683 command = subst(command, "$$f", fulltexname);
4684 command = subst(command, "$$t", texname);
4685 command = subst(command, "$$o", outname);
4687 volatile PathChanger p(path);
4689 one.startscript(Systemcall::DontWait, command);
4693 case LFUN_SPELLING_CONTINUOUSLY:
4694 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4695 dr.screenUpdate(Update::Force);
4698 case LFUN_CITATION_OPEN: {
4700 if (theFormats().getFormat("pdf"))
4701 pdfv = theFormats().getFormat("pdf")->viewer();
4702 if (theFormats().getFormat("ps"))
4703 psv = theFormats().getFormat("ps")->viewer();
4704 frontend::showTarget(argument, pdfv, psv);
4709 // The LFUN must be for one of BufferView, Buffer or Cursor;
4711 dispatchToBufferView(cmd, dr);
4715 // Need to update bv because many LFUNs here might have destroyed it
4716 bv = currentBufferView();
4718 // Clear non-empty selections
4719 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4721 Cursor & cur = bv->cursor();
4722 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4723 cur.clearSelection();
4729 bool GuiView::lfunUiToggle(string const & ui_component)
4731 if (ui_component == "scrollbar") {
4732 // hide() is of no help
4733 if (d.current_work_area_->verticalScrollBarPolicy() ==
4734 Qt::ScrollBarAlwaysOff)
4736 d.current_work_area_->setVerticalScrollBarPolicy(
4737 Qt::ScrollBarAsNeeded);
4739 d.current_work_area_->setVerticalScrollBarPolicy(
4740 Qt::ScrollBarAlwaysOff);
4741 } else if (ui_component == "statusbar") {
4742 statusBar()->setVisible(!statusBar()->isVisible());
4743 } else if (ui_component == "menubar") {
4744 menuBar()->setVisible(!menuBar()->isVisible());
4746 if (ui_component == "frame") {
4747 int const l = contentsMargins().left();
4749 //are the frames in default state?
4750 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4752 #if QT_VERSION > 0x050903
4753 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4755 setContentsMargins(-2, -2, -2, -2);
4757 #if QT_VERSION > 0x050903
4758 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4760 setContentsMargins(0, 0, 0, 0);
4763 if (ui_component == "fullscreen") {
4771 void GuiView::toggleFullScreen()
4773 setWindowState(windowState() ^ Qt::WindowFullScreen);
4777 Buffer const * GuiView::updateInset(Inset const * inset)
4782 Buffer const * inset_buffer = &(inset->buffer());
4784 for (int i = 0; i != d.splitter_->count(); ++i) {
4785 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4788 Buffer const * buffer = &(wa->bufferView().buffer());
4789 if (inset_buffer == buffer)
4790 wa->scheduleRedraw(true);
4792 return inset_buffer;
4796 void GuiView::restartCaret()
4798 /* When we move around, or type, it's nice to be able to see
4799 * the caret immediately after the keypress.
4801 if (d.current_work_area_)
4802 d.current_work_area_->startBlinkingCaret();
4804 // Take this occasion to update the other GUI elements.
4810 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4812 if (d.current_work_area_)
4813 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4818 // This list should be kept in sync with the list of insets in
4819 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4820 // dialog should have the same name as the inset.
4821 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4822 // docs in LyXAction.cpp.
4824 char const * const dialognames[] = {
4826 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4827 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4828 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4829 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4830 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4831 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4832 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4833 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4835 char const * const * const end_dialognames =
4836 dialognames + (sizeof(dialognames) / sizeof(char *));
4840 cmpCStr(char const * name) : name_(name) {}
4841 bool operator()(char const * other) {
4842 return strcmp(other, name_) == 0;
4849 bool isValidName(string const & name)
4851 return find_if(dialognames, end_dialognames,
4852 cmpCStr(name.c_str())) != end_dialognames;
4858 void GuiView::resetDialogs()
4860 // Make sure that no LFUN uses any GuiView.
4861 guiApp->setCurrentView(nullptr);
4865 constructToolbars();
4866 guiApp->menus().fillMenuBar(menuBar(), this, false);
4867 d.layout_->updateContents(true);
4868 // Now update controls with current buffer.
4869 guiApp->setCurrentView(this);
4875 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4877 for (QObject * child: widget->children()) {
4878 if (child->inherits("QGroupBox")) {
4879 QGroupBox * box = (QGroupBox*) child;
4882 flatGroupBoxes(child, flag);
4888 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4890 if (!isValidName(name))
4893 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4895 if (it != d.dialogs_.end()) {
4897 it->second->hideView();
4898 return it->second.get();
4901 Dialog * dialog = build(name);
4902 d.dialogs_[name].reset(dialog);
4903 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4904 // Force a uniform style for group boxes
4905 // On Mac non-flat works better, on Linux flat is standard
4906 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4908 if (lyxrc.allow_geometry_session)
4909 dialog->restoreSession();
4916 void GuiView::showDialog(string const & name, string const & sdata,
4919 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4923 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4929 const string name = fromqstr(qname);
4930 const string sdata = fromqstr(qdata);
4934 Dialog * dialog = findOrBuild(name, false);
4936 bool const visible = dialog->isVisibleView();
4937 dialog->showData(sdata);
4938 if (currentBufferView())
4939 currentBufferView()->editInset(name, inset);
4940 // We only set the focus to the new dialog if it was not yet
4941 // visible in order not to change the existing previous behaviour
4943 // activateWindow is needed for floating dockviews
4944 dialog->asQWidget()->raise();
4945 dialog->asQWidget()->activateWindow();
4946 if (dialog->wantInitialFocus())
4947 dialog->asQWidget()->setFocus();
4951 catch (ExceptionMessage const &) {
4959 bool GuiView::isDialogVisible(string const & name) const
4961 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4962 if (it == d.dialogs_.end())
4964 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4968 void GuiView::hideDialog(string const & name, Inset * inset)
4970 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4971 if (it == d.dialogs_.end())
4975 if (!currentBufferView())
4977 if (inset != currentBufferView()->editedInset(name))
4981 Dialog * const dialog = it->second.get();
4982 if (dialog->isVisibleView())
4984 if (currentBufferView())
4985 currentBufferView()->editInset(name, nullptr);
4989 void GuiView::disconnectDialog(string const & name)
4991 if (!isValidName(name))
4993 if (currentBufferView())
4994 currentBufferView()->editInset(name, nullptr);
4998 void GuiView::hideAll() const
5000 for(auto const & dlg_p : d.dialogs_)
5001 dlg_p.second->hideView();
5005 void GuiView::updateDialogs()
5007 for(auto const & dlg_p : d.dialogs_) {
5008 Dialog * dialog = dlg_p.second.get();
5010 if (dialog->needBufferOpen() && !documentBufferView())
5011 hideDialog(fromqstr(dialog->name()), nullptr);
5012 else if (dialog->isVisibleView())
5013 dialog->checkStatus();
5021 Dialog * GuiView::build(string const & name)
5023 return createDialog(*this, name);
5027 SEMenu::SEMenu(QWidget * parent)
5029 QAction * action = addAction(qt_("Disable Shell Escape"));
5030 connect(action, SIGNAL(triggered()),
5031 parent, SLOT(disableShellEscape()));
5034 } // namespace frontend
5037 #include "moc_GuiView.cpp"