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>
103 #include <QShowEvent>
106 #include <QStackedWidget>
107 #include <QStatusBar>
108 #include <QSvgRenderer>
109 #include <QtConcurrentRun>
112 #include <QWindowStateChangeEvent>
115 // sync with GuiAlert.cpp
116 #define EXPORT_in_THREAD 1
119 #include "support/bind.h"
123 #ifdef HAVE_SYS_TIME_H
124 # include <sys/time.h>
132 using namespace lyx::support;
136 using support::addExtension;
137 using support::changeExtension;
138 using support::removeExtension;
144 class BackgroundWidget : public QWidget
147 BackgroundWidget(int width, int height)
148 : width_(width), height_(height)
150 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
151 if (!lyxrc.show_banner)
153 /// The text to be written on top of the pixmap
154 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
155 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
156 /// The text to be written on top of the pixmap
157 QString const text = lyx_version ?
158 qt_("version ") + lyx_version : qt_("unknown version");
159 #if QT_VERSION >= 0x050000
160 QString imagedir = "images/";
161 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
162 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
163 if (svgRenderer.isValid()) {
164 splash_ = QPixmap(splashSize());
165 QPainter painter(&splash_);
166 svgRenderer.render(&painter);
167 splash_.setDevicePixelRatio(pixelRatio());
169 splash_ = getPixmap("images/", "banner", "png");
172 splash_ = getPixmap("images/", "banner", "svgz,png");
175 QPainter pain(&splash_);
176 pain.setPen(QColor(0, 0, 0));
177 qreal const fsize = fontSize();
180 qreal locscale = htextsize.toFloat(&ok);
183 QPointF const position = textPosition(false);
184 QPointF const hposition = textPosition(true);
185 QRectF const hrect(hposition, splashSize());
187 "widget pixel ratio: " << pixelRatio() <<
188 " splash pixel ratio: " << splashPixelRatio() <<
189 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
191 // The font used to display the version info
192 font.setStyleHint(QFont::SansSerif);
193 font.setWeight(QFont::Bold);
194 font.setPointSizeF(fsize);
196 pain.drawText(position, text);
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Normal);
200 font.setPointSizeF(hfsize);
201 // Check how long the logo gets with the current font
202 // and adapt if the font is running wider than what
204 GuiFontMetrics fm(font);
205 // Split the title into lines to measure the longest line
206 // in the current l7n.
207 QStringList titlesegs = htext.split('\n');
209 int hline = fm.maxHeight();
210 QStringList::const_iterator sit;
211 for (QString const & seg : titlesegs) {
212 if (fm.width(seg) > wline)
213 wline = fm.width(seg);
215 // The longest line in the reference font (for English)
216 // is 180. Calculate scale factor from that.
217 double const wscale = wline > 0 ? (180.0 / wline) : 1;
218 // Now do the same for the height (necessary for condensed fonts)
219 double const hscale = (34.0 / hline);
220 // take the lower of the two scale factors.
221 double const scale = min(wscale, hscale);
222 // Now rescale. Also consider l7n's offset factor.
223 font.setPointSizeF(hfsize * scale * locscale);
226 pain.drawText(hrect, Qt::AlignLeft, htext);
227 setFocusPolicy(Qt::StrongFocus);
230 void paintEvent(QPaintEvent *) override
232 int const w = width_;
233 int const h = height_;
234 int const x = (width() - w) / 2;
235 int const y = (height() - h) / 2;
237 "widget pixel ratio: " << pixelRatio() <<
238 " splash pixel ratio: " << splashPixelRatio() <<
239 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
241 pain.drawPixmap(x, y, w, h, splash_);
244 void keyPressEvent(QKeyEvent * ev) override
247 setKeySymbol(&sym, ev);
249 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
261 /// Current ratio between physical pixels and device-independent pixels
262 double pixelRatio() const {
263 #if QT_VERSION >= 0x050000
264 return qt_scale_factor * devicePixelRatio();
270 qreal fontSize() const {
271 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
274 QPointF textPosition(bool const heading) const {
275 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
276 : QPointF(width_/2 - 18, height_/2 + 45);
279 QSize splashSize() const {
281 static_cast<unsigned int>(width_ * pixelRatio()),
282 static_cast<unsigned int>(height_ * pixelRatio()));
285 /// Ratio between physical pixels and device-independent pixels of splash image
286 double splashPixelRatio() const {
287 #if QT_VERSION >= 0x050000
288 return splash_.devicePixelRatio();
296 /// Toolbar store providing access to individual toolbars by name.
297 typedef map<string, GuiToolbar *> ToolbarMap;
299 typedef shared_ptr<Dialog> DialogPtr;
304 class GuiView::GuiViewPrivate
307 GuiViewPrivate(GuiViewPrivate const &);
308 void operator=(GuiViewPrivate const &);
310 GuiViewPrivate(GuiView * gv)
311 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
312 layout_(nullptr), autosave_timeout_(5000),
315 // hardcode here the platform specific icon size
316 smallIconSize = 16; // scaling problems
317 normalIconSize = 20; // ok, default if iconsize.png is missing
318 bigIconSize = 26; // better for some math icons
319 hugeIconSize = 32; // better for hires displays
322 // if it exists, use width of iconsize.png as normal size
323 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
324 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
326 QImage image(toqstr(fn.absFileName()));
327 if (image.width() < int(smallIconSize))
328 normalIconSize = smallIconSize;
329 else if (image.width() > int(giantIconSize))
330 normalIconSize = giantIconSize;
332 normalIconSize = image.width();
335 splitter_ = new QSplitter;
336 bg_widget_ = new BackgroundWidget(400, 250);
337 stack_widget_ = new QStackedWidget;
338 stack_widget_->addWidget(bg_widget_);
339 stack_widget_->addWidget(splitter_);
342 // TODO cleanup, remove the singleton, handle multiple Windows?
343 progress_ = ProgressInterface::instance();
344 if (!dynamic_cast<GuiProgress*>(progress_)) {
345 progress_ = new GuiProgress; // TODO who deletes it
346 ProgressInterface::setInstance(progress_);
349 dynamic_cast<GuiProgress*>(progress_),
350 SIGNAL(updateStatusBarMessage(QString const&)),
351 gv, SLOT(updateStatusBarMessage(QString const&)));
353 dynamic_cast<GuiProgress*>(progress_),
354 SIGNAL(clearMessageText()),
355 gv, SLOT(clearMessageText()));
362 delete stack_widget_;
367 stack_widget_->setCurrentWidget(bg_widget_);
368 bg_widget_->setUpdatesEnabled(true);
369 bg_widget_->setFocus();
372 int tabWorkAreaCount()
374 return splitter_->count();
377 TabWorkArea * tabWorkArea(int i)
379 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
382 TabWorkArea * currentTabWorkArea()
384 int areas = tabWorkAreaCount();
386 // The first TabWorkArea is always the first one, if any.
387 return tabWorkArea(0);
389 for (int i = 0; i != areas; ++i) {
390 TabWorkArea * twa = tabWorkArea(i);
391 if (current_main_work_area_ == twa->currentWorkArea())
395 // None has the focus so we just take the first one.
396 return tabWorkArea(0);
399 int countWorkAreasOf(Buffer & buf)
401 int areas = tabWorkAreaCount();
403 for (int i = 0; i != areas; ++i) {
404 TabWorkArea * twa = tabWorkArea(i);
405 if (twa->workArea(buf))
411 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
413 if (processing_thread_watcher_.isRunning()) {
414 // we prefer to cancel this preview in order to keep a snappy
418 processing_thread_watcher_.setFuture(f);
421 QSize iconSize(docstring const & icon_size)
424 if (icon_size == "small")
425 size = smallIconSize;
426 else if (icon_size == "normal")
427 size = normalIconSize;
428 else if (icon_size == "big")
430 else if (icon_size == "huge")
432 else if (icon_size == "giant")
433 size = giantIconSize;
435 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
437 if (size < smallIconSize)
438 size = smallIconSize;
440 return QSize(size, size);
443 QSize iconSize(QString const & icon_size)
445 return iconSize(qstring_to_ucs4(icon_size));
448 string & iconSize(QSize const & qsize)
450 LATTEST(qsize.width() == qsize.height());
452 static string icon_size;
454 unsigned int size = qsize.width();
456 if (size < smallIconSize)
457 size = smallIconSize;
459 if (size == smallIconSize)
461 else if (size == normalIconSize)
462 icon_size = "normal";
463 else if (size == bigIconSize)
465 else if (size == hugeIconSize)
467 else if (size == giantIconSize)
470 icon_size = convert<string>(size);
475 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
476 Buffer * buffer, string const & format);
477 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
484 static Buffer::ExportStatus runAndDestroy(const T& func,
485 Buffer const * orig, Buffer * buffer, string const & format);
487 // TODO syncFunc/previewFunc: use bind
488 bool asyncBufferProcessing(string const & argument,
489 Buffer const * used_buffer,
490 docstring const & msg,
491 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
492 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
493 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
494 bool allow_async, bool use_tmpdir = false);
496 QVector<GuiWorkArea*> guiWorkAreas();
500 GuiWorkArea * current_work_area_;
501 GuiWorkArea * current_main_work_area_;
502 QSplitter * splitter_;
503 QStackedWidget * stack_widget_;
504 BackgroundWidget * bg_widget_;
506 ToolbarMap toolbars_;
507 ProgressInterface* progress_;
508 /// The main layout box.
510 * \warning Don't Delete! The layout box is actually owned by
511 * whichever toolbar contains it. All the GuiView class needs is a
512 * means of accessing it.
514 * FIXME: replace that with a proper model so that we are not limited
515 * to only one dialog.
520 map<string, DialogPtr> dialogs_;
523 QTimer statusbar_timer_;
524 /// auto-saving of buffers
525 Timeout autosave_timeout_;
528 TocModels toc_models_;
531 QFutureWatcher<docstring> autosave_watcher_;
532 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
534 string last_export_format;
535 string processing_format;
537 static QSet<Buffer const *> busyBuffers;
539 unsigned int smallIconSize;
540 unsigned int normalIconSize;
541 unsigned int bigIconSize;
542 unsigned int hugeIconSize;
543 unsigned int giantIconSize;
545 /// flag against a race condition due to multiclicks, see bug #1119
549 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
552 GuiView::GuiView(int id)
553 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
554 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
557 connect(this, SIGNAL(bufferViewChanged()),
558 this, SLOT(onBufferViewChanged()));
560 // GuiToolbars *must* be initialised before the menu bar.
561 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
564 // set ourself as the current view. This is needed for the menu bar
565 // filling, at least for the static special menu item on Mac. Otherwise
566 // they are greyed out.
567 guiApp->setCurrentView(this);
569 // Fill up the menu bar.
570 guiApp->menus().fillMenuBar(menuBar(), this, true);
572 setCentralWidget(d.stack_widget_);
574 // Start autosave timer
575 if (lyxrc.autosave) {
576 // The connection is closed when this is destroyed.
577 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
578 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
579 d.autosave_timeout_.start();
581 connect(&d.statusbar_timer_, SIGNAL(timeout()),
582 this, SLOT(clearMessage()));
584 // We don't want to keep the window in memory if it is closed.
585 setAttribute(Qt::WA_DeleteOnClose, true);
587 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
588 // QIcon::fromTheme was introduced in Qt 4.6
589 #if (QT_VERSION >= 0x040600)
590 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
591 // since the icon is provided in the application bundle. We use a themed
592 // version when available and use the bundled one as fallback.
593 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
595 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
601 // use tabbed dock area for multiple docks
602 // (such as "source" and "messages")
603 setDockOptions(QMainWindow::ForceTabbedDocks);
606 // use document mode tabs on docks
607 setDocumentMode(true);
611 setAcceptDrops(true);
613 // add busy indicator to statusbar
614 search_mode mode = theGuiApp()->imageSearchMode();
615 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
616 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
617 statusBar()->addPermanentWidget(busySVG);
618 // make busy indicator square with 5px margins
619 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
622 connect(&d.processing_thread_watcher_, SIGNAL(started()),
623 busySVG, SLOT(show()));
624 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
625 busySVG, SLOT(hide()));
626 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
628 QFontMetrics const fm(statusBar()->fontMetrics());
630 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
631 // Small size slider for macOS to prevent the status bar from enlarging
632 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
633 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
634 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
636 zoom_slider_->setFixedWidth(fm.width('x') * 15);
638 // Make the defaultZoom center
639 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
640 // Initialize proper zoom value
642 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
643 // Actual zoom value: default zoom + fractional offset
644 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
645 if (zoom < static_cast<int>(zoom_min_))
647 zoom_slider_->setValue(zoom);
648 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
650 // Buttons to change zoom stepwise
651 zoom_in_ = new QPushButton(statusBar());
652 zoom_in_->setText("+");
653 zoom_in_->setFlat(true);
654 zoom_in_->setFixedSize(QSize(fm.height(), fm.height()));
655 zoom_out_ = new QPushButton(statusBar());
656 zoom_out_->setText(QString(QChar(0x2212)));
657 zoom_out_->setFixedSize(QSize(fm.height(), fm.height()));
658 zoom_out_->setFlat(true);
660 statusBar()->addPermanentWidget(zoom_out_);
661 zoom_out_->setEnabled(currentBufferView());
662 statusBar()->addPermanentWidget(zoom_slider_);
663 zoom_slider_->setEnabled(currentBufferView());
664 zoom_in_->setEnabled(currentBufferView());
665 statusBar()->addPermanentWidget(zoom_in_);
667 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
668 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
669 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
670 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
671 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
673 zoom_value_ = new QLabel(statusBar());
674 zoom_value_->setFixedHeight(fm.height());
675 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
676 zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
678 zoom_value_->setMinimumWidth(fm.width("000%"));
680 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
681 statusBar()->addPermanentWidget(zoom_value_);
682 zoom_value_->setEnabled(currentBufferView());
683 zoom_value_->setContextMenuPolicy(Qt::CustomContextMenu);
685 connect(zoom_value_, SIGNAL(customContextMenuRequested(QPoint)),
686 this, SLOT(showZoomContextMenu()));
688 int const iconheight = max(int(d.normalIconSize), fm.height());
689 QSize const iconsize(iconheight, iconheight);
691 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
692 shell_escape_ = new QLabel(statusBar());
693 shell_escape_->setPixmap(shellescape);
694 shell_escape_->setAlignment(Qt::AlignCenter);
695 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
696 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
697 "external commands for this document. "
698 "Right click to change."));
699 SEMenu * menu = new SEMenu(this);
700 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
701 menu, SLOT(showMenu(QPoint)));
702 shell_escape_->hide();
703 statusBar()->addPermanentWidget(shell_escape_);
705 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
706 read_only_ = new QLabel(statusBar());
707 read_only_->setPixmap(readonly);
708 read_only_->setAlignment(Qt::AlignCenter);
710 statusBar()->addPermanentWidget(read_only_);
712 version_control_ = new QLabel(statusBar());
713 version_control_->setAlignment(Qt::AlignCenter);
714 version_control_->setFrameStyle(QFrame::StyledPanel);
715 version_control_->hide();
716 statusBar()->addPermanentWidget(version_control_);
718 statusBar()->setSizeGripEnabled(true);
721 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
722 SLOT(autoSaveThreadFinished()));
724 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
725 SLOT(processingThreadStarted()));
726 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
727 SLOT(processingThreadFinished()));
729 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
730 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
732 // set custom application bars context menu, e.g. tool bar and menu bar
733 setContextMenuPolicy(Qt::CustomContextMenu);
734 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
735 SLOT(toolBarPopup(const QPoint &)));
737 // Forbid too small unresizable window because it can happen
738 // with some window manager under X11.
739 setMinimumSize(300, 200);
741 if (lyxrc.allow_geometry_session) {
742 // Now take care of session management.
747 // no session handling, default to a sane size.
748 setGeometry(50, 50, 690, 510);
751 // clear session data if any.
752 settings.remove("views");
762 void GuiView::disableShellEscape()
764 BufferView * bv = documentBufferView();
767 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
768 bv->buffer().params().shell_escape = false;
769 bv->processUpdateFlags(Update::Force);
773 void GuiView::checkCancelBackground()
775 docstring const ttl = _("Cancel Export?");
776 docstring const msg = _("Do you want to cancel the background export process?");
778 Alert::prompt(ttl, msg, 1, 1,
779 _("&Cancel export"), _("Co&ntinue"));
781 Systemcall::killscript();
785 void GuiView::zoomSliderMoved(int value)
788 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
789 scheduleRedrawWorkAreas();
790 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
794 void GuiView::zoomValueChanged(int value)
796 if (value != lyxrc.currentZoom)
797 zoomSliderMoved(value);
801 void GuiView::zoomInPressed()
804 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
805 scheduleRedrawWorkAreas();
809 void GuiView::zoomOutPressed()
812 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
813 scheduleRedrawWorkAreas();
817 void GuiView::showZoomContextMenu()
819 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
822 menu->exec(QCursor::pos());
826 void GuiView::scheduleRedrawWorkAreas()
828 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
829 TabWorkArea* ta = d.tabWorkArea(i);
830 for (int u = 0; u < ta->count(); u++) {
831 ta->workArea(u)->scheduleRedraw(true);
837 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
839 QVector<GuiWorkArea*> areas;
840 for (int i = 0; i < tabWorkAreaCount(); i++) {
841 TabWorkArea* ta = tabWorkArea(i);
842 for (int u = 0; u < ta->count(); u++) {
843 areas << ta->workArea(u);
849 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
850 string const & format)
852 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
855 case Buffer::ExportSuccess:
856 msg = bformat(_("Successful export to format: %1$s"), fmt);
858 case Buffer::ExportCancel:
859 msg = _("Document export cancelled.");
861 case Buffer::ExportError:
862 case Buffer::ExportNoPathToFormat:
863 case Buffer::ExportTexPathHasSpaces:
864 case Buffer::ExportConverterError:
865 msg = bformat(_("Error while exporting format: %1$s"), fmt);
867 case Buffer::PreviewSuccess:
868 msg = bformat(_("Successful preview of format: %1$s"), fmt);
870 case Buffer::PreviewError:
871 msg = bformat(_("Error while previewing format: %1$s"), fmt);
873 case Buffer::ExportKilled:
874 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
881 void GuiView::processingThreadStarted()
886 void GuiView::processingThreadFinished()
888 QFutureWatcher<Buffer::ExportStatus> const * watcher =
889 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
891 Buffer::ExportStatus const status = watcher->result();
892 handleExportStatus(this, status, d.processing_format);
895 BufferView const * const bv = currentBufferView();
896 if (bv && !bv->buffer().errorList("Export").empty()) {
901 bool const error = (status != Buffer::ExportSuccess &&
902 status != Buffer::PreviewSuccess &&
903 status != Buffer::ExportCancel);
905 ErrorList & el = bv->buffer().errorList(d.last_export_format);
906 // at this point, we do not know if buffer-view or
907 // master-buffer-view was called. If there was an export error,
908 // and the current buffer's error log is empty, we guess that
909 // it must be master-buffer-view that was called so we set
911 errors(d.last_export_format, el.empty());
916 void GuiView::autoSaveThreadFinished()
918 QFutureWatcher<docstring> const * watcher =
919 static_cast<QFutureWatcher<docstring> const *>(sender());
920 message(watcher->result());
925 void GuiView::saveLayout() const
928 settings.setValue("zoom_ratio", zoom_ratio_);
929 settings.setValue("devel_mode", devel_mode_);
930 settings.beginGroup("views");
931 settings.beginGroup(QString::number(id_));
932 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
933 settings.setValue("pos", pos());
934 settings.setValue("size", size());
936 settings.setValue("geometry", saveGeometry());
937 settings.setValue("layout", saveState(0));
938 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
939 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
943 void GuiView::saveUISettings() const
947 // Save the toolbar private states
948 for (auto const & tb_p : d.toolbars_)
949 tb_p.second->saveSession(settings);
950 // Now take care of all other dialogs
951 for (auto const & dlg_p : d.dialogs_)
952 dlg_p.second->saveSession(settings);
956 void GuiView::setCurrentZoom(const int v)
958 lyxrc.currentZoom = v;
959 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
960 Q_EMIT currentZoomChanged(v);
964 bool GuiView::restoreLayout()
967 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
968 // Actual zoom value: default zoom + fractional offset
969 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
970 if (zoom < static_cast<int>(zoom_min_))
972 setCurrentZoom(zoom);
973 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
974 settings.beginGroup("views");
975 settings.beginGroup(QString::number(id_));
976 QString const icon_key = "icon_size";
977 if (!settings.contains(icon_key))
980 //code below is skipped when when ~/.config/LyX is (re)created
981 setIconSize(d.iconSize(settings.value(icon_key).toString()));
983 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
984 zoom_slider_->setVisible(show_zoom_slider);
985 zoom_in_->setVisible(show_zoom_slider);
986 zoom_out_->setVisible(show_zoom_slider);
988 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
989 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
990 QSize size = settings.value("size", QSize(690, 510)).toSize();
994 // Work-around for bug #6034: the window ends up in an undetermined
995 // state when trying to restore a maximized window when it is
996 // already maximized.
997 if (!(windowState() & Qt::WindowMaximized))
998 if (!restoreGeometry(settings.value("geometry").toByteArray()))
999 setGeometry(50, 50, 690, 510);
1002 // Make sure layout is correctly oriented.
1003 setLayoutDirection(qApp->layoutDirection());
1005 // Allow the toc and view-source dock widget to be restored if needed.
1007 if ((dialog = findOrBuild("toc", true)))
1008 // see bug 5082. At least setup title and enabled state.
1009 // Visibility will be adjusted by restoreState below.
1010 dialog->prepareView();
1011 if ((dialog = findOrBuild("view-source", true)))
1012 dialog->prepareView();
1013 if ((dialog = findOrBuild("progress", true)))
1014 dialog->prepareView();
1016 if (!restoreState(settings.value("layout").toByteArray(), 0))
1019 // init the toolbars that have not been restored
1020 for (auto const & tb_p : guiApp->toolbars()) {
1021 GuiToolbar * tb = toolbar(tb_p.name);
1022 if (tb && !tb->isRestored())
1023 initToolbar(tb_p.name);
1026 // update lock (all) toolbars positions
1027 updateLockToolbars();
1034 GuiToolbar * GuiView::toolbar(string const & name)
1036 ToolbarMap::iterator it = d.toolbars_.find(name);
1037 if (it != d.toolbars_.end())
1040 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1045 void GuiView::updateLockToolbars()
1047 toolbarsMovable_ = false;
1048 for (ToolbarInfo const & info : guiApp->toolbars()) {
1049 GuiToolbar * tb = toolbar(info.name);
1050 if (tb && tb->isMovable())
1051 toolbarsMovable_ = true;
1053 #if QT_VERSION >= 0x050200
1054 // set unified mac toolbars only when not movable as recommended:
1055 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1056 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1061 void GuiView::constructToolbars()
1063 for (auto const & tb_p : d.toolbars_)
1065 d.toolbars_.clear();
1067 // I don't like doing this here, but the standard toolbar
1068 // destroys this object when it's destroyed itself (vfr)
1069 d.layout_ = new LayoutBox(*this);
1070 d.stack_widget_->addWidget(d.layout_);
1071 d.layout_->move(0,0);
1073 // extracts the toolbars from the backend
1074 for (ToolbarInfo const & inf : guiApp->toolbars())
1075 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1077 DynamicMenuButton::resetIconCache();
1081 void GuiView::initToolbars()
1083 // extracts the toolbars from the backend
1084 for (ToolbarInfo const & inf : guiApp->toolbars())
1085 initToolbar(inf.name);
1089 void GuiView::initToolbar(string const & name)
1091 GuiToolbar * tb = toolbar(name);
1094 int const visibility = guiApp->toolbars().defaultVisibility(name);
1095 bool newline = !(visibility & Toolbars::SAMEROW);
1096 tb->setVisible(false);
1097 tb->setVisibility(visibility);
1099 if (visibility & Toolbars::TOP) {
1101 addToolBarBreak(Qt::TopToolBarArea);
1102 addToolBar(Qt::TopToolBarArea, tb);
1105 if (visibility & Toolbars::BOTTOM) {
1107 addToolBarBreak(Qt::BottomToolBarArea);
1108 addToolBar(Qt::BottomToolBarArea, tb);
1111 if (visibility & Toolbars::LEFT) {
1113 addToolBarBreak(Qt::LeftToolBarArea);
1114 addToolBar(Qt::LeftToolBarArea, tb);
1117 if (visibility & Toolbars::RIGHT) {
1119 addToolBarBreak(Qt::RightToolBarArea);
1120 addToolBar(Qt::RightToolBarArea, tb);
1123 if (visibility & Toolbars::ON)
1124 tb->setVisible(true);
1126 tb->setMovable(true);
1130 TocModels & GuiView::tocModels()
1132 return d.toc_models_;
1136 void GuiView::setFocus()
1138 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1139 QMainWindow::setFocus();
1143 bool GuiView::hasFocus() const
1145 if (currentWorkArea())
1146 return currentWorkArea()->hasFocus();
1147 if (currentMainWorkArea())
1148 return currentMainWorkArea()->hasFocus();
1149 return d.bg_widget_->hasFocus();
1153 void GuiView::focusInEvent(QFocusEvent * e)
1155 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1156 QMainWindow::focusInEvent(e);
1157 // Make sure guiApp points to the correct view.
1158 guiApp->setCurrentView(this);
1159 if (currentWorkArea())
1160 currentWorkArea()->setFocus();
1161 else if (currentMainWorkArea())
1162 currentMainWorkArea()->setFocus();
1164 d.bg_widget_->setFocus();
1168 void GuiView::showEvent(QShowEvent * e)
1170 LYXERR(Debug::GUI, "Passed Geometry "
1171 << size().height() << "x" << size().width()
1172 << "+" << pos().x() << "+" << pos().y());
1174 if (d.splitter_->count() == 0)
1175 // No work area, switch to the background widget.
1179 QMainWindow::showEvent(e);
1183 bool GuiView::closeScheduled()
1190 bool GuiView::prepareAllBuffersForLogout()
1192 Buffer * first = theBufferList().first();
1196 // First, iterate over all buffers and ask the users if unsaved
1197 // changes should be saved.
1198 // We cannot use a for loop as the buffer list cycles.
1201 if (!saveBufferIfNeeded(*b, false))
1203 b = theBufferList().next(b);
1204 } while (b != first);
1206 // Next, save session state
1207 // When a view/window was closed before without quitting LyX, there
1208 // are already entries in the lastOpened list.
1209 theSession().lastOpened().clear();
1216 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1217 ** is responsibility of the container (e.g., dialog)
1219 void GuiView::closeEvent(QCloseEvent * close_event)
1221 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1223 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1224 Alert::warning(_("Exit LyX"),
1225 _("LyX could not be closed because documents are being processed by LyX."));
1226 close_event->setAccepted(false);
1230 // If the user pressed the x (so we didn't call closeView
1231 // programmatically), we want to clear all existing entries.
1233 theSession().lastOpened().clear();
1238 // it can happen that this event arrives without selecting the view,
1239 // e.g. when clicking the close button on a background window.
1241 if (!closeWorkAreaAll()) {
1243 close_event->ignore();
1247 // Make sure that nothing will use this to be closed View.
1248 guiApp->unregisterView(this);
1250 if (isFullScreen()) {
1251 // Switch off fullscreen before closing.
1256 // Make sure the timer time out will not trigger a statusbar update.
1257 d.statusbar_timer_.stop();
1259 // Saving fullscreen requires additional tweaks in the toolbar code.
1260 // It wouldn't also work under linux natively.
1261 if (lyxrc.allow_geometry_session) {
1266 close_event->accept();
1270 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1272 if (event->mimeData()->hasUrls())
1274 /// \todo Ask lyx-devel is this is enough:
1275 /// if (event->mimeData()->hasFormat("text/plain"))
1276 /// event->acceptProposedAction();
1280 void GuiView::dropEvent(QDropEvent * event)
1282 QList<QUrl> files = event->mimeData()->urls();
1283 if (files.isEmpty())
1286 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1287 for (int i = 0; i != files.size(); ++i) {
1288 string const file = os::internal_path(fromqstr(
1289 files.at(i).toLocalFile()));
1293 string const ext = support::getExtension(file);
1294 vector<const Format *> found_formats;
1296 // Find all formats that have the correct extension.
1297 for (const Format * fmt : theConverters().importableFormats())
1298 if (fmt->hasExtension(ext))
1299 found_formats.push_back(fmt);
1302 if (!found_formats.empty()) {
1303 if (found_formats.size() > 1) {
1304 //FIXME: show a dialog to choose the correct importable format
1305 LYXERR(Debug::FILES,
1306 "Multiple importable formats found, selecting first");
1308 string const arg = found_formats[0]->name() + " " + file;
1309 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1312 //FIXME: do we have to explicitly check whether it's a lyx file?
1313 LYXERR(Debug::FILES,
1314 "No formats found, trying to open it as a lyx file");
1315 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1317 // add the functions to the queue
1318 guiApp->addToFuncRequestQueue(cmd);
1321 // now process the collected functions. We perform the events
1322 // asynchronously. This prevents potential problems in case the
1323 // BufferView is closed within an event.
1324 guiApp->processFuncRequestQueueAsync();
1328 void GuiView::message(docstring const & str)
1330 if (ForkedProcess::iAmAChild())
1333 // call is moved to GUI-thread by GuiProgress
1334 d.progress_->appendMessage(toqstr(str));
1338 void GuiView::clearMessageText()
1340 message(docstring());
1344 void GuiView::updateStatusBarMessage(QString const & str)
1346 statusBar()->showMessage(str);
1347 d.statusbar_timer_.stop();
1348 d.statusbar_timer_.start(3000);
1352 void GuiView::clearMessage()
1354 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1355 // the hasFocus function mostly returns false, even if the focus is on
1356 // a workarea in this view.
1360 d.statusbar_timer_.stop();
1364 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1366 if (wa != d.current_work_area_
1367 || wa->bufferView().buffer().isInternal())
1369 Buffer const & buf = wa->bufferView().buffer();
1370 // Set the windows title
1371 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1372 if (buf.notifiesExternalModification()) {
1373 title = bformat(_("%1$s (modified externally)"), title);
1374 // If the external modification status has changed, then maybe the status of
1375 // buffer-save has changed too.
1379 title += from_ascii(" - LyX");
1381 setWindowTitle(toqstr(title));
1382 // Sets the path for the window: this is used by OSX to
1383 // allow a context click on the title bar showing a menu
1384 // with the path up to the file
1385 setWindowFilePath(toqstr(buf.absFileName()));
1386 // Tell Qt whether the current document is changed
1387 setWindowModified(!buf.isClean());
1389 if (buf.params().shell_escape)
1390 shell_escape_->show();
1392 shell_escape_->hide();
1394 if (buf.hasReadonlyFlag())
1399 if (buf.lyxvc().inUse()) {
1400 version_control_->show();
1401 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1403 version_control_->hide();
1407 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1409 if (d.current_work_area_)
1410 // disconnect the current work area from all slots
1411 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1413 disconnectBufferView();
1414 connectBufferView(wa->bufferView());
1415 connectBuffer(wa->bufferView().buffer());
1416 d.current_work_area_ = wa;
1417 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1418 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1419 QObject::connect(wa, SIGNAL(busy(bool)),
1420 this, SLOT(setBusy(bool)));
1421 // connection of a signal to a signal
1422 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1423 this, SIGNAL(bufferViewChanged()));
1424 Q_EMIT updateWindowTitle(wa);
1425 Q_EMIT bufferViewChanged();
1429 void GuiView::onBufferViewChanged()
1432 // Buffer-dependent dialogs must be updated. This is done here because
1433 // some dialogs require buffer()->text.
1435 zoom_slider_->setEnabled(currentBufferView());
1436 zoom_value_->setEnabled(currentBufferView());
1437 zoom_in_->setEnabled(currentBufferView());
1438 zoom_out_->setEnabled(currentBufferView());
1442 void GuiView::on_lastWorkAreaRemoved()
1445 // We already are in a close event. Nothing more to do.
1448 if (d.splitter_->count() > 1)
1449 // We have a splitter so don't close anything.
1452 // Reset and updates the dialogs.
1453 Q_EMIT bufferViewChanged();
1458 if (lyxrc.open_buffers_in_tabs)
1459 // Nothing more to do, the window should stay open.
1462 if (guiApp->viewIds().size() > 1) {
1468 // On Mac we also close the last window because the application stay
1469 // resident in memory. On other platforms we don't close the last
1470 // window because this would quit the application.
1476 void GuiView::updateStatusBar()
1478 // let the user see the explicit message
1479 if (d.statusbar_timer_.isActive())
1486 void GuiView::showMessage()
1490 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1491 if (msg.isEmpty()) {
1492 BufferView const * bv = currentBufferView();
1494 msg = toqstr(bv->cursor().currentState(devel_mode_));
1496 msg = qt_("Welcome to LyX!");
1498 statusBar()->showMessage(msg);
1502 bool GuiView::event(QEvent * e)
1506 // Useful debug code:
1507 //case QEvent::ActivationChange:
1508 //case QEvent::WindowDeactivate:
1509 //case QEvent::Paint:
1510 //case QEvent::Enter:
1511 //case QEvent::Leave:
1512 //case QEvent::HoverEnter:
1513 //case QEvent::HoverLeave:
1514 //case QEvent::HoverMove:
1515 //case QEvent::StatusTip:
1516 //case QEvent::DragEnter:
1517 //case QEvent::DragLeave:
1518 //case QEvent::Drop:
1521 case QEvent::WindowStateChange: {
1522 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1523 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1524 bool result = QMainWindow::event(e);
1525 bool nfstate = (windowState() & Qt::WindowFullScreen);
1526 if (!ofstate && nfstate) {
1527 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1528 // switch to full-screen state
1529 if (lyxrc.full_screen_statusbar)
1530 statusBar()->hide();
1531 if (lyxrc.full_screen_menubar)
1533 if (lyxrc.full_screen_toolbars) {
1534 for (auto const & tb_p : d.toolbars_)
1535 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1536 tb_p.second->hide();
1538 for (int i = 0; i != d.splitter_->count(); ++i)
1539 d.tabWorkArea(i)->setFullScreen(true);
1540 #if QT_VERSION > 0x050903
1541 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1542 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1544 setContentsMargins(-2, -2, -2, -2);
1546 hideDialogs("prefs", nullptr);
1547 } else if (ofstate && !nfstate) {
1548 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1549 // switch back from full-screen state
1550 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1551 statusBar()->show();
1552 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1554 if (lyxrc.full_screen_toolbars) {
1555 for (auto const & tb_p : d.toolbars_)
1556 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1557 tb_p.second->show();
1560 for (int i = 0; i != d.splitter_->count(); ++i)
1561 d.tabWorkArea(i)->setFullScreen(false);
1562 #if QT_VERSION > 0x050903
1563 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1565 setContentsMargins(0, 0, 0, 0);
1569 case QEvent::WindowActivate: {
1570 GuiView * old_view = guiApp->currentView();
1571 if (this == old_view) {
1573 return QMainWindow::event(e);
1575 if (old_view && old_view->currentBufferView()) {
1576 // save current selection to the selection buffer to allow
1577 // middle-button paste in this window.
1578 cap::saveSelection(old_view->currentBufferView()->cursor());
1580 guiApp->setCurrentView(this);
1581 if (d.current_work_area_)
1582 on_currentWorkAreaChanged(d.current_work_area_);
1586 return QMainWindow::event(e);
1589 case QEvent::ShortcutOverride: {
1591 if (isFullScreen() && menuBar()->isHidden()) {
1592 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1593 // FIXME: we should also try to detect special LyX shortcut such as
1594 // Alt-P and Alt-M. Right now there is a hack in
1595 // GuiWorkArea::processKeySym() that hides again the menubar for
1597 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1599 return QMainWindow::event(e);
1602 return QMainWindow::event(e);
1605 case QEvent::ApplicationPaletteChange: {
1606 // runtime switch from/to dark mode
1608 return QMainWindow::event(e);
1612 return QMainWindow::event(e);
1616 void GuiView::resetWindowTitle()
1618 setWindowTitle(qt_("LyX"));
1621 bool GuiView::focusNextPrevChild(bool /*next*/)
1628 bool GuiView::busy() const
1634 void GuiView::setBusy(bool busy)
1636 bool const busy_before = busy_ > 0;
1637 busy ? ++busy_ : --busy_;
1638 if ((busy_ > 0) == busy_before)
1639 // busy state didn't change
1643 QApplication::setOverrideCursor(Qt::WaitCursor);
1646 QApplication::restoreOverrideCursor();
1651 void GuiView::resetCommandExecute()
1653 command_execute_ = false;
1658 double GuiView::pixelRatio() const
1660 #if QT_VERSION >= 0x050000
1661 return qt_scale_factor * devicePixelRatio();
1668 GuiWorkArea * GuiView::workArea(int index)
1670 if (TabWorkArea * twa = d.currentTabWorkArea())
1671 if (index < twa->count())
1672 return twa->workArea(index);
1677 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1679 if (currentWorkArea()
1680 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1681 return currentWorkArea();
1682 if (TabWorkArea * twa = d.currentTabWorkArea())
1683 return twa->workArea(buffer);
1688 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1690 // Automatically create a TabWorkArea if there are none yet.
1691 TabWorkArea * tab_widget = d.splitter_->count()
1692 ? d.currentTabWorkArea() : addTabWorkArea();
1693 return tab_widget->addWorkArea(buffer, *this);
1697 TabWorkArea * GuiView::addTabWorkArea()
1699 TabWorkArea * twa = new TabWorkArea;
1700 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1701 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1702 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1703 this, SLOT(on_lastWorkAreaRemoved()));
1705 d.splitter_->addWidget(twa);
1706 d.stack_widget_->setCurrentWidget(d.splitter_);
1711 GuiWorkArea const * GuiView::currentWorkArea() const
1713 return d.current_work_area_;
1717 GuiWorkArea * GuiView::currentWorkArea()
1719 return d.current_work_area_;
1723 GuiWorkArea const * GuiView::currentMainWorkArea() const
1725 if (!d.currentTabWorkArea())
1727 return d.currentTabWorkArea()->currentWorkArea();
1731 GuiWorkArea * GuiView::currentMainWorkArea()
1733 if (!d.currentTabWorkArea())
1735 return d.currentTabWorkArea()->currentWorkArea();
1739 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1741 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1743 d.current_work_area_ = nullptr;
1745 Q_EMIT bufferViewChanged();
1749 // FIXME: I've no clue why this is here and why it accesses
1750 // theGuiApp()->currentView, which might be 0 (bug 6464).
1751 // See also 27525 (vfr).
1752 if (theGuiApp()->currentView() == this
1753 && theGuiApp()->currentView()->currentWorkArea() == wa)
1756 if (currentBufferView())
1757 cap::saveSelection(currentBufferView()->cursor());
1759 theGuiApp()->setCurrentView(this);
1760 d.current_work_area_ = wa;
1762 // We need to reset this now, because it will need to be
1763 // right if the tabWorkArea gets reset in the for loop. We
1764 // will change it back if we aren't in that case.
1765 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1766 d.current_main_work_area_ = wa;
1768 for (int i = 0; i != d.splitter_->count(); ++i) {
1769 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1770 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1771 << ", Current main wa: " << currentMainWorkArea());
1776 d.current_main_work_area_ = old_cmwa;
1778 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1779 on_currentWorkAreaChanged(wa);
1780 BufferView & bv = wa->bufferView();
1781 bv.cursor().fixIfBroken();
1783 wa->setUpdatesEnabled(true);
1784 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1788 void GuiView::removeWorkArea(GuiWorkArea * wa)
1790 LASSERT(wa, return);
1791 if (wa == d.current_work_area_) {
1793 disconnectBufferView();
1794 d.current_work_area_ = nullptr;
1795 d.current_main_work_area_ = nullptr;
1798 bool found_twa = false;
1799 for (int i = 0; i != d.splitter_->count(); ++i) {
1800 TabWorkArea * twa = d.tabWorkArea(i);
1801 if (twa->removeWorkArea(wa)) {
1802 // Found in this tab group, and deleted the GuiWorkArea.
1804 if (twa->count() != 0) {
1805 if (d.current_work_area_ == nullptr)
1806 // This means that we are closing the current GuiWorkArea, so
1807 // switch to the next GuiWorkArea in the found TabWorkArea.
1808 setCurrentWorkArea(twa->currentWorkArea());
1810 // No more WorkAreas in this tab group, so delete it.
1817 // It is not a tabbed work area (i.e., the search work area), so it
1818 // should be deleted by other means.
1819 LASSERT(found_twa, return);
1821 if (d.current_work_area_ == nullptr) {
1822 if (d.splitter_->count() != 0) {
1823 TabWorkArea * twa = d.currentTabWorkArea();
1824 setCurrentWorkArea(twa->currentWorkArea());
1826 // No more work areas, switch to the background widget.
1827 setCurrentWorkArea(nullptr);
1833 LayoutBox * GuiView::getLayoutDialog() const
1839 void GuiView::updateLayoutList()
1842 d.layout_->updateContents(false);
1846 void GuiView::updateToolbars()
1848 if (d.current_work_area_) {
1850 if (d.current_work_area_->bufferView().cursor().inMathed()
1851 && !d.current_work_area_->bufferView().cursor().inRegexped())
1852 context |= Toolbars::MATH;
1853 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1854 context |= Toolbars::TABLE;
1855 if (currentBufferView()->buffer().areChangesPresent()
1856 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1857 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1858 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1859 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1860 context |= Toolbars::REVIEW;
1861 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1862 context |= Toolbars::MATHMACROTEMPLATE;
1863 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1864 context |= Toolbars::IPA;
1865 if (command_execute_)
1866 context |= Toolbars::MINIBUFFER;
1867 if (minibuffer_focus_) {
1868 context |= Toolbars::MINIBUFFER_FOCUS;
1869 minibuffer_focus_ = false;
1872 for (auto const & tb_p : d.toolbars_)
1873 tb_p.second->update(context);
1875 for (auto const & tb_p : d.toolbars_)
1876 tb_p.second->update();
1880 void GuiView::refillToolbars()
1882 DynamicMenuButton::resetIconCache();
1883 for (auto const & tb_p : d.toolbars_)
1884 tb_p.second->refill();
1888 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1890 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1891 LASSERT(newBuffer, return);
1893 GuiWorkArea * wa = workArea(*newBuffer);
1894 if (wa == nullptr) {
1896 newBuffer->masterBuffer()->updateBuffer();
1898 wa = addWorkArea(*newBuffer);
1899 // scroll to the position when the BufferView was last closed
1900 if (lyxrc.use_lastfilepos) {
1901 LastFilePosSection::FilePos filepos =
1902 theSession().lastFilePos().load(newBuffer->fileName());
1903 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1906 //Disconnect the old buffer...there's no new one.
1909 connectBuffer(*newBuffer);
1910 connectBufferView(wa->bufferView());
1912 setCurrentWorkArea(wa);
1916 void GuiView::connectBuffer(Buffer & buf)
1918 buf.setGuiDelegate(this);
1922 void GuiView::disconnectBuffer()
1924 if (d.current_work_area_)
1925 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1929 void GuiView::connectBufferView(BufferView & bv)
1931 bv.setGuiDelegate(this);
1935 void GuiView::disconnectBufferView()
1937 if (d.current_work_area_)
1938 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1942 void GuiView::errors(string const & error_type, bool from_master)
1944 BufferView const * const bv = currentBufferView();
1948 ErrorList const & el = from_master ?
1949 bv->buffer().masterBuffer()->errorList(error_type) :
1950 bv->buffer().errorList(error_type);
1955 string err = error_type;
1957 err = "from_master|" + error_type;
1958 showDialog("errorlist", err);
1962 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1964 d.toc_models_.updateItem(toqstr(type), dit);
1968 void GuiView::structureChanged()
1970 // This is called from the Buffer, which has no way to ensure that cursors
1971 // in BufferView remain valid.
1972 if (documentBufferView())
1973 documentBufferView()->cursor().sanitize();
1974 // FIXME: This is slightly expensive, though less than the tocBackend update
1975 // (#9880). This also resets the view in the Toc Widget (#6675).
1976 d.toc_models_.reset(documentBufferView());
1977 // Navigator needs more than a simple update in this case. It needs to be
1979 updateDialog("toc", "");
1983 void GuiView::updateDialog(string const & name, string const & sdata)
1985 if (!isDialogVisible(name))
1988 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1989 if (it == d.dialogs_.end())
1992 Dialog * const dialog = it->second.get();
1993 if (dialog->isVisibleView())
1994 dialog->initialiseParams(sdata);
1998 BufferView * GuiView::documentBufferView()
2000 return currentMainWorkArea()
2001 ? ¤tMainWorkArea()->bufferView()
2006 BufferView const * GuiView::documentBufferView() const
2008 return currentMainWorkArea()
2009 ? ¤tMainWorkArea()->bufferView()
2014 BufferView * GuiView::currentBufferView()
2016 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2020 BufferView const * GuiView::currentBufferView() const
2022 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2026 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2027 Buffer const * orig, Buffer * clone)
2029 bool const success = clone->autoSave();
2031 busyBuffers.remove(orig);
2033 ? _("Automatic save done.")
2034 : _("Automatic save failed!");
2038 void GuiView::autoSave()
2040 LYXERR(Debug::INFO, "Running autoSave()");
2042 Buffer * buffer = documentBufferView()
2043 ? &documentBufferView()->buffer() : nullptr;
2045 resetAutosaveTimers();
2049 GuiViewPrivate::busyBuffers.insert(buffer);
2050 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2051 buffer, buffer->cloneBufferOnly());
2052 d.autosave_watcher_.setFuture(f);
2053 resetAutosaveTimers();
2057 void GuiView::resetAutosaveTimers()
2060 d.autosave_timeout_.restart();
2064 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2067 Buffer * buf = currentBufferView()
2068 ? ¤tBufferView()->buffer() : nullptr;
2069 Buffer * doc_buffer = documentBufferView()
2070 ? &(documentBufferView()->buffer()) : nullptr;
2073 /* In LyX/Mac, when a dialog is open, the menus of the
2074 application can still be accessed without giving focus to
2075 the main window. In this case, we want to disable the menu
2076 entries that are buffer-related.
2077 This code must not be used on Linux and Windows, since it
2078 would disable buffer-related entries when hovering over the
2079 menu (see bug #9574).
2081 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2087 // Check whether we need a buffer
2088 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2089 // no, exit directly
2090 flag.message(from_utf8(N_("Command not allowed with"
2091 "out any document open")));
2092 flag.setEnabled(false);
2096 if (cmd.origin() == FuncRequest::TOC) {
2097 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2098 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2099 flag.setEnabled(false);
2103 switch(cmd.action()) {
2104 case LFUN_BUFFER_IMPORT:
2107 case LFUN_MASTER_BUFFER_EXPORT:
2109 && (doc_buffer->parent() != nullptr
2110 || doc_buffer->hasChildren())
2111 && !d.processing_thread_watcher_.isRunning()
2112 // this launches a dialog, which would be in the wrong Buffer
2113 && !(::lyx::operator==(cmd.argument(), "custom"));
2116 case LFUN_MASTER_BUFFER_UPDATE:
2117 case LFUN_MASTER_BUFFER_VIEW:
2119 && (doc_buffer->parent() != nullptr
2120 || doc_buffer->hasChildren())
2121 && !d.processing_thread_watcher_.isRunning();
2124 case LFUN_BUFFER_UPDATE:
2125 case LFUN_BUFFER_VIEW: {
2126 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2130 string format = to_utf8(cmd.argument());
2131 if (cmd.argument().empty())
2132 format = doc_buffer->params().getDefaultOutputFormat();
2133 enable = doc_buffer->params().isExportable(format, true);
2137 case LFUN_BUFFER_RELOAD:
2138 enable = doc_buffer && !doc_buffer->isUnnamed()
2139 && doc_buffer->fileName().exists()
2140 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2143 case LFUN_BUFFER_RESET_EXPORT:
2144 enable = doc_buffer != nullptr;
2147 case LFUN_BUFFER_CHILD_OPEN:
2148 enable = doc_buffer != nullptr;
2151 case LFUN_MASTER_BUFFER_FORALL: {
2152 if (doc_buffer == nullptr) {
2153 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2157 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2158 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2159 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2164 for (Buffer * buf : doc_buffer->allRelatives()) {
2165 GuiWorkArea * wa = workArea(*buf);
2168 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2169 enable = flag.enabled();
2176 case LFUN_BUFFER_WRITE:
2177 enable = doc_buffer && (doc_buffer->isUnnamed()
2178 || (!doc_buffer->isClean()
2179 || cmd.argument() == "force"));
2182 //FIXME: This LFUN should be moved to GuiApplication.
2183 case LFUN_BUFFER_WRITE_ALL: {
2184 // We enable the command only if there are some modified buffers
2185 Buffer * first = theBufferList().first();
2190 // We cannot use a for loop as the buffer list is a cycle.
2192 if (!b->isClean()) {
2196 b = theBufferList().next(b);
2197 } while (b != first);
2201 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2202 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2205 case LFUN_BUFFER_EXPORT: {
2206 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2210 return doc_buffer->getStatus(cmd, flag);
2213 case LFUN_BUFFER_EXPORT_AS:
2214 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2219 case LFUN_BUFFER_WRITE_AS:
2220 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2221 enable = doc_buffer != nullptr;
2224 case LFUN_EXPORT_CANCEL:
2225 enable = d.processing_thread_watcher_.isRunning();
2228 case LFUN_BUFFER_CLOSE:
2229 case LFUN_VIEW_CLOSE:
2230 enable = doc_buffer != nullptr;
2233 case LFUN_BUFFER_CLOSE_ALL:
2234 enable = theBufferList().last() != theBufferList().first();
2237 case LFUN_BUFFER_CHKTEX: {
2238 // hide if we have no checktex command
2239 if (lyxrc.chktex_command.empty()) {
2240 flag.setUnknown(true);
2244 if (!doc_buffer || !doc_buffer->params().isLatex()
2245 || d.processing_thread_watcher_.isRunning()) {
2246 // grey out, don't hide
2254 case LFUN_VIEW_SPLIT:
2255 if (cmd.getArg(0) == "vertical")
2256 enable = doc_buffer && (d.splitter_->count() == 1 ||
2257 d.splitter_->orientation() == Qt::Vertical);
2259 enable = doc_buffer && (d.splitter_->count() == 1 ||
2260 d.splitter_->orientation() == Qt::Horizontal);
2263 case LFUN_TAB_GROUP_CLOSE:
2264 enable = d.tabWorkAreaCount() > 1;
2267 case LFUN_DEVEL_MODE_TOGGLE:
2268 flag.setOnOff(devel_mode_);
2271 case LFUN_TOOLBAR_SET: {
2272 string const name = cmd.getArg(0);
2273 string const state = cmd.getArg(1);
2274 if (name.empty() || state.empty()) {
2276 docstring const msg =
2277 _("Function toolbar-set requires two arguments!");
2281 if (state != "on" && state != "off" && state != "auto") {
2283 docstring const msg =
2284 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2289 if (GuiToolbar * t = toolbar(name)) {
2290 bool const autovis = t->visibility() & Toolbars::AUTO;
2292 flag.setOnOff(t->isVisible() && !autovis);
2293 else if (state == "off")
2294 flag.setOnOff(!t->isVisible() && !autovis);
2295 else if (state == "auto")
2296 flag.setOnOff(autovis);
2299 docstring const msg =
2300 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2306 case LFUN_TOOLBAR_TOGGLE: {
2307 string const name = cmd.getArg(0);
2308 if (GuiToolbar * t = toolbar(name))
2309 flag.setOnOff(t->isVisible());
2312 docstring const msg =
2313 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2319 case LFUN_TOOLBAR_MOVABLE: {
2320 string const name = cmd.getArg(0);
2321 // use negation since locked == !movable
2323 // toolbar name * locks all toolbars
2324 flag.setOnOff(!toolbarsMovable_);
2325 else if (GuiToolbar * t = toolbar(name))
2326 flag.setOnOff(!(t->isMovable()));
2329 docstring const msg =
2330 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2336 case LFUN_ICON_SIZE:
2337 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2340 case LFUN_DROP_LAYOUTS_CHOICE:
2341 enable = buf != nullptr;
2344 case LFUN_UI_TOGGLE:
2345 if (cmd.argument() == "zoomslider") {
2346 enable = doc_buffer;
2347 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2349 flag.setOnOff(isFullScreen());
2352 case LFUN_DIALOG_DISCONNECT_INSET:
2355 case LFUN_DIALOG_HIDE:
2356 // FIXME: should we check if the dialog is shown?
2359 case LFUN_DIALOG_TOGGLE:
2360 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2363 case LFUN_DIALOG_SHOW: {
2364 string const name = cmd.getArg(0);
2366 enable = name == "aboutlyx"
2367 || name == "file" //FIXME: should be removed.
2368 || name == "lyxfiles"
2370 || name == "texinfo"
2371 || name == "progress"
2372 || name == "compare";
2373 else if (name == "character" || name == "symbols"
2374 || name == "mathdelimiter" || name == "mathmatrix") {
2375 if (!buf || buf->isReadonly())
2378 Cursor const & cur = currentBufferView()->cursor();
2379 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2382 else if (name == "latexlog")
2383 enable = FileName(doc_buffer->logName()).isReadableFile();
2384 else if (name == "spellchecker")
2385 enable = theSpellChecker()
2386 && !doc_buffer->text().empty();
2387 else if (name == "vclog")
2388 enable = doc_buffer->lyxvc().inUse();
2392 case LFUN_DIALOG_UPDATE: {
2393 string const name = cmd.getArg(0);
2395 enable = name == "prefs";
2399 case LFUN_COMMAND_EXECUTE:
2401 case LFUN_MENU_OPEN:
2402 // Nothing to check.
2405 case LFUN_COMPLETION_INLINE:
2406 if (!d.current_work_area_
2407 || !d.current_work_area_->completer().inlinePossible(
2408 currentBufferView()->cursor()))
2412 case LFUN_COMPLETION_POPUP:
2413 if (!d.current_work_area_
2414 || !d.current_work_area_->completer().popupPossible(
2415 currentBufferView()->cursor()))
2420 if (!d.current_work_area_
2421 || !d.current_work_area_->completer().inlinePossible(
2422 currentBufferView()->cursor()))
2426 case LFUN_COMPLETION_ACCEPT:
2427 if (!d.current_work_area_
2428 || (!d.current_work_area_->completer().popupVisible()
2429 && !d.current_work_area_->completer().inlineVisible()
2430 && !d.current_work_area_->completer().completionAvailable()))
2434 case LFUN_COMPLETION_CANCEL:
2435 if (!d.current_work_area_
2436 || (!d.current_work_area_->completer().popupVisible()
2437 && !d.current_work_area_->completer().inlineVisible()))
2441 case LFUN_BUFFER_ZOOM_OUT:
2442 case LFUN_BUFFER_ZOOM_IN: {
2443 // only diff between these two is that the default for ZOOM_OUT
2445 bool const neg_zoom =
2446 convert<int>(cmd.argument()) < 0 ||
2447 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2448 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2449 docstring const msg =
2450 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2454 enable = doc_buffer;
2458 case LFUN_BUFFER_ZOOM: {
2459 bool const less_than_min_zoom =
2460 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2461 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2462 docstring const msg =
2463 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2466 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2469 enable = doc_buffer;
2473 case LFUN_BUFFER_MOVE_NEXT:
2474 case LFUN_BUFFER_MOVE_PREVIOUS:
2475 // we do not cycle when moving
2476 case LFUN_BUFFER_NEXT:
2477 case LFUN_BUFFER_PREVIOUS:
2478 // because we cycle, it doesn't matter whether on first or last
2479 enable = (d.currentTabWorkArea()->count() > 1);
2481 case LFUN_BUFFER_SWITCH:
2482 // toggle on the current buffer, but do not toggle off
2483 // the other ones (is that a good idea?)
2485 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2486 flag.setOnOff(true);
2489 case LFUN_VC_REGISTER:
2490 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2492 case LFUN_VC_RENAME:
2493 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2496 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2498 case LFUN_VC_CHECK_IN:
2499 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2501 case LFUN_VC_CHECK_OUT:
2502 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2504 case LFUN_VC_LOCKING_TOGGLE:
2505 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2506 && doc_buffer->lyxvc().lockingToggleEnabled();
2507 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2509 case LFUN_VC_REVERT:
2510 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2511 && !doc_buffer->hasReadonlyFlag();
2513 case LFUN_VC_UNDO_LAST:
2514 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2516 case LFUN_VC_REPO_UPDATE:
2517 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2519 case LFUN_VC_COMMAND: {
2520 if (cmd.argument().empty())
2522 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2526 case LFUN_VC_COMPARE:
2527 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2530 case LFUN_SERVER_GOTO_FILE_ROW:
2531 case LFUN_LYX_ACTIVATE:
2532 case LFUN_WINDOW_RAISE:
2534 case LFUN_FORWARD_SEARCH:
2535 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2538 case LFUN_FILE_INSERT_PLAINTEXT:
2539 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2540 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2543 case LFUN_SPELLING_CONTINUOUSLY:
2544 flag.setOnOff(lyxrc.spellcheck_continuously);
2547 case LFUN_CITATION_OPEN:
2556 flag.setEnabled(false);
2562 static FileName selectTemplateFile()
2564 FileDialog dlg(qt_("Select template file"));
2565 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2566 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2568 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2569 QStringList(qt_("LyX Documents (*.lyx)")));
2571 if (result.first == FileDialog::Later)
2573 if (result.second.isEmpty())
2575 return FileName(fromqstr(result.second));
2579 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2583 Buffer * newBuffer = nullptr;
2585 newBuffer = checkAndLoadLyXFile(filename);
2586 } catch (ExceptionMessage const &) {
2593 message(_("Document not loaded."));
2597 setBuffer(newBuffer);
2598 newBuffer->errors("Parse");
2601 theSession().lastFiles().add(filename);
2602 theSession().writeFile();
2609 void GuiView::openDocument(string const & fname)
2611 string initpath = lyxrc.document_path;
2613 if (documentBufferView()) {
2614 string const trypath = documentBufferView()->buffer().filePath();
2615 // If directory is writeable, use this as default.
2616 if (FileName(trypath).isDirWritable())
2622 if (fname.empty()) {
2623 FileDialog dlg(qt_("Select document to open"));
2624 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2625 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2627 QStringList const filter({
2628 qt_("LyX Documents (*.lyx)"),
2629 qt_("LyX Document Backups (*.lyx~)"),
2630 qt_("All Files (*.*)")
2632 FileDialog::Result result =
2633 dlg.open(toqstr(initpath), filter);
2635 if (result.first == FileDialog::Later)
2638 filename = fromqstr(result.second);
2640 // check selected filename
2641 if (filename.empty()) {
2642 message(_("Canceled."));
2648 // get absolute path of file and add ".lyx" to the filename if
2650 FileName const fullname =
2651 fileSearch(string(), filename, "lyx", support::may_not_exist);
2652 if (!fullname.empty())
2653 filename = fullname.absFileName();
2655 if (!fullname.onlyPath().isDirectory()) {
2656 Alert::warning(_("Invalid filename"),
2657 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2658 from_utf8(fullname.absFileName())));
2662 // if the file doesn't exist and isn't already open (bug 6645),
2663 // let the user create one
2664 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2665 !LyXVC::file_not_found_hook(fullname)) {
2666 // the user specifically chose this name. Believe him.
2667 Buffer * const b = newFile(filename, string(), true);
2673 docstring const disp_fn = makeDisplayPath(filename);
2674 message(bformat(_("Opening document %1$s..."), disp_fn));
2677 Buffer * buf = loadDocument(fullname);
2679 str2 = bformat(_("Document %1$s opened."), disp_fn);
2680 if (buf->lyxvc().inUse())
2681 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2682 " " + _("Version control detected.");
2684 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2689 // FIXME: clean that
2690 static bool import(GuiView * lv, FileName const & filename,
2691 string const & format, ErrorList & errorList)
2693 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2695 string loader_format;
2696 vector<string> loaders = theConverters().loaders();
2697 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2698 for (string const & loader : loaders) {
2699 if (!theConverters().isReachable(format, loader))
2702 string const tofile =
2703 support::changeExtension(filename.absFileName(),
2704 theFormats().extension(loader));
2705 if (theConverters().convert(nullptr, filename, FileName(tofile),
2706 filename, format, loader, errorList) != Converters::SUCCESS)
2708 loader_format = loader;
2711 if (loader_format.empty()) {
2712 frontend::Alert::error(_("Couldn't import file"),
2713 bformat(_("No information for importing the format %1$s."),
2714 translateIfPossible(theFormats().prettyName(format))));
2718 loader_format = format;
2720 if (loader_format == "lyx") {
2721 Buffer * buf = lv->loadDocument(lyxfile);
2725 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2729 bool as_paragraphs = loader_format == "textparagraph";
2730 string filename2 = (loader_format == format) ? filename.absFileName()
2731 : support::changeExtension(filename.absFileName(),
2732 theFormats().extension(loader_format));
2733 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2735 guiApp->setCurrentView(lv);
2736 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2743 void GuiView::importDocument(string const & argument)
2746 string filename = split(argument, format, ' ');
2748 LYXERR(Debug::INFO, format << " file: " << filename);
2750 // need user interaction
2751 if (filename.empty()) {
2752 string initpath = lyxrc.document_path;
2753 if (documentBufferView()) {
2754 string const trypath = documentBufferView()->buffer().filePath();
2755 // If directory is writeable, use this as default.
2756 if (FileName(trypath).isDirWritable())
2760 docstring const text = bformat(_("Select %1$s file to import"),
2761 translateIfPossible(theFormats().prettyName(format)));
2763 FileDialog dlg(toqstr(text));
2764 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2765 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2767 docstring filter = translateIfPossible(theFormats().prettyName(format));
2770 filter += from_utf8(theFormats().extensions(format));
2773 FileDialog::Result result =
2774 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2776 if (result.first == FileDialog::Later)
2779 filename = fromqstr(result.second);
2781 // check selected filename
2782 if (filename.empty())
2783 message(_("Canceled."));
2786 if (filename.empty())
2789 // get absolute path of file
2790 FileName const fullname(support::makeAbsPath(filename));
2792 // Can happen if the user entered a path into the dialog
2794 if (fullname.onlyFileName().empty()) {
2795 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2796 "Aborting import."),
2797 from_utf8(fullname.absFileName()));
2798 frontend::Alert::error(_("File name error"), msg);
2799 message(_("Canceled."));
2804 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2806 // Check if the document already is open
2807 Buffer * buf = theBufferList().getBuffer(lyxfile);
2810 if (!closeBuffer()) {
2811 message(_("Canceled."));
2816 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2818 // if the file exists already, and we didn't do
2819 // -i lyx thefile.lyx, warn
2820 if (lyxfile.exists() && fullname != lyxfile) {
2822 docstring text = bformat(_("The document %1$s already exists.\n\n"
2823 "Do you want to overwrite that document?"), displaypath);
2824 int const ret = Alert::prompt(_("Overwrite document?"),
2825 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2828 message(_("Canceled."));
2833 message(bformat(_("Importing %1$s..."), displaypath));
2834 ErrorList errorList;
2835 if (import(this, fullname, format, errorList))
2836 message(_("imported."));
2838 message(_("file not imported!"));
2840 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2844 void GuiView::newDocument(string const & filename, string templatefile,
2847 FileName initpath(lyxrc.document_path);
2848 if (documentBufferView()) {
2849 FileName const trypath(documentBufferView()->buffer().filePath());
2850 // If directory is writeable, use this as default.
2851 if (trypath.isDirWritable())
2855 if (from_template) {
2856 if (templatefile.empty())
2857 templatefile = selectTemplateFile().absFileName();
2858 if (templatefile.empty())
2863 if (filename.empty())
2864 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2866 b = newFile(filename, templatefile, true);
2871 // If no new document could be created, it is unsure
2872 // whether there is a valid BufferView.
2873 if (currentBufferView())
2874 // Ensure the cursor is correctly positioned on screen.
2875 currentBufferView()->showCursor();
2879 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2881 BufferView * bv = documentBufferView();
2886 FileName filename(to_utf8(fname));
2887 if (filename.empty()) {
2888 // Launch a file browser
2890 string initpath = lyxrc.document_path;
2891 string const trypath = bv->buffer().filePath();
2892 // If directory is writeable, use this as default.
2893 if (FileName(trypath).isDirWritable())
2897 FileDialog dlg(qt_("Select LyX document to insert"));
2898 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2899 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2901 FileDialog::Result result = dlg.open(toqstr(initpath),
2902 QStringList(qt_("LyX Documents (*.lyx)")));
2904 if (result.first == FileDialog::Later)
2908 filename.set(fromqstr(result.second));
2910 // check selected filename
2911 if (filename.empty()) {
2912 // emit message signal.
2913 message(_("Canceled."));
2918 bv->insertLyXFile(filename, ignorelang);
2919 bv->buffer().errors("Parse");
2924 string const GuiView::getTemplatesPath(Buffer & b)
2926 // We start off with the user's templates path
2927 string result = addPath(package().user_support().absFileName(), "templates");
2928 // Check for the document language
2929 string const langcode = b.params().language->code();
2930 string const shortcode = langcode.substr(0, 2);
2931 if (!langcode.empty() && shortcode != "en") {
2932 string subpath = addPath(result, shortcode);
2933 string subpath_long = addPath(result, langcode);
2934 // If we have a subdirectory for the language already,
2936 FileName sp = FileName(subpath);
2937 if (sp.isDirectory())
2939 else if (FileName(subpath_long).isDirectory())
2940 result = subpath_long;
2942 // Ask whether we should create such a subdirectory
2943 docstring const text =
2944 bformat(_("It is suggested to save the template in a subdirectory\n"
2945 "appropriate to the document language (%1$s).\n"
2946 "This subdirectory does not exists yet.\n"
2947 "Do you want to create it?"),
2948 _(b.params().language->display()));
2949 if (Alert::prompt(_("Create Language Directory?"),
2950 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2951 // If the user agreed, we try to create it and report if this failed.
2952 if (!sp.createDirectory(0777))
2953 Alert::error(_("Subdirectory creation failed!"),
2954 _("Could not create subdirectory.\n"
2955 "The template will be saved in the parent directory."));
2961 // Do we have a layout category?
2962 string const cat = b.params().baseClass() ?
2963 b.params().baseClass()->category()
2966 string subpath = addPath(result, cat);
2967 // If we have a subdirectory for the category already,
2969 FileName sp = FileName(subpath);
2970 if (sp.isDirectory())
2973 // Ask whether we should create such a subdirectory
2974 docstring const text =
2975 bformat(_("It is suggested to save the template in a subdirectory\n"
2976 "appropriate to the layout category (%1$s).\n"
2977 "This subdirectory does not exists yet.\n"
2978 "Do you want to create it?"),
2980 if (Alert::prompt(_("Create Category Directory?"),
2981 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2982 // If the user agreed, we try to create it and report if this failed.
2983 if (!sp.createDirectory(0777))
2984 Alert::error(_("Subdirectory creation failed!"),
2985 _("Could not create subdirectory.\n"
2986 "The template will be saved in the parent directory."));
2996 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2998 FileName fname = b.fileName();
2999 FileName const oldname = fname;
3000 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3002 if (!newname.empty()) {
3005 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3007 fname = support::makeAbsPath(to_utf8(newname),
3008 oldname.onlyPath().absFileName());
3010 // Switch to this Buffer.
3013 // No argument? Ask user through dialog.
3015 QString const title = as_template ? qt_("Choose a filename to save template as")
3016 : qt_("Choose a filename to save document as");
3017 FileDialog dlg(title);
3018 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3019 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3021 if (!isLyXFileName(fname.absFileName()))
3022 fname.changeExtension(".lyx");
3024 string const path = as_template ?
3026 : fname.onlyPath().absFileName();
3027 FileDialog::Result result =
3028 dlg.save(toqstr(path),
3029 QStringList(qt_("LyX Documents (*.lyx)")),
3030 toqstr(fname.onlyFileName()));
3032 if (result.first == FileDialog::Later)
3035 fname.set(fromqstr(result.second));
3040 if (!isLyXFileName(fname.absFileName()))
3041 fname.changeExtension(".lyx");
3044 // fname is now the new Buffer location.
3046 // if there is already a Buffer open with this name, we do not want
3047 // to have another one. (the second test makes sure we're not just
3048 // trying to overwrite ourselves, which is fine.)
3049 if (theBufferList().exists(fname) && fname != oldname
3050 && theBufferList().getBuffer(fname) != &b) {
3051 docstring const text =
3052 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3053 "Please close it before attempting to overwrite it.\n"
3054 "Do you want to choose a new filename?"),
3055 from_utf8(fname.absFileName()));
3056 int const ret = Alert::prompt(_("Chosen File Already Open"),
3057 text, 0, 1, _("&Rename"), _("&Cancel"));
3059 case 0: return renameBuffer(b, docstring(), kind);
3060 case 1: return false;
3065 bool const existsLocal = fname.exists();
3066 bool const existsInVC = LyXVC::fileInVC(fname);
3067 if (existsLocal || existsInVC) {
3068 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3069 if (kind != LV_WRITE_AS && existsInVC) {
3070 // renaming to a name that is already in VC
3072 docstring text = bformat(_("The document %1$s "
3073 "is already registered.\n\n"
3074 "Do you want to choose a new name?"),
3076 docstring const title = (kind == LV_VC_RENAME) ?
3077 _("Rename document?") : _("Copy document?");
3078 docstring const button = (kind == LV_VC_RENAME) ?
3079 _("&Rename") : _("&Copy");
3080 int const ret = Alert::prompt(title, text, 0, 1,
3081 button, _("&Cancel"));
3083 case 0: return renameBuffer(b, docstring(), kind);
3084 case 1: return false;
3089 docstring text = bformat(_("The document %1$s "
3090 "already exists.\n\n"
3091 "Do you want to overwrite that document?"),
3093 int const ret = Alert::prompt(_("Overwrite document?"),
3094 text, 0, 2, _("&Overwrite"),
3095 _("&Rename"), _("&Cancel"));
3098 case 1: return renameBuffer(b, docstring(), kind);
3099 case 2: return false;
3105 case LV_VC_RENAME: {
3106 string msg = b.lyxvc().rename(fname);
3109 message(from_utf8(msg));
3113 string msg = b.lyxvc().copy(fname);
3116 message(from_utf8(msg));
3120 case LV_WRITE_AS_TEMPLATE:
3123 // LyXVC created the file already in case of LV_VC_RENAME or
3124 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3125 // relative paths of included stuff right if we moved e.g. from
3126 // /a/b.lyx to /a/c/b.lyx.
3128 bool const saved = saveBuffer(b, fname);
3135 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3137 FileName fname = b.fileName();
3139 FileDialog dlg(qt_("Choose a filename to export the document as"));
3140 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3143 QString const anyformat = qt_("Guess from extension (*.*)");
3146 vector<Format const *> export_formats;
3147 for (Format const & f : theFormats())
3148 if (f.documentFormat())
3149 export_formats.push_back(&f);
3150 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3151 map<QString, string> fmap;
3154 for (Format const * f : export_formats) {
3155 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3156 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3158 from_ascii(f->extension())));
3159 types << loc_filter;
3160 fmap[loc_filter] = f->name();
3161 if (from_ascii(f->name()) == iformat) {
3162 filter = loc_filter;
3163 ext = f->extension();
3166 string ofname = fname.onlyFileName();
3168 ofname = support::changeExtension(ofname, ext);
3169 FileDialog::Result result =
3170 dlg.save(toqstr(fname.onlyPath().absFileName()),
3174 if (result.first != FileDialog::Chosen)
3178 fname.set(fromqstr(result.second));
3179 if (filter == anyformat)
3180 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3182 fmt_name = fmap[filter];
3183 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3184 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3186 if (fmt_name.empty() || fname.empty())
3189 // fname is now the new Buffer location.
3190 if (fname.exists()) {
3191 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3192 docstring text = bformat(_("The document %1$s already "
3193 "exists.\n\nDo you want to "
3194 "overwrite that document?"),
3196 int const ret = Alert::prompt(_("Overwrite document?"),
3197 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3200 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3201 case 2: return false;
3205 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3208 return dr.dispatched();
3212 bool GuiView::saveBuffer(Buffer & b)
3214 return saveBuffer(b, FileName());
3218 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3220 if (workArea(b) && workArea(b)->inDialogMode())
3223 if (fn.empty() && b.isUnnamed())
3224 return renameBuffer(b, docstring());
3226 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3228 theSession().lastFiles().add(b.fileName());
3229 theSession().writeFile();
3233 // Switch to this Buffer.
3236 // FIXME: we don't tell the user *WHY* the save failed !!
3237 docstring const file = makeDisplayPath(b.absFileName(), 30);
3238 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3239 "Do you want to rename the document and "
3240 "try again?"), file);
3241 int const ret = Alert::prompt(_("Rename and save?"),
3242 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3245 if (!renameBuffer(b, docstring()))
3254 return saveBuffer(b, fn);
3258 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3260 return closeWorkArea(wa, false);
3264 // We only want to close the buffer if it is not visible in other workareas
3265 // of the same view, nor in other views, and if this is not a child
3266 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3268 Buffer & buf = wa->bufferView().buffer();
3270 bool last_wa = d.countWorkAreasOf(buf) == 1
3271 && !inOtherView(buf) && !buf.parent();
3273 bool close_buffer = last_wa;
3276 if (lyxrc.close_buffer_with_last_view == "yes")
3278 else if (lyxrc.close_buffer_with_last_view == "no")
3279 close_buffer = false;
3282 if (buf.isUnnamed())
3283 file = from_utf8(buf.fileName().onlyFileName());
3285 file = buf.fileName().displayName(30);
3286 docstring const text = bformat(
3287 _("Last view on document %1$s is being closed.\n"
3288 "Would you like to close or hide the document?\n"
3290 "Hidden documents can be displayed back through\n"
3291 "the menu: View->Hidden->...\n"
3293 "To remove this question, set your preference in:\n"
3294 " Tools->Preferences->Look&Feel->UserInterface\n"
3296 int ret = Alert::prompt(_("Close or hide document?"),
3297 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3300 close_buffer = (ret == 0);
3304 return closeWorkArea(wa, close_buffer);
3308 bool GuiView::closeBuffer()
3310 GuiWorkArea * wa = currentMainWorkArea();
3311 // coverity complained about this
3312 // it seems unnecessary, but perhaps is worth the check
3313 LASSERT(wa, return false);
3315 setCurrentWorkArea(wa);
3316 Buffer & buf = wa->bufferView().buffer();
3317 return closeWorkArea(wa, !buf.parent());
3321 void GuiView::writeSession() const {
3322 GuiWorkArea const * active_wa = currentMainWorkArea();
3323 for (int i = 0; i < d.splitter_->count(); ++i) {
3324 TabWorkArea * twa = d.tabWorkArea(i);
3325 for (int j = 0; j < twa->count(); ++j) {
3326 GuiWorkArea * wa = twa->workArea(j);
3327 Buffer & buf = wa->bufferView().buffer();
3328 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3334 bool GuiView::closeBufferAll()
3337 for (auto & buf : theBufferList()) {
3338 if (!saveBufferIfNeeded(*buf, false)) {
3339 // Closing has been cancelled, so abort.
3344 // Close the workareas in all other views
3345 QList<int> const ids = guiApp->viewIds();
3346 for (int i = 0; i != ids.size(); ++i) {
3347 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3351 // Close our own workareas
3352 if (!closeWorkAreaAll())
3359 bool GuiView::closeWorkAreaAll()
3361 setCurrentWorkArea(currentMainWorkArea());
3363 // We might be in a situation that there is still a tabWorkArea, but
3364 // there are no tabs anymore. This can happen when we get here after a
3365 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3366 // many TabWorkArea's have no documents anymore.
3369 // We have to call count() each time, because it can happen that
3370 // more than one splitter will disappear in one iteration (bug 5998).
3371 while (d.splitter_->count() > empty_twa) {
3372 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3374 if (twa->count() == 0)
3377 setCurrentWorkArea(twa->currentWorkArea());
3378 if (!closeTabWorkArea(twa))
3386 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3391 Buffer & buf = wa->bufferView().buffer();
3393 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3394 Alert::warning(_("Close document"),
3395 _("Document could not be closed because it is being processed by LyX."));
3400 return closeBuffer(buf);
3402 if (!inMultiTabs(wa))
3403 if (!saveBufferIfNeeded(buf, true))
3411 bool GuiView::closeBuffer(Buffer & buf)
3413 bool success = true;
3414 for (Buffer * child_buf : buf.getChildren()) {
3415 if (theBufferList().isOthersChild(&buf, child_buf)) {
3416 child_buf->setParent(nullptr);
3420 // FIXME: should we look in other tabworkareas?
3421 // ANSWER: I don't think so. I've tested, and if the child is
3422 // open in some other window, it closes without a problem.
3423 GuiWorkArea * child_wa = workArea(*child_buf);
3426 // If we are in a close_event all children will be closed in some time,
3427 // so no need to do it here. This will ensure that the children end up
3428 // in the session file in the correct order. If we close the master
3429 // buffer, we can close or release the child buffers here too.
3431 success = closeWorkArea(child_wa, true);
3435 // In this case the child buffer is open but hidden.
3436 // Even in this case, children can be dirty (e.g.,
3437 // after a label change in the master, see #11405).
3438 // Therefore, check this
3439 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3440 // If we are in a close_event all children will be closed in some time,
3441 // so no need to do it here. This will ensure that the children end up
3442 // in the session file in the correct order. If we close the master
3443 // buffer, we can close or release the child buffers here too.
3446 // Save dirty buffers also if closing_!
3447 if (saveBufferIfNeeded(*child_buf, false)) {
3448 child_buf->removeAutosaveFile();
3449 theBufferList().release(child_buf);
3451 // Saving of dirty children has been cancelled.
3452 // Cancel the whole process.
3459 // goto bookmark to update bookmark pit.
3460 // FIXME: we should update only the bookmarks related to this buffer!
3461 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3462 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3463 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3464 guiApp->gotoBookmark(i, false, false);
3466 if (saveBufferIfNeeded(buf, false)) {
3467 buf.removeAutosaveFile();
3468 theBufferList().release(&buf);
3472 // open all children again to avoid a crash because of dangling
3473 // pointers (bug 6603)
3479 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3481 while (twa == d.currentTabWorkArea()) {
3482 twa->setCurrentIndex(twa->count() - 1);
3484 GuiWorkArea * wa = twa->currentWorkArea();
3485 Buffer & b = wa->bufferView().buffer();
3487 // We only want to close the buffer if the same buffer is not visible
3488 // in another view, and if this is not a child and if we are closing
3489 // a view (not a tabgroup).
3490 bool const close_buffer =
3491 !inOtherView(b) && !b.parent() && closing_;
3493 if (!closeWorkArea(wa, close_buffer))
3500 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3502 if (buf.isClean() || buf.paragraphs().empty())
3505 // Switch to this Buffer.
3511 if (buf.isUnnamed()) {
3512 file = from_utf8(buf.fileName().onlyFileName());
3515 FileName filename = buf.fileName();
3517 file = filename.displayName(30);
3518 exists = filename.exists();
3521 // Bring this window to top before asking questions.
3526 if (hiding && buf.isUnnamed()) {
3527 docstring const text = bformat(_("The document %1$s has not been "
3528 "saved yet.\n\nDo you want to save "
3529 "the document?"), file);
3530 ret = Alert::prompt(_("Save new document?"),
3531 text, 0, 1, _("&Save"), _("&Cancel"));
3535 docstring const text = exists ?
3536 bformat(_("The document %1$s has unsaved changes."
3537 "\n\nDo you want to save the document or "
3538 "discard the changes?"), file) :
3539 bformat(_("The document %1$s has not been saved yet."
3540 "\n\nDo you want to save the document or "
3541 "discard it entirely?"), file);
3542 docstring const title = exists ?
3543 _("Save changed document?") : _("Save document?");
3544 ret = Alert::prompt(title, text, 0, 2,
3545 _("&Save"), _("&Discard"), _("&Cancel"));
3550 if (!saveBuffer(buf))
3554 // If we crash after this we could have no autosave file
3555 // but I guess this is really improbable (Jug).
3556 // Sometimes improbable things happen:
3557 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3558 // buf.removeAutosaveFile();
3560 // revert all changes
3571 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3573 Buffer & buf = wa->bufferView().buffer();
3575 for (int i = 0; i != d.splitter_->count(); ++i) {
3576 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3577 if (wa_ && wa_ != wa)
3580 return inOtherView(buf);
3584 bool GuiView::inOtherView(Buffer & buf)
3586 QList<int> const ids = guiApp->viewIds();
3588 for (int i = 0; i != ids.size(); ++i) {
3592 if (guiApp->view(ids[i]).workArea(buf))
3599 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3601 if (!documentBufferView())
3604 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3605 Buffer * const curbuf = &documentBufferView()->buffer();
3606 int nwa = twa->count();
3607 for (int i = 0; i < nwa; ++i) {
3608 if (&workArea(i)->bufferView().buffer() == curbuf) {
3610 if (np == NEXTBUFFER)
3611 next_index = (i == nwa - 1 ? 0 : i + 1);
3613 next_index = (i == 0 ? nwa - 1 : i - 1);
3615 twa->moveTab(i, next_index);
3617 setBuffer(&workArea(next_index)->bufferView().buffer());
3625 /// make sure the document is saved
3626 static bool ensureBufferClean(Buffer * buffer)
3628 LASSERT(buffer, return false);
3629 if (buffer->isClean() && !buffer->isUnnamed())
3632 docstring const file = buffer->fileName().displayName(30);
3635 if (!buffer->isUnnamed()) {
3636 text = bformat(_("The document %1$s has unsaved "
3637 "changes.\n\nDo you want to save "
3638 "the document?"), file);
3639 title = _("Save changed document?");
3642 text = bformat(_("The document %1$s has not been "
3643 "saved yet.\n\nDo you want to save "
3644 "the document?"), file);
3645 title = _("Save new document?");
3647 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3650 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3652 return buffer->isClean() && !buffer->isUnnamed();
3656 bool GuiView::reloadBuffer(Buffer & buf)
3658 currentBufferView()->cursor().reset();
3659 Buffer::ReadStatus status = buf.reload();
3660 return status == Buffer::ReadSuccess;
3664 void GuiView::checkExternallyModifiedBuffers()
3666 for (Buffer * buf : theBufferList()) {
3667 if (buf->fileName().exists() && buf->isChecksumModified()) {
3668 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3669 " Reload now? Any local changes will be lost."),
3670 from_utf8(buf->absFileName()));
3671 int const ret = Alert::prompt(_("Reload externally changed document?"),
3672 text, 0, 1, _("&Reload"), _("&Cancel"));
3680 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3682 Buffer * buffer = documentBufferView()
3683 ? &(documentBufferView()->buffer()) : nullptr;
3685 switch (cmd.action()) {
3686 case LFUN_VC_REGISTER:
3687 if (!buffer || !ensureBufferClean(buffer))
3689 if (!buffer->lyxvc().inUse()) {
3690 if (buffer->lyxvc().registrer()) {
3691 reloadBuffer(*buffer);
3692 dr.clearMessageUpdate();
3697 case LFUN_VC_RENAME:
3698 case LFUN_VC_COPY: {
3699 if (!buffer || !ensureBufferClean(buffer))
3701 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3702 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3703 // Some changes are not yet committed.
3704 // We test here and not in getStatus(), since
3705 // this test is expensive.
3707 LyXVC::CommandResult ret =
3708 buffer->lyxvc().checkIn(log);
3710 if (ret == LyXVC::ErrorCommand ||
3711 ret == LyXVC::VCSuccess)
3712 reloadBuffer(*buffer);
3713 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3714 frontend::Alert::error(
3715 _("Revision control error."),
3716 _("Document could not be checked in."));
3720 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3721 LV_VC_RENAME : LV_VC_COPY;
3722 renameBuffer(*buffer, cmd.argument(), kind);
3727 case LFUN_VC_CHECK_IN:
3728 if (!buffer || !ensureBufferClean(buffer))
3730 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3732 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3734 // Only skip reloading if the checkin was cancelled or
3735 // an error occurred before the real checkin VCS command
3736 // was executed, since the VCS might have changed the
3737 // file even if it could not checkin successfully.
3738 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3739 reloadBuffer(*buffer);
3743 case LFUN_VC_CHECK_OUT:
3744 if (!buffer || !ensureBufferClean(buffer))
3746 if (buffer->lyxvc().inUse()) {
3747 dr.setMessage(buffer->lyxvc().checkOut());
3748 reloadBuffer(*buffer);
3752 case LFUN_VC_LOCKING_TOGGLE:
3753 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3755 if (buffer->lyxvc().inUse()) {
3756 string res = buffer->lyxvc().lockingToggle();
3758 frontend::Alert::error(_("Revision control error."),
3759 _("Error when setting the locking property."));
3762 reloadBuffer(*buffer);
3767 case LFUN_VC_REVERT:
3770 if (buffer->lyxvc().revert()) {
3771 reloadBuffer(*buffer);
3772 dr.clearMessageUpdate();
3776 case LFUN_VC_UNDO_LAST:
3779 buffer->lyxvc().undoLast();
3780 reloadBuffer(*buffer);
3781 dr.clearMessageUpdate();
3784 case LFUN_VC_REPO_UPDATE:
3787 if (ensureBufferClean(buffer)) {
3788 dr.setMessage(buffer->lyxvc().repoUpdate());
3789 checkExternallyModifiedBuffers();
3793 case LFUN_VC_COMMAND: {
3794 string flag = cmd.getArg(0);
3795 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3798 if (contains(flag, 'M')) {
3799 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3802 string path = cmd.getArg(1);
3803 if (contains(path, "$$p") && buffer)
3804 path = subst(path, "$$p", buffer->filePath());
3805 LYXERR(Debug::LYXVC, "Directory: " << path);
3807 if (!pp.isReadableDirectory()) {
3808 lyxerr << _("Directory is not accessible.") << endl;
3811 support::PathChanger p(pp);
3813 string command = cmd.getArg(2);
3814 if (command.empty())
3817 command = subst(command, "$$i", buffer->absFileName());
3818 command = subst(command, "$$p", buffer->filePath());
3820 command = subst(command, "$$m", to_utf8(message));
3821 LYXERR(Debug::LYXVC, "Command: " << command);
3823 one.startscript(Systemcall::Wait, command);
3827 if (contains(flag, 'I'))
3828 buffer->markDirty();
3829 if (contains(flag, 'R'))
3830 reloadBuffer(*buffer);
3835 case LFUN_VC_COMPARE: {
3836 if (cmd.argument().empty()) {
3837 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3843 string rev1 = cmd.getArg(0);
3847 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3850 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3851 f2 = buffer->absFileName();
3853 string rev2 = cmd.getArg(1);
3857 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3861 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3862 f1 << "\n" << f2 << "\n" );
3863 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3864 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3874 void GuiView::openChildDocument(string const & fname)
3876 LASSERT(documentBufferView(), return);
3877 Buffer & buffer = documentBufferView()->buffer();
3878 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3879 documentBufferView()->saveBookmark(false);
3880 Buffer * child = nullptr;
3881 if (theBufferList().exists(filename)) {
3882 child = theBufferList().getBuffer(filename);
3885 message(bformat(_("Opening child document %1$s..."),
3886 makeDisplayPath(filename.absFileName())));
3887 child = loadDocument(filename, false);
3889 // Set the parent name of the child document.
3890 // This makes insertion of citations and references in the child work,
3891 // when the target is in the parent or another child document.
3893 child->setParent(&buffer);
3897 bool GuiView::goToFileRow(string const & argument)
3901 size_t i = argument.find_last_of(' ');
3902 if (i != string::npos) {
3903 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3904 istringstream is(argument.substr(i + 1));
3909 if (i == string::npos) {
3910 LYXERR0("Wrong argument: " << argument);
3913 Buffer * buf = nullptr;
3914 string const realtmp = package().temp_dir().realPath();
3915 // We have to use os::path_prefix_is() here, instead of
3916 // simply prefixIs(), because the file name comes from
3917 // an external application and may need case adjustment.
3918 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3919 buf = theBufferList().getBufferFromTmp(file_name, true);
3920 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3921 << (buf ? " success" : " failed"));
3923 // Must replace extension of the file to be .lyx
3924 // and get full path
3925 FileName const s = fileSearch(string(),
3926 support::changeExtension(file_name, ".lyx"), "lyx");
3927 // Either change buffer or load the file
3928 if (theBufferList().exists(s))
3929 buf = theBufferList().getBuffer(s);
3930 else if (s.exists()) {
3931 buf = loadDocument(s);
3936 _("File does not exist: %1$s"),
3937 makeDisplayPath(file_name)));
3943 _("No buffer for file: %1$s."),
3944 makeDisplayPath(file_name))
3949 bool success = documentBufferView()->setCursorFromRow(row);
3951 LYXERR(Debug::LATEX,
3952 "setCursorFromRow: invalid position for row " << row);
3953 frontend::Alert::error(_("Inverse Search Failed"),
3954 _("Invalid position requested by inverse search.\n"
3955 "You may need to update the viewed document."));
3961 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3963 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3964 menu->exec(QCursor::pos());
3969 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3970 Buffer const * orig, Buffer * clone, string const & format)
3972 Buffer::ExportStatus const status = func(format);
3974 // the cloning operation will have produced a clone of the entire set of
3975 // documents, starting from the master. so we must delete those.
3976 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3978 busyBuffers.remove(orig);
3983 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3984 Buffer const * orig, Buffer * clone, string const & format)
3986 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3988 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3992 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3993 Buffer const * orig, Buffer * clone, string const & format)
3995 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3997 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4001 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4002 Buffer const * orig, Buffer * clone, string const & format)
4004 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4006 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4010 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4011 Buffer const * used_buffer,
4012 docstring const & msg,
4013 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4014 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4015 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4016 bool allow_async, bool use_tmpdir)
4021 string format = argument;
4023 format = used_buffer->params().getDefaultOutputFormat();
4024 processing_format = format;
4026 progress_->clearMessages();
4029 #if EXPORT_in_THREAD
4031 GuiViewPrivate::busyBuffers.insert(used_buffer);
4032 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4033 if (!cloned_buffer) {
4034 Alert::error(_("Export Error"),
4035 _("Error cloning the Buffer."));
4038 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4043 setPreviewFuture(f);
4044 last_export_format = used_buffer->params().bufferFormat();
4047 // We are asynchronous, so we don't know here anything about the success
4050 Buffer::ExportStatus status;
4052 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4053 } else if (previewFunc) {
4054 status = (used_buffer->*previewFunc)(format);
4057 handleExportStatus(gv_, status, format);
4059 return (status == Buffer::ExportSuccess
4060 || status == Buffer::PreviewSuccess);
4064 Buffer::ExportStatus status;
4066 status = (used_buffer->*syncFunc)(format, true);
4067 } else if (previewFunc) {
4068 status = (used_buffer->*previewFunc)(format);
4071 handleExportStatus(gv_, status, format);
4073 return (status == Buffer::ExportSuccess
4074 || status == Buffer::PreviewSuccess);
4078 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4080 BufferView * bv = currentBufferView();
4081 LASSERT(bv, return);
4083 // Let the current BufferView dispatch its own actions.
4084 bv->dispatch(cmd, dr);
4085 if (dr.dispatched()) {
4086 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4087 updateDialog("document", "");
4091 // Try with the document BufferView dispatch if any.
4092 BufferView * doc_bv = documentBufferView();
4093 if (doc_bv && doc_bv != bv) {
4094 doc_bv->dispatch(cmd, dr);
4095 if (dr.dispatched()) {
4096 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4097 updateDialog("document", "");
4102 // Then let the current Cursor dispatch its own actions.
4103 bv->cursor().dispatch(cmd);
4105 // update completion. We do it here and not in
4106 // processKeySym to avoid another redraw just for a
4107 // changed inline completion
4108 if (cmd.origin() == FuncRequest::KEYBOARD) {
4109 if (cmd.action() == LFUN_SELF_INSERT
4110 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4111 updateCompletion(bv->cursor(), true, true);
4112 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4113 updateCompletion(bv->cursor(), false, true);
4115 updateCompletion(bv->cursor(), false, false);
4118 dr = bv->cursor().result();
4122 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4124 BufferView * bv = currentBufferView();
4125 // By default we won't need any update.
4126 dr.screenUpdate(Update::None);
4127 // assume cmd will be dispatched
4128 dr.dispatched(true);
4130 Buffer * doc_buffer = documentBufferView()
4131 ? &(documentBufferView()->buffer()) : nullptr;
4133 if (cmd.origin() == FuncRequest::TOC) {
4134 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4135 toc->doDispatch(bv->cursor(), cmd, dr);
4139 string const argument = to_utf8(cmd.argument());
4141 switch(cmd.action()) {
4142 case LFUN_BUFFER_CHILD_OPEN:
4143 openChildDocument(to_utf8(cmd.argument()));
4146 case LFUN_BUFFER_IMPORT:
4147 importDocument(to_utf8(cmd.argument()));
4150 case LFUN_MASTER_BUFFER_EXPORT:
4152 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4154 case LFUN_BUFFER_EXPORT: {
4157 // GCC only sees strfwd.h when building merged
4158 if (::lyx::operator==(cmd.argument(), "custom")) {
4159 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4160 // so the following test should not be needed.
4161 // In principle, we could try to switch to such a view...
4162 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4163 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4167 string const dest = cmd.getArg(1);
4168 FileName target_dir;
4169 if (!dest.empty() && FileName::isAbsolute(dest))
4170 target_dir = FileName(support::onlyPath(dest));
4172 target_dir = doc_buffer->fileName().onlyPath();
4174 string const format = (argument.empty() || argument == "default") ?
4175 doc_buffer->params().getDefaultOutputFormat() : argument;
4177 if ((dest.empty() && doc_buffer->isUnnamed())
4178 || !target_dir.isDirWritable()) {
4179 exportBufferAs(*doc_buffer, from_utf8(format));
4182 /* TODO/Review: Is it a problem to also export the children?
4183 See the update_unincluded flag */
4184 d.asyncBufferProcessing(format,
4187 &GuiViewPrivate::exportAndDestroy,
4189 nullptr, cmd.allowAsync());
4190 // TODO Inform user about success
4194 case LFUN_BUFFER_EXPORT_AS: {
4195 LASSERT(doc_buffer, break);
4196 docstring f = cmd.argument();
4198 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4199 exportBufferAs(*doc_buffer, f);
4203 case LFUN_BUFFER_UPDATE: {
4204 d.asyncBufferProcessing(argument,
4207 &GuiViewPrivate::compileAndDestroy,
4209 nullptr, cmd.allowAsync(), true);
4212 case LFUN_BUFFER_VIEW: {
4213 d.asyncBufferProcessing(argument,
4215 _("Previewing ..."),
4216 &GuiViewPrivate::previewAndDestroy,
4218 &Buffer::preview, cmd.allowAsync());
4221 case LFUN_MASTER_BUFFER_UPDATE: {
4222 d.asyncBufferProcessing(argument,
4223 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4225 &GuiViewPrivate::compileAndDestroy,
4227 nullptr, cmd.allowAsync(), true);
4230 case LFUN_MASTER_BUFFER_VIEW: {
4231 d.asyncBufferProcessing(argument,
4232 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4234 &GuiViewPrivate::previewAndDestroy,
4235 nullptr, &Buffer::preview, cmd.allowAsync());
4238 case LFUN_EXPORT_CANCEL: {
4239 Systemcall::killscript();
4242 case LFUN_BUFFER_SWITCH: {
4243 string const file_name = to_utf8(cmd.argument());
4244 if (!FileName::isAbsolute(file_name)) {
4246 dr.setMessage(_("Absolute filename expected."));
4250 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4253 dr.setMessage(_("Document not loaded"));
4257 // Do we open or switch to the buffer in this view ?
4258 if (workArea(*buffer)
4259 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4264 // Look for the buffer in other views
4265 QList<int> const ids = guiApp->viewIds();
4267 for (; i != ids.size(); ++i) {
4268 GuiView & gv = guiApp->view(ids[i]);
4269 if (gv.workArea(*buffer)) {
4271 gv.activateWindow();
4273 gv.setBuffer(buffer);
4278 // If necessary, open a new window as a last resort
4279 if (i == ids.size()) {
4280 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4286 case LFUN_BUFFER_NEXT:
4287 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4290 case LFUN_BUFFER_MOVE_NEXT:
4291 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4294 case LFUN_BUFFER_PREVIOUS:
4295 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4298 case LFUN_BUFFER_MOVE_PREVIOUS:
4299 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4302 case LFUN_BUFFER_CHKTEX:
4303 LASSERT(doc_buffer, break);
4304 doc_buffer->runChktex();
4307 case LFUN_COMMAND_EXECUTE: {
4308 command_execute_ = true;
4309 minibuffer_focus_ = true;
4312 case LFUN_DROP_LAYOUTS_CHOICE:
4313 d.layout_->showPopup();
4316 case LFUN_MENU_OPEN:
4317 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4318 menu->exec(QCursor::pos());
4321 case LFUN_FILE_INSERT: {
4322 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4323 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4324 dr.forceBufferUpdate();
4325 dr.screenUpdate(Update::Force);
4330 case LFUN_FILE_INSERT_PLAINTEXT:
4331 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4332 string const fname = to_utf8(cmd.argument());
4333 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4334 dr.setMessage(_("Absolute filename expected."));
4338 FileName filename(fname);
4339 if (fname.empty()) {
4340 FileDialog dlg(qt_("Select file to insert"));
4342 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4343 QStringList(qt_("All Files (*)")));
4345 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4346 dr.setMessage(_("Canceled."));
4350 filename.set(fromqstr(result.second));
4354 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4355 bv->dispatch(new_cmd, dr);
4360 case LFUN_BUFFER_RELOAD: {
4361 LASSERT(doc_buffer, break);
4364 bool drop = (cmd.argument() == "dump");
4367 if (!drop && !doc_buffer->isClean()) {
4368 docstring const file =
4369 makeDisplayPath(doc_buffer->absFileName(), 20);
4370 if (doc_buffer->notifiesExternalModification()) {
4371 docstring text = _("The current version will be lost. "
4372 "Are you sure you want to load the version on disk "
4373 "of the document %1$s?");
4374 ret = Alert::prompt(_("Reload saved document?"),
4375 bformat(text, file), 1, 1,
4376 _("&Reload"), _("&Cancel"));
4378 docstring text = _("Any changes will be lost. "
4379 "Are you sure you want to revert to the saved version "
4380 "of the document %1$s?");
4381 ret = Alert::prompt(_("Revert to saved document?"),
4382 bformat(text, file), 1, 1,
4383 _("&Revert"), _("&Cancel"));
4388 doc_buffer->markClean();
4389 reloadBuffer(*doc_buffer);
4390 dr.forceBufferUpdate();
4395 case LFUN_BUFFER_RESET_EXPORT:
4396 LASSERT(doc_buffer, break);
4397 doc_buffer->requireFreshStart(true);
4398 dr.setMessage(_("Buffer export reset."));
4401 case LFUN_BUFFER_WRITE:
4402 LASSERT(doc_buffer, break);
4403 saveBuffer(*doc_buffer);
4406 case LFUN_BUFFER_WRITE_AS:
4407 LASSERT(doc_buffer, break);
4408 renameBuffer(*doc_buffer, cmd.argument());
4411 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4412 LASSERT(doc_buffer, break);
4413 renameBuffer(*doc_buffer, cmd.argument(),
4414 LV_WRITE_AS_TEMPLATE);
4417 case LFUN_BUFFER_WRITE_ALL: {
4418 Buffer * first = theBufferList().first();
4421 message(_("Saving all documents..."));
4422 // We cannot use a for loop as the buffer list cycles.
4425 if (!b->isClean()) {
4427 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4429 b = theBufferList().next(b);
4430 } while (b != first);
4431 dr.setMessage(_("All documents saved."));
4435 case LFUN_MASTER_BUFFER_FORALL: {
4439 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4440 funcToRun.allowAsync(false);
4442 for (Buffer const * buf : doc_buffer->allRelatives()) {
4443 // Switch to other buffer view and resend cmd
4444 lyx::dispatch(FuncRequest(
4445 LFUN_BUFFER_SWITCH, buf->absFileName()));
4446 lyx::dispatch(funcToRun);
4449 lyx::dispatch(FuncRequest(
4450 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4454 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4455 LASSERT(doc_buffer, break);
4456 doc_buffer->clearExternalModification();
4459 case LFUN_BUFFER_CLOSE:
4463 case LFUN_BUFFER_CLOSE_ALL:
4467 case LFUN_DEVEL_MODE_TOGGLE:
4468 devel_mode_ = !devel_mode_;
4470 dr.setMessage(_("Developer mode is now enabled."));
4472 dr.setMessage(_("Developer mode is now disabled."));
4475 case LFUN_TOOLBAR_SET: {
4476 string const name = cmd.getArg(0);
4477 string const state = cmd.getArg(1);
4478 if (GuiToolbar * t = toolbar(name))
4483 case LFUN_TOOLBAR_TOGGLE: {
4484 string const name = cmd.getArg(0);
4485 if (GuiToolbar * t = toolbar(name))
4490 case LFUN_TOOLBAR_MOVABLE: {
4491 string const name = cmd.getArg(0);
4493 // toggle (all) toolbars movablility
4494 toolbarsMovable_ = !toolbarsMovable_;
4495 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4496 GuiToolbar * tb = toolbar(ti.name);
4497 if (tb && tb->isMovable() != toolbarsMovable_)
4498 // toggle toolbar movablity if it does not fit lock
4499 // (all) toolbars positions state silent = true, since
4500 // status bar notifications are slow
4503 if (toolbarsMovable_)
4504 dr.setMessage(_("Toolbars unlocked."));
4506 dr.setMessage(_("Toolbars locked."));
4507 } else if (GuiToolbar * tb = toolbar(name))
4508 // toggle current toolbar movablity
4510 // update lock (all) toolbars positions
4511 updateLockToolbars();
4515 case LFUN_ICON_SIZE: {
4516 QSize size = d.iconSize(cmd.argument());
4518 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4519 size.width(), size.height()));
4523 case LFUN_DIALOG_UPDATE: {
4524 string const name = to_utf8(cmd.argument());
4525 if (name == "prefs" || name == "document")
4526 updateDialog(name, string());
4527 else if (name == "paragraph")
4528 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4529 else if (currentBufferView()) {
4530 Inset * inset = currentBufferView()->editedInset(name);
4531 // Can only update a dialog connected to an existing inset
4533 // FIXME: get rid of this indirection; GuiView ask the inset
4534 // if he is kind enough to update itself...
4535 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4536 //FIXME: pass DispatchResult here?
4537 inset->dispatch(currentBufferView()->cursor(), fr);
4543 case LFUN_DIALOG_TOGGLE: {
4544 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4545 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4546 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4550 case LFUN_DIALOG_DISCONNECT_INSET:
4551 disconnectDialog(to_utf8(cmd.argument()));
4554 case LFUN_DIALOG_HIDE: {
4555 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4559 case LFUN_DIALOG_SHOW: {
4560 string const name = cmd.getArg(0);
4561 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4563 if (name == "latexlog") {
4564 // getStatus checks that
4565 LASSERT(doc_buffer, break);
4566 Buffer::LogType type;
4567 string const logfile = doc_buffer->logName(&type);
4569 case Buffer::latexlog:
4572 case Buffer::buildlog:
4573 sdata = "literate ";
4576 sdata += Lexer::quoteString(logfile);
4577 showDialog("log", sdata);
4578 } else if (name == "vclog") {
4579 // getStatus checks that
4580 LASSERT(doc_buffer, break);
4581 string const sdata2 = "vc " +
4582 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4583 showDialog("log", sdata2);
4584 } else if (name == "symbols") {
4585 sdata = bv->cursor().getEncoding()->name();
4587 showDialog("symbols", sdata);
4588 } else if (name == "findreplace") {
4589 sdata = to_utf8(bv->cursor().selectionAsString(false));
4590 showDialog(name, sdata);
4592 } else if (name == "prefs" && isFullScreen()) {
4593 lfunUiToggle("fullscreen");
4594 showDialog("prefs", sdata);
4596 showDialog(name, sdata);
4601 dr.setMessage(cmd.argument());
4604 case LFUN_UI_TOGGLE: {
4605 string arg = cmd.getArg(0);
4606 if (!lfunUiToggle(arg)) {
4607 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4608 dr.setMessage(bformat(msg, from_utf8(arg)));
4610 // Make sure the keyboard focus stays in the work area.
4615 case LFUN_VIEW_SPLIT: {
4616 LASSERT(doc_buffer, break);
4617 string const orientation = cmd.getArg(0);
4618 d.splitter_->setOrientation(orientation == "vertical"
4619 ? Qt::Vertical : Qt::Horizontal);
4620 TabWorkArea * twa = addTabWorkArea();
4621 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4622 setCurrentWorkArea(wa);
4625 case LFUN_TAB_GROUP_CLOSE:
4626 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4627 closeTabWorkArea(twa);
4628 d.current_work_area_ = nullptr;
4629 twa = d.currentTabWorkArea();
4630 // Switch to the next GuiWorkArea in the found TabWorkArea.
4632 // Make sure the work area is up to date.
4633 setCurrentWorkArea(twa->currentWorkArea());
4635 setCurrentWorkArea(nullptr);
4640 case LFUN_VIEW_CLOSE:
4641 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4642 closeWorkArea(twa->currentWorkArea());
4643 d.current_work_area_ = nullptr;
4644 twa = d.currentTabWorkArea();
4645 // Switch to the next GuiWorkArea in the found TabWorkArea.
4647 // Make sure the work area is up to date.
4648 setCurrentWorkArea(twa->currentWorkArea());
4650 setCurrentWorkArea(nullptr);
4655 case LFUN_COMPLETION_INLINE:
4656 if (d.current_work_area_)
4657 d.current_work_area_->completer().showInline();
4660 case LFUN_COMPLETION_POPUP:
4661 if (d.current_work_area_)
4662 d.current_work_area_->completer().showPopup();
4667 if (d.current_work_area_)
4668 d.current_work_area_->completer().tab();
4671 case LFUN_COMPLETION_CANCEL:
4672 if (d.current_work_area_) {
4673 if (d.current_work_area_->completer().popupVisible())
4674 d.current_work_area_->completer().hidePopup();
4676 d.current_work_area_->completer().hideInline();
4680 case LFUN_COMPLETION_ACCEPT:
4681 if (d.current_work_area_)
4682 d.current_work_area_->completer().activate();
4685 case LFUN_BUFFER_ZOOM_IN:
4686 case LFUN_BUFFER_ZOOM_OUT:
4687 case LFUN_BUFFER_ZOOM: {
4688 if (cmd.argument().empty()) {
4689 if (cmd.action() == LFUN_BUFFER_ZOOM)
4691 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4696 if (cmd.action() == LFUN_BUFFER_ZOOM)
4697 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4698 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4699 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4701 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4704 // Actual zoom value: default zoom + fractional extra value
4705 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4706 if (zoom < static_cast<int>(zoom_min_))
4709 setCurrentZoom(zoom);
4711 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4712 lyxrc.currentZoom, lyxrc.defaultZoom));
4714 guiApp->fontLoader().update();
4715 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4719 case LFUN_VC_REGISTER:
4720 case LFUN_VC_RENAME:
4722 case LFUN_VC_CHECK_IN:
4723 case LFUN_VC_CHECK_OUT:
4724 case LFUN_VC_REPO_UPDATE:
4725 case LFUN_VC_LOCKING_TOGGLE:
4726 case LFUN_VC_REVERT:
4727 case LFUN_VC_UNDO_LAST:
4728 case LFUN_VC_COMMAND:
4729 case LFUN_VC_COMPARE:
4730 dispatchVC(cmd, dr);
4733 case LFUN_SERVER_GOTO_FILE_ROW:
4734 if(goToFileRow(to_utf8(cmd.argument())))
4735 dr.screenUpdate(Update::Force | Update::FitCursor);
4738 case LFUN_LYX_ACTIVATE:
4742 case LFUN_WINDOW_RAISE:
4748 case LFUN_FORWARD_SEARCH: {
4749 // it seems safe to assume we have a document buffer, since
4750 // getStatus wants one.
4751 LASSERT(doc_buffer, break);
4752 Buffer const * doc_master = doc_buffer->masterBuffer();
4753 FileName const path(doc_master->temppath());
4754 string const texname = doc_master->isChild(doc_buffer)
4755 ? DocFileName(changeExtension(
4756 doc_buffer->absFileName(),
4757 "tex")).mangledFileName()
4758 : doc_buffer->latexName();
4759 string const fulltexname =
4760 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4761 string const mastername =
4762 removeExtension(doc_master->latexName());
4763 FileName const dviname(addName(path.absFileName(),
4764 addExtension(mastername, "dvi")));
4765 FileName const pdfname(addName(path.absFileName(),
4766 addExtension(mastername, "pdf")));
4767 bool const have_dvi = dviname.exists();
4768 bool const have_pdf = pdfname.exists();
4769 if (!have_dvi && !have_pdf) {
4770 dr.setMessage(_("Please, preview the document first."));
4773 string outname = dviname.onlyFileName();
4774 string command = lyxrc.forward_search_dvi;
4775 if (!have_dvi || (have_pdf &&
4776 pdfname.lastModified() > dviname.lastModified())) {
4777 outname = pdfname.onlyFileName();
4778 command = lyxrc.forward_search_pdf;
4781 DocIterator cur = bv->cursor();
4782 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4783 LYXERR(Debug::ACTION, "Forward search: row:" << row
4785 if (row == -1 || command.empty()) {
4786 dr.setMessage(_("Couldn't proceed."));
4789 string texrow = convert<string>(row);
4791 command = subst(command, "$$n", texrow);
4792 command = subst(command, "$$f", fulltexname);
4793 command = subst(command, "$$t", texname);
4794 command = subst(command, "$$o", outname);
4796 volatile PathChanger p(path);
4798 one.startscript(Systemcall::DontWait, command);
4802 case LFUN_SPELLING_CONTINUOUSLY:
4803 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4804 dr.screenUpdate(Update::Force);
4807 case LFUN_CITATION_OPEN: {
4809 if (theFormats().getFormat("pdf"))
4810 pdfv = theFormats().getFormat("pdf")->viewer();
4811 if (theFormats().getFormat("ps"))
4812 psv = theFormats().getFormat("ps")->viewer();
4813 frontend::showTarget(argument, pdfv, psv);
4818 // The LFUN must be for one of BufferView, Buffer or Cursor;
4820 dispatchToBufferView(cmd, dr);
4824 // Need to update bv because many LFUNs here might have destroyed it
4825 bv = currentBufferView();
4827 // Clear non-empty selections
4828 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4830 Cursor & cur = bv->cursor();
4831 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4832 cur.clearSelection();
4838 bool GuiView::lfunUiToggle(string const & ui_component)
4840 if (ui_component == "scrollbar") {
4841 // hide() is of no help
4842 if (d.current_work_area_->verticalScrollBarPolicy() ==
4843 Qt::ScrollBarAlwaysOff)
4845 d.current_work_area_->setVerticalScrollBarPolicy(
4846 Qt::ScrollBarAsNeeded);
4848 d.current_work_area_->setVerticalScrollBarPolicy(
4849 Qt::ScrollBarAlwaysOff);
4850 } else if (ui_component == "statusbar") {
4851 statusBar()->setVisible(!statusBar()->isVisible());
4852 } else if (ui_component == "menubar") {
4853 menuBar()->setVisible(!menuBar()->isVisible());
4854 } else if (ui_component == "zoomslider") {
4855 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4856 zoom_in_->setVisible(zoom_slider_->isVisible());
4857 zoom_out_->setVisible(zoom_slider_->isVisible());
4858 } else if (ui_component == "frame") {
4859 int const l = contentsMargins().left();
4861 //are the frames in default state?
4862 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4864 #if QT_VERSION > 0x050903
4865 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4867 setContentsMargins(-2, -2, -2, -2);
4869 #if QT_VERSION > 0x050903
4870 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4872 setContentsMargins(0, 0, 0, 0);
4875 if (ui_component == "fullscreen") {
4883 void GuiView::toggleFullScreen()
4885 setWindowState(windowState() ^ Qt::WindowFullScreen);
4889 Buffer const * GuiView::updateInset(Inset const * inset)
4894 Buffer const * inset_buffer = &(inset->buffer());
4896 for (int i = 0; i != d.splitter_->count(); ++i) {
4897 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4900 Buffer const * buffer = &(wa->bufferView().buffer());
4901 if (inset_buffer == buffer)
4902 wa->scheduleRedraw(true);
4904 return inset_buffer;
4908 void GuiView::restartCaret()
4910 /* When we move around, or type, it's nice to be able to see
4911 * the caret immediately after the keypress.
4913 if (d.current_work_area_)
4914 d.current_work_area_->startBlinkingCaret();
4916 // Take this occasion to update the other GUI elements.
4922 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4924 if (d.current_work_area_)
4925 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4930 // This list should be kept in sync with the list of insets in
4931 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4932 // dialog should have the same name as the inset.
4933 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4934 // docs in LyXAction.cpp.
4936 char const * const dialognames[] = {
4938 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4939 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4940 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4941 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4942 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4943 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4944 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4945 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4947 char const * const * const end_dialognames =
4948 dialognames + (sizeof(dialognames) / sizeof(char *));
4952 cmpCStr(char const * name) : name_(name) {}
4953 bool operator()(char const * other) {
4954 return strcmp(other, name_) == 0;
4961 bool isValidName(string const & name)
4963 return find_if(dialognames, end_dialognames,
4964 cmpCStr(name.c_str())) != end_dialognames;
4970 void GuiView::resetDialogs()
4972 // Make sure that no LFUN uses any GuiView.
4973 guiApp->setCurrentView(nullptr);
4977 constructToolbars();
4978 guiApp->menus().fillMenuBar(menuBar(), this, false);
4979 d.layout_->updateContents(true);
4980 // Now update controls with current buffer.
4981 guiApp->setCurrentView(this);
4987 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4989 for (QObject * child: widget->children()) {
4990 if (child->inherits("QGroupBox")) {
4991 QGroupBox * box = (QGroupBox*) child;
4994 flatGroupBoxes(child, flag);
5000 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5002 if (!isValidName(name))
5005 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5007 if (it != d.dialogs_.end()) {
5009 it->second->hideView();
5010 return it->second.get();
5013 Dialog * dialog = build(name);
5014 d.dialogs_[name].reset(dialog);
5015 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5016 // Force a uniform style for group boxes
5017 // On Mac non-flat works better, on Linux flat is standard
5018 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5020 if (lyxrc.allow_geometry_session)
5021 dialog->restoreSession();
5028 void GuiView::showDialog(string const & name, string const & sdata,
5031 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5035 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5041 const string name = fromqstr(qname);
5042 const string sdata = fromqstr(qdata);
5046 Dialog * dialog = findOrBuild(name, false);
5048 bool const visible = dialog->isVisibleView();
5049 dialog->showData(sdata);
5050 if (currentBufferView())
5051 currentBufferView()->editInset(name, inset);
5052 // We only set the focus to the new dialog if it was not yet
5053 // visible in order not to change the existing previous behaviour
5055 // activateWindow is needed for floating dockviews
5056 dialog->asQWidget()->raise();
5057 dialog->asQWidget()->activateWindow();
5058 if (dialog->wantInitialFocus())
5059 dialog->asQWidget()->setFocus();
5063 catch (ExceptionMessage const &) {
5071 bool GuiView::isDialogVisible(string const & name) const
5073 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5074 if (it == d.dialogs_.end())
5076 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5080 void GuiView::hideDialog(string const & name, Inset * inset)
5082 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5083 if (it == d.dialogs_.end())
5087 if (!currentBufferView())
5089 if (inset != currentBufferView()->editedInset(name))
5093 Dialog * const dialog = it->second.get();
5094 if (dialog->isVisibleView())
5096 if (currentBufferView())
5097 currentBufferView()->editInset(name, nullptr);
5101 void GuiView::disconnectDialog(string const & name)
5103 if (!isValidName(name))
5105 if (currentBufferView())
5106 currentBufferView()->editInset(name, nullptr);
5110 void GuiView::hideAll() const
5112 for(auto const & dlg_p : d.dialogs_)
5113 dlg_p.second->hideView();
5117 void GuiView::updateDialogs()
5119 for(auto const & dlg_p : d.dialogs_) {
5120 Dialog * dialog = dlg_p.second.get();
5122 if (dialog->needBufferOpen() && !documentBufferView())
5123 hideDialog(fromqstr(dialog->name()), nullptr);
5124 else if (dialog->isVisibleView())
5125 dialog->checkStatus();
5133 Dialog * GuiView::build(string const & name)
5135 return createDialog(*this, name);
5139 SEMenu::SEMenu(QWidget * parent)
5141 QAction * action = addAction(qt_("Disable Shell Escape"));
5142 connect(action, SIGNAL(triggered()),
5143 parent, SLOT(disableShellEscape()));
5147 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5149 if (event->button() == Qt::LeftButton) {
5154 } // namespace frontend
5157 #include "moc_GuiView.cpp"