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 currentWorkArea()->scheduleRedraw(true);
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 currentWorkArea()->scheduleRedraw(true);
809 void GuiView::zoomOutPressed()
812 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
813 currentWorkArea()->scheduleRedraw(true);
817 void GuiView::showZoomContextMenu()
819 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
822 menu->exec(QCursor::pos());
826 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
828 QVector<GuiWorkArea*> areas;
829 for (int i = 0; i < tabWorkAreaCount(); i++) {
830 TabWorkArea* ta = tabWorkArea(i);
831 for (int u = 0; u < ta->count(); u++) {
832 areas << ta->workArea(u);
838 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
839 string const & format)
841 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
844 case Buffer::ExportSuccess:
845 msg = bformat(_("Successful export to format: %1$s"), fmt);
847 case Buffer::ExportCancel:
848 msg = _("Document export cancelled.");
850 case Buffer::ExportError:
851 case Buffer::ExportNoPathToFormat:
852 case Buffer::ExportTexPathHasSpaces:
853 case Buffer::ExportConverterError:
854 msg = bformat(_("Error while exporting format: %1$s"), fmt);
856 case Buffer::PreviewSuccess:
857 msg = bformat(_("Successful preview of format: %1$s"), fmt);
859 case Buffer::PreviewError:
860 msg = bformat(_("Error while previewing format: %1$s"), fmt);
862 case Buffer::ExportKilled:
863 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
870 void GuiView::processingThreadStarted()
875 void GuiView::processingThreadFinished()
877 QFutureWatcher<Buffer::ExportStatus> const * watcher =
878 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
880 Buffer::ExportStatus const status = watcher->result();
881 handleExportStatus(this, status, d.processing_format);
884 BufferView const * const bv = currentBufferView();
885 if (bv && !bv->buffer().errorList("Export").empty()) {
890 bool const error = (status != Buffer::ExportSuccess &&
891 status != Buffer::PreviewSuccess &&
892 status != Buffer::ExportCancel);
894 ErrorList & el = bv->buffer().errorList(d.last_export_format);
895 // at this point, we do not know if buffer-view or
896 // master-buffer-view was called. If there was an export error,
897 // and the current buffer's error log is empty, we guess that
898 // it must be master-buffer-view that was called so we set
900 errors(d.last_export_format, el.empty());
905 void GuiView::autoSaveThreadFinished()
907 QFutureWatcher<docstring> const * watcher =
908 static_cast<QFutureWatcher<docstring> const *>(sender());
909 message(watcher->result());
914 void GuiView::saveLayout() const
917 settings.setValue("zoom_ratio", zoom_ratio_);
918 settings.setValue("devel_mode", devel_mode_);
919 settings.beginGroup("views");
920 settings.beginGroup(QString::number(id_));
921 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
922 settings.setValue("pos", pos());
923 settings.setValue("size", size());
925 settings.setValue("geometry", saveGeometry());
926 settings.setValue("layout", saveState(0));
927 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
928 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
932 void GuiView::saveUISettings() const
936 // Save the toolbar private states
937 for (auto const & tb_p : d.toolbars_)
938 tb_p.second->saveSession(settings);
939 // Now take care of all other dialogs
940 for (auto const & dlg_p : d.dialogs_)
941 dlg_p.second->saveSession(settings);
945 void GuiView::setCurrentZoom(const int v)
947 lyxrc.currentZoom = v;
948 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
949 Q_EMIT currentZoomChanged(v);
953 bool GuiView::restoreLayout()
956 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
957 // Actual zoom value: default zoom + fractional offset
958 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
959 if (zoom < static_cast<int>(zoom_min_))
961 setCurrentZoom(zoom);
962 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
963 settings.beginGroup("views");
964 settings.beginGroup(QString::number(id_));
965 QString const icon_key = "icon_size";
966 if (!settings.contains(icon_key))
969 //code below is skipped when when ~/.config/LyX is (re)created
970 setIconSize(d.iconSize(settings.value(icon_key).toString()));
972 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
973 zoom_slider_->setVisible(show_zoom_slider);
974 zoom_in_->setVisible(show_zoom_slider);
975 zoom_out_->setVisible(show_zoom_slider);
977 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
978 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
979 QSize size = settings.value("size", QSize(690, 510)).toSize();
983 // Work-around for bug #6034: the window ends up in an undetermined
984 // state when trying to restore a maximized window when it is
985 // already maximized.
986 if (!(windowState() & Qt::WindowMaximized))
987 if (!restoreGeometry(settings.value("geometry").toByteArray()))
988 setGeometry(50, 50, 690, 510);
991 // Make sure layout is correctly oriented.
992 setLayoutDirection(qApp->layoutDirection());
994 // Allow the toc and view-source dock widget to be restored if needed.
996 if ((dialog = findOrBuild("toc", true)))
997 // see bug 5082. At least setup title and enabled state.
998 // Visibility will be adjusted by restoreState below.
999 dialog->prepareView();
1000 if ((dialog = findOrBuild("view-source", true)))
1001 dialog->prepareView();
1002 if ((dialog = findOrBuild("progress", true)))
1003 dialog->prepareView();
1005 if (!restoreState(settings.value("layout").toByteArray(), 0))
1008 // init the toolbars that have not been restored
1009 for (auto const & tb_p : guiApp->toolbars()) {
1010 GuiToolbar * tb = toolbar(tb_p.name);
1011 if (tb && !tb->isRestored())
1012 initToolbar(tb_p.name);
1015 // update lock (all) toolbars positions
1016 updateLockToolbars();
1023 GuiToolbar * GuiView::toolbar(string const & name)
1025 ToolbarMap::iterator it = d.toolbars_.find(name);
1026 if (it != d.toolbars_.end())
1029 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1034 void GuiView::updateLockToolbars()
1036 toolbarsMovable_ = false;
1037 for (ToolbarInfo const & info : guiApp->toolbars()) {
1038 GuiToolbar * tb = toolbar(info.name);
1039 if (tb && tb->isMovable())
1040 toolbarsMovable_ = true;
1042 #if QT_VERSION >= 0x050200
1043 // set unified mac toolbars only when not movable as recommended:
1044 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1045 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1050 void GuiView::constructToolbars()
1052 for (auto const & tb_p : d.toolbars_)
1054 d.toolbars_.clear();
1056 // I don't like doing this here, but the standard toolbar
1057 // destroys this object when it's destroyed itself (vfr)
1058 d.layout_ = new LayoutBox(*this);
1059 d.stack_widget_->addWidget(d.layout_);
1060 d.layout_->move(0,0);
1062 // extracts the toolbars from the backend
1063 for (ToolbarInfo const & inf : guiApp->toolbars())
1064 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1066 DynamicMenuButton::resetIconCache();
1070 void GuiView::initToolbars()
1072 // extracts the toolbars from the backend
1073 for (ToolbarInfo const & inf : guiApp->toolbars())
1074 initToolbar(inf.name);
1078 void GuiView::initToolbar(string const & name)
1080 GuiToolbar * tb = toolbar(name);
1083 int const visibility = guiApp->toolbars().defaultVisibility(name);
1084 bool newline = !(visibility & Toolbars::SAMEROW);
1085 tb->setVisible(false);
1086 tb->setVisibility(visibility);
1088 if (visibility & Toolbars::TOP) {
1090 addToolBarBreak(Qt::TopToolBarArea);
1091 addToolBar(Qt::TopToolBarArea, tb);
1094 if (visibility & Toolbars::BOTTOM) {
1096 addToolBarBreak(Qt::BottomToolBarArea);
1097 addToolBar(Qt::BottomToolBarArea, tb);
1100 if (visibility & Toolbars::LEFT) {
1102 addToolBarBreak(Qt::LeftToolBarArea);
1103 addToolBar(Qt::LeftToolBarArea, tb);
1106 if (visibility & Toolbars::RIGHT) {
1108 addToolBarBreak(Qt::RightToolBarArea);
1109 addToolBar(Qt::RightToolBarArea, tb);
1112 if (visibility & Toolbars::ON)
1113 tb->setVisible(true);
1115 tb->setMovable(true);
1119 TocModels & GuiView::tocModels()
1121 return d.toc_models_;
1125 void GuiView::setFocus()
1127 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1128 QMainWindow::setFocus();
1132 bool GuiView::hasFocus() const
1134 if (currentWorkArea())
1135 return currentWorkArea()->hasFocus();
1136 if (currentMainWorkArea())
1137 return currentMainWorkArea()->hasFocus();
1138 return d.bg_widget_->hasFocus();
1142 void GuiView::focusInEvent(QFocusEvent * e)
1144 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1145 QMainWindow::focusInEvent(e);
1146 // Make sure guiApp points to the correct view.
1147 guiApp->setCurrentView(this);
1148 if (currentWorkArea())
1149 currentWorkArea()->setFocus();
1150 else if (currentMainWorkArea())
1151 currentMainWorkArea()->setFocus();
1153 d.bg_widget_->setFocus();
1157 void GuiView::showEvent(QShowEvent * e)
1159 LYXERR(Debug::GUI, "Passed Geometry "
1160 << size().height() << "x" << size().width()
1161 << "+" << pos().x() << "+" << pos().y());
1163 if (d.splitter_->count() == 0)
1164 // No work area, switch to the background widget.
1168 QMainWindow::showEvent(e);
1172 bool GuiView::closeScheduled()
1179 bool GuiView::prepareAllBuffersForLogout()
1181 Buffer * first = theBufferList().first();
1185 // First, iterate over all buffers and ask the users if unsaved
1186 // changes should be saved.
1187 // We cannot use a for loop as the buffer list cycles.
1190 if (!saveBufferIfNeeded(*b, false))
1192 b = theBufferList().next(b);
1193 } while (b != first);
1195 // Next, save session state
1196 // When a view/window was closed before without quitting LyX, there
1197 // are already entries in the lastOpened list.
1198 theSession().lastOpened().clear();
1205 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1206 ** is responsibility of the container (e.g., dialog)
1208 void GuiView::closeEvent(QCloseEvent * close_event)
1210 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1212 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1213 Alert::warning(_("Exit LyX"),
1214 _("LyX could not be closed because documents are being processed by LyX."));
1215 close_event->setAccepted(false);
1219 // If the user pressed the x (so we didn't call closeView
1220 // programmatically), we want to clear all existing entries.
1222 theSession().lastOpened().clear();
1227 // it can happen that this event arrives without selecting the view,
1228 // e.g. when clicking the close button on a background window.
1230 if (!closeWorkAreaAll()) {
1232 close_event->ignore();
1236 // Make sure that nothing will use this to be closed View.
1237 guiApp->unregisterView(this);
1239 if (isFullScreen()) {
1240 // Switch off fullscreen before closing.
1245 // Make sure the timer time out will not trigger a statusbar update.
1246 d.statusbar_timer_.stop();
1248 // Saving fullscreen requires additional tweaks in the toolbar code.
1249 // It wouldn't also work under linux natively.
1250 if (lyxrc.allow_geometry_session) {
1255 close_event->accept();
1259 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1261 if (event->mimeData()->hasUrls())
1263 /// \todo Ask lyx-devel is this is enough:
1264 /// if (event->mimeData()->hasFormat("text/plain"))
1265 /// event->acceptProposedAction();
1269 void GuiView::dropEvent(QDropEvent * event)
1271 QList<QUrl> files = event->mimeData()->urls();
1272 if (files.isEmpty())
1275 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1276 for (int i = 0; i != files.size(); ++i) {
1277 string const file = os::internal_path(fromqstr(
1278 files.at(i).toLocalFile()));
1282 string const ext = support::getExtension(file);
1283 vector<const Format *> found_formats;
1285 // Find all formats that have the correct extension.
1286 for (const Format * fmt : theConverters().importableFormats())
1287 if (fmt->hasExtension(ext))
1288 found_formats.push_back(fmt);
1291 if (!found_formats.empty()) {
1292 if (found_formats.size() > 1) {
1293 //FIXME: show a dialog to choose the correct importable format
1294 LYXERR(Debug::FILES,
1295 "Multiple importable formats found, selecting first");
1297 string const arg = found_formats[0]->name() + " " + file;
1298 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1301 //FIXME: do we have to explicitly check whether it's a lyx file?
1302 LYXERR(Debug::FILES,
1303 "No formats found, trying to open it as a lyx file");
1304 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1306 // add the functions to the queue
1307 guiApp->addToFuncRequestQueue(cmd);
1310 // now process the collected functions. We perform the events
1311 // asynchronously. This prevents potential problems in case the
1312 // BufferView is closed within an event.
1313 guiApp->processFuncRequestQueueAsync();
1317 void GuiView::message(docstring const & str)
1319 if (ForkedProcess::iAmAChild())
1322 // call is moved to GUI-thread by GuiProgress
1323 d.progress_->appendMessage(toqstr(str));
1327 void GuiView::clearMessageText()
1329 message(docstring());
1333 void GuiView::updateStatusBarMessage(QString const & str)
1335 statusBar()->showMessage(str);
1336 d.statusbar_timer_.stop();
1337 d.statusbar_timer_.start(3000);
1341 void GuiView::clearMessage()
1343 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1344 // the hasFocus function mostly returns false, even if the focus is on
1345 // a workarea in this view.
1349 d.statusbar_timer_.stop();
1353 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1355 if (wa != d.current_work_area_
1356 || wa->bufferView().buffer().isInternal())
1358 Buffer const & buf = wa->bufferView().buffer();
1359 // Set the windows title
1360 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1361 if (buf.notifiesExternalModification()) {
1362 title = bformat(_("%1$s (modified externally)"), title);
1363 // If the external modification status has changed, then maybe the status of
1364 // buffer-save has changed too.
1368 title += from_ascii(" - LyX");
1370 setWindowTitle(toqstr(title));
1371 // Sets the path for the window: this is used by OSX to
1372 // allow a context click on the title bar showing a menu
1373 // with the path up to the file
1374 setWindowFilePath(toqstr(buf.absFileName()));
1375 // Tell Qt whether the current document is changed
1376 setWindowModified(!buf.isClean());
1378 if (buf.params().shell_escape)
1379 shell_escape_->show();
1381 shell_escape_->hide();
1383 if (buf.hasReadonlyFlag())
1388 if (buf.lyxvc().inUse()) {
1389 version_control_->show();
1390 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1392 version_control_->hide();
1396 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1398 if (d.current_work_area_)
1399 // disconnect the current work area from all slots
1400 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1402 disconnectBufferView();
1403 connectBufferView(wa->bufferView());
1404 connectBuffer(wa->bufferView().buffer());
1405 d.current_work_area_ = wa;
1406 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1407 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1408 QObject::connect(wa, SIGNAL(busy(bool)),
1409 this, SLOT(setBusy(bool)));
1410 // connection of a signal to a signal
1411 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1412 this, SIGNAL(bufferViewChanged()));
1413 Q_EMIT updateWindowTitle(wa);
1414 Q_EMIT bufferViewChanged();
1418 void GuiView::onBufferViewChanged()
1421 // Buffer-dependent dialogs must be updated. This is done here because
1422 // some dialogs require buffer()->text.
1424 zoom_slider_->setEnabled(currentBufferView());
1425 zoom_value_->setEnabled(currentBufferView());
1426 zoom_in_->setEnabled(currentBufferView());
1427 zoom_out_->setEnabled(currentBufferView());
1431 void GuiView::on_lastWorkAreaRemoved()
1434 // We already are in a close event. Nothing more to do.
1437 if (d.splitter_->count() > 1)
1438 // We have a splitter so don't close anything.
1441 // Reset and updates the dialogs.
1442 Q_EMIT bufferViewChanged();
1447 if (lyxrc.open_buffers_in_tabs)
1448 // Nothing more to do, the window should stay open.
1451 if (guiApp->viewIds().size() > 1) {
1457 // On Mac we also close the last window because the application stay
1458 // resident in memory. On other platforms we don't close the last
1459 // window because this would quit the application.
1465 void GuiView::updateStatusBar()
1467 // let the user see the explicit message
1468 if (d.statusbar_timer_.isActive())
1475 void GuiView::showMessage()
1479 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1480 if (msg.isEmpty()) {
1481 BufferView const * bv = currentBufferView();
1483 msg = toqstr(bv->cursor().currentState(devel_mode_));
1485 msg = qt_("Welcome to LyX!");
1487 statusBar()->showMessage(msg);
1491 bool GuiView::event(QEvent * e)
1495 // Useful debug code:
1496 //case QEvent::ActivationChange:
1497 //case QEvent::WindowDeactivate:
1498 //case QEvent::Paint:
1499 //case QEvent::Enter:
1500 //case QEvent::Leave:
1501 //case QEvent::HoverEnter:
1502 //case QEvent::HoverLeave:
1503 //case QEvent::HoverMove:
1504 //case QEvent::StatusTip:
1505 //case QEvent::DragEnter:
1506 //case QEvent::DragLeave:
1507 //case QEvent::Drop:
1510 case QEvent::WindowStateChange: {
1511 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1512 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1513 bool result = QMainWindow::event(e);
1514 bool nfstate = (windowState() & Qt::WindowFullScreen);
1515 if (!ofstate && nfstate) {
1516 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1517 // switch to full-screen state
1518 if (lyxrc.full_screen_statusbar)
1519 statusBar()->hide();
1520 if (lyxrc.full_screen_menubar)
1522 if (lyxrc.full_screen_toolbars) {
1523 for (auto const & tb_p : d.toolbars_)
1524 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1525 tb_p.second->hide();
1527 for (int i = 0; i != d.splitter_->count(); ++i)
1528 d.tabWorkArea(i)->setFullScreen(true);
1529 #if QT_VERSION > 0x050903
1530 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1531 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1533 setContentsMargins(-2, -2, -2, -2);
1535 hideDialogs("prefs", nullptr);
1536 } else if (ofstate && !nfstate) {
1537 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1538 // switch back from full-screen state
1539 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1540 statusBar()->show();
1541 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1543 if (lyxrc.full_screen_toolbars) {
1544 for (auto const & tb_p : d.toolbars_)
1545 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1546 tb_p.second->show();
1549 for (int i = 0; i != d.splitter_->count(); ++i)
1550 d.tabWorkArea(i)->setFullScreen(false);
1551 #if QT_VERSION > 0x050903
1552 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1554 setContentsMargins(0, 0, 0, 0);
1558 case QEvent::WindowActivate: {
1559 GuiView * old_view = guiApp->currentView();
1560 if (this == old_view) {
1562 return QMainWindow::event(e);
1564 if (old_view && old_view->currentBufferView()) {
1565 // save current selection to the selection buffer to allow
1566 // middle-button paste in this window.
1567 cap::saveSelection(old_view->currentBufferView()->cursor());
1569 guiApp->setCurrentView(this);
1570 if (d.current_work_area_)
1571 on_currentWorkAreaChanged(d.current_work_area_);
1575 return QMainWindow::event(e);
1578 case QEvent::ShortcutOverride: {
1580 if (isFullScreen() && menuBar()->isHidden()) {
1581 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1582 // FIXME: we should also try to detect special LyX shortcut such as
1583 // Alt-P and Alt-M. Right now there is a hack in
1584 // GuiWorkArea::processKeySym() that hides again the menubar for
1586 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1588 return QMainWindow::event(e);
1591 return QMainWindow::event(e);
1594 case QEvent::ApplicationPaletteChange: {
1595 // runtime switch from/to dark mode
1597 return QMainWindow::event(e);
1601 return QMainWindow::event(e);
1605 void GuiView::resetWindowTitle()
1607 setWindowTitle(qt_("LyX"));
1610 bool GuiView::focusNextPrevChild(bool /*next*/)
1617 bool GuiView::busy() const
1623 void GuiView::setBusy(bool busy)
1625 bool const busy_before = busy_ > 0;
1626 busy ? ++busy_ : --busy_;
1627 if ((busy_ > 0) == busy_before)
1628 // busy state didn't change
1632 QApplication::setOverrideCursor(Qt::WaitCursor);
1635 QApplication::restoreOverrideCursor();
1640 void GuiView::resetCommandExecute()
1642 command_execute_ = false;
1647 double GuiView::pixelRatio() const
1649 #if QT_VERSION >= 0x050000
1650 return qt_scale_factor * devicePixelRatio();
1657 GuiWorkArea * GuiView::workArea(int index)
1659 if (TabWorkArea * twa = d.currentTabWorkArea())
1660 if (index < twa->count())
1661 return twa->workArea(index);
1666 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1668 if (currentWorkArea()
1669 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1670 return currentWorkArea();
1671 if (TabWorkArea * twa = d.currentTabWorkArea())
1672 return twa->workArea(buffer);
1677 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1679 // Automatically create a TabWorkArea if there are none yet.
1680 TabWorkArea * tab_widget = d.splitter_->count()
1681 ? d.currentTabWorkArea() : addTabWorkArea();
1682 return tab_widget->addWorkArea(buffer, *this);
1686 TabWorkArea * GuiView::addTabWorkArea()
1688 TabWorkArea * twa = new TabWorkArea;
1689 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1690 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1691 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1692 this, SLOT(on_lastWorkAreaRemoved()));
1694 d.splitter_->addWidget(twa);
1695 d.stack_widget_->setCurrentWidget(d.splitter_);
1700 GuiWorkArea const * GuiView::currentWorkArea() const
1702 return d.current_work_area_;
1706 GuiWorkArea * GuiView::currentWorkArea()
1708 return d.current_work_area_;
1712 GuiWorkArea const * GuiView::currentMainWorkArea() const
1714 if (!d.currentTabWorkArea())
1716 return d.currentTabWorkArea()->currentWorkArea();
1720 GuiWorkArea * GuiView::currentMainWorkArea()
1722 if (!d.currentTabWorkArea())
1724 return d.currentTabWorkArea()->currentWorkArea();
1728 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1730 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1732 d.current_work_area_ = nullptr;
1734 Q_EMIT bufferViewChanged();
1738 // FIXME: I've no clue why this is here and why it accesses
1739 // theGuiApp()->currentView, which might be 0 (bug 6464).
1740 // See also 27525 (vfr).
1741 if (theGuiApp()->currentView() == this
1742 && theGuiApp()->currentView()->currentWorkArea() == wa)
1745 if (currentBufferView())
1746 cap::saveSelection(currentBufferView()->cursor());
1748 theGuiApp()->setCurrentView(this);
1749 d.current_work_area_ = wa;
1751 // We need to reset this now, because it will need to be
1752 // right if the tabWorkArea gets reset in the for loop. We
1753 // will change it back if we aren't in that case.
1754 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1755 d.current_main_work_area_ = wa;
1757 for (int i = 0; i != d.splitter_->count(); ++i) {
1758 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1759 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1760 << ", Current main wa: " << currentMainWorkArea());
1765 d.current_main_work_area_ = old_cmwa;
1767 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1768 on_currentWorkAreaChanged(wa);
1769 BufferView & bv = wa->bufferView();
1770 bv.cursor().fixIfBroken();
1772 wa->setUpdatesEnabled(true);
1773 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1777 void GuiView::removeWorkArea(GuiWorkArea * wa)
1779 LASSERT(wa, return);
1780 if (wa == d.current_work_area_) {
1782 disconnectBufferView();
1783 d.current_work_area_ = nullptr;
1784 d.current_main_work_area_ = nullptr;
1787 bool found_twa = false;
1788 for (int i = 0; i != d.splitter_->count(); ++i) {
1789 TabWorkArea * twa = d.tabWorkArea(i);
1790 if (twa->removeWorkArea(wa)) {
1791 // Found in this tab group, and deleted the GuiWorkArea.
1793 if (twa->count() != 0) {
1794 if (d.current_work_area_ == nullptr)
1795 // This means that we are closing the current GuiWorkArea, so
1796 // switch to the next GuiWorkArea in the found TabWorkArea.
1797 setCurrentWorkArea(twa->currentWorkArea());
1799 // No more WorkAreas in this tab group, so delete it.
1806 // It is not a tabbed work area (i.e., the search work area), so it
1807 // should be deleted by other means.
1808 LASSERT(found_twa, return);
1810 if (d.current_work_area_ == nullptr) {
1811 if (d.splitter_->count() != 0) {
1812 TabWorkArea * twa = d.currentTabWorkArea();
1813 setCurrentWorkArea(twa->currentWorkArea());
1815 // No more work areas, switch to the background widget.
1816 setCurrentWorkArea(nullptr);
1822 LayoutBox * GuiView::getLayoutDialog() const
1828 void GuiView::updateLayoutList()
1831 d.layout_->updateContents(false);
1835 void GuiView::updateToolbars()
1837 if (d.current_work_area_) {
1839 if (d.current_work_area_->bufferView().cursor().inMathed()
1840 && !d.current_work_area_->bufferView().cursor().inRegexped())
1841 context |= Toolbars::MATH;
1842 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1843 context |= Toolbars::TABLE;
1844 if (currentBufferView()->buffer().areChangesPresent()
1845 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1846 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1847 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1848 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1849 context |= Toolbars::REVIEW;
1850 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1851 context |= Toolbars::MATHMACROTEMPLATE;
1852 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1853 context |= Toolbars::IPA;
1854 if (command_execute_)
1855 context |= Toolbars::MINIBUFFER;
1856 if (minibuffer_focus_) {
1857 context |= Toolbars::MINIBUFFER_FOCUS;
1858 minibuffer_focus_ = false;
1861 for (auto const & tb_p : d.toolbars_)
1862 tb_p.second->update(context);
1864 for (auto const & tb_p : d.toolbars_)
1865 tb_p.second->update();
1869 void GuiView::refillToolbars()
1871 DynamicMenuButton::resetIconCache();
1872 for (auto const & tb_p : d.toolbars_)
1873 tb_p.second->refill();
1877 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1879 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1880 LASSERT(newBuffer, return);
1882 GuiWorkArea * wa = workArea(*newBuffer);
1883 if (wa == nullptr) {
1885 newBuffer->masterBuffer()->updateBuffer();
1887 wa = addWorkArea(*newBuffer);
1888 // scroll to the position when the BufferView was last closed
1889 if (lyxrc.use_lastfilepos) {
1890 LastFilePosSection::FilePos filepos =
1891 theSession().lastFilePos().load(newBuffer->fileName());
1892 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1895 //Disconnect the old buffer...there's no new one.
1898 connectBuffer(*newBuffer);
1899 connectBufferView(wa->bufferView());
1901 setCurrentWorkArea(wa);
1905 void GuiView::connectBuffer(Buffer & buf)
1907 buf.setGuiDelegate(this);
1911 void GuiView::disconnectBuffer()
1913 if (d.current_work_area_)
1914 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1918 void GuiView::connectBufferView(BufferView & bv)
1920 bv.setGuiDelegate(this);
1924 void GuiView::disconnectBufferView()
1926 if (d.current_work_area_)
1927 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1931 void GuiView::errors(string const & error_type, bool from_master)
1933 BufferView const * const bv = currentBufferView();
1937 ErrorList const & el = from_master ?
1938 bv->buffer().masterBuffer()->errorList(error_type) :
1939 bv->buffer().errorList(error_type);
1944 string err = error_type;
1946 err = "from_master|" + error_type;
1947 showDialog("errorlist", err);
1951 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1953 d.toc_models_.updateItem(toqstr(type), dit);
1957 void GuiView::structureChanged()
1959 // This is called from the Buffer, which has no way to ensure that cursors
1960 // in BufferView remain valid.
1961 if (documentBufferView())
1962 documentBufferView()->cursor().sanitize();
1963 // FIXME: This is slightly expensive, though less than the tocBackend update
1964 // (#9880). This also resets the view in the Toc Widget (#6675).
1965 d.toc_models_.reset(documentBufferView());
1966 // Navigator needs more than a simple update in this case. It needs to be
1968 updateDialog("toc", "");
1972 void GuiView::updateDialog(string const & name, string const & sdata)
1974 if (!isDialogVisible(name))
1977 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1978 if (it == d.dialogs_.end())
1981 Dialog * const dialog = it->second.get();
1982 if (dialog->isVisibleView())
1983 dialog->initialiseParams(sdata);
1987 BufferView * GuiView::documentBufferView()
1989 return currentMainWorkArea()
1990 ? ¤tMainWorkArea()->bufferView()
1995 BufferView const * GuiView::documentBufferView() const
1997 return currentMainWorkArea()
1998 ? ¤tMainWorkArea()->bufferView()
2003 BufferView * GuiView::currentBufferView()
2005 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2009 BufferView const * GuiView::currentBufferView() const
2011 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2015 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2016 Buffer const * orig, Buffer * clone)
2018 bool const success = clone->autoSave();
2020 busyBuffers.remove(orig);
2022 ? _("Automatic save done.")
2023 : _("Automatic save failed!");
2027 void GuiView::autoSave()
2029 LYXERR(Debug::INFO, "Running autoSave()");
2031 Buffer * buffer = documentBufferView()
2032 ? &documentBufferView()->buffer() : nullptr;
2034 resetAutosaveTimers();
2038 GuiViewPrivate::busyBuffers.insert(buffer);
2039 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2040 buffer, buffer->cloneBufferOnly());
2041 d.autosave_watcher_.setFuture(f);
2042 resetAutosaveTimers();
2046 void GuiView::resetAutosaveTimers()
2049 d.autosave_timeout_.restart();
2053 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2056 Buffer * buf = currentBufferView()
2057 ? ¤tBufferView()->buffer() : nullptr;
2058 Buffer * doc_buffer = documentBufferView()
2059 ? &(documentBufferView()->buffer()) : nullptr;
2062 /* In LyX/Mac, when a dialog is open, the menus of the
2063 application can still be accessed without giving focus to
2064 the main window. In this case, we want to disable the menu
2065 entries that are buffer-related.
2066 This code must not be used on Linux and Windows, since it
2067 would disable buffer-related entries when hovering over the
2068 menu (see bug #9574).
2070 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2076 // Check whether we need a buffer
2077 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2078 // no, exit directly
2079 flag.message(from_utf8(N_("Command not allowed with"
2080 "out any document open")));
2081 flag.setEnabled(false);
2085 if (cmd.origin() == FuncRequest::TOC) {
2086 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2087 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2088 flag.setEnabled(false);
2092 switch(cmd.action()) {
2093 case LFUN_BUFFER_IMPORT:
2096 case LFUN_MASTER_BUFFER_EXPORT:
2098 && (doc_buffer->parent() != nullptr
2099 || doc_buffer->hasChildren())
2100 && !d.processing_thread_watcher_.isRunning()
2101 // this launches a dialog, which would be in the wrong Buffer
2102 && !(::lyx::operator==(cmd.argument(), "custom"));
2105 case LFUN_MASTER_BUFFER_UPDATE:
2106 case LFUN_MASTER_BUFFER_VIEW:
2108 && (doc_buffer->parent() != nullptr
2109 || doc_buffer->hasChildren())
2110 && !d.processing_thread_watcher_.isRunning();
2113 case LFUN_BUFFER_UPDATE:
2114 case LFUN_BUFFER_VIEW: {
2115 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2119 string format = to_utf8(cmd.argument());
2120 if (cmd.argument().empty())
2121 format = doc_buffer->params().getDefaultOutputFormat();
2122 enable = doc_buffer->params().isExportable(format, true);
2126 case LFUN_BUFFER_RELOAD:
2127 enable = doc_buffer && !doc_buffer->isUnnamed()
2128 && doc_buffer->fileName().exists()
2129 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2132 case LFUN_BUFFER_RESET_EXPORT:
2133 enable = doc_buffer != nullptr;
2136 case LFUN_BUFFER_CHILD_OPEN:
2137 enable = doc_buffer != nullptr;
2140 case LFUN_MASTER_BUFFER_FORALL: {
2141 if (doc_buffer == nullptr) {
2142 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2146 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2147 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2148 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2153 for (Buffer * buf : doc_buffer->allRelatives()) {
2154 GuiWorkArea * wa = workArea(*buf);
2157 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2158 enable = flag.enabled();
2165 case LFUN_BUFFER_WRITE:
2166 enable = doc_buffer && (doc_buffer->isUnnamed()
2167 || (!doc_buffer->isClean()
2168 || cmd.argument() == "force"));
2171 //FIXME: This LFUN should be moved to GuiApplication.
2172 case LFUN_BUFFER_WRITE_ALL: {
2173 // We enable the command only if there are some modified buffers
2174 Buffer * first = theBufferList().first();
2179 // We cannot use a for loop as the buffer list is a cycle.
2181 if (!b->isClean()) {
2185 b = theBufferList().next(b);
2186 } while (b != first);
2190 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2191 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2194 case LFUN_BUFFER_EXPORT: {
2195 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2199 return doc_buffer->getStatus(cmd, flag);
2202 case LFUN_BUFFER_EXPORT_AS:
2203 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2208 case LFUN_BUFFER_WRITE_AS:
2209 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2210 enable = doc_buffer != nullptr;
2213 case LFUN_EXPORT_CANCEL:
2214 enable = d.processing_thread_watcher_.isRunning();
2217 case LFUN_BUFFER_CLOSE:
2218 case LFUN_VIEW_CLOSE:
2219 enable = doc_buffer != nullptr;
2222 case LFUN_BUFFER_CLOSE_ALL:
2223 enable = theBufferList().last() != theBufferList().first();
2226 case LFUN_BUFFER_CHKTEX: {
2227 // hide if we have no checktex command
2228 if (lyxrc.chktex_command.empty()) {
2229 flag.setUnknown(true);
2233 if (!doc_buffer || !doc_buffer->params().isLatex()
2234 || d.processing_thread_watcher_.isRunning()) {
2235 // grey out, don't hide
2243 case LFUN_VIEW_SPLIT:
2244 if (cmd.getArg(0) == "vertical")
2245 enable = doc_buffer && (d.splitter_->count() == 1 ||
2246 d.splitter_->orientation() == Qt::Vertical);
2248 enable = doc_buffer && (d.splitter_->count() == 1 ||
2249 d.splitter_->orientation() == Qt::Horizontal);
2252 case LFUN_TAB_GROUP_CLOSE:
2253 enable = d.tabWorkAreaCount() > 1;
2256 case LFUN_DEVEL_MODE_TOGGLE:
2257 flag.setOnOff(devel_mode_);
2260 case LFUN_TOOLBAR_SET: {
2261 string const name = cmd.getArg(0);
2262 string const state = cmd.getArg(1);
2263 if (name.empty() || state.empty()) {
2265 docstring const msg =
2266 _("Function toolbar-set requires two arguments!");
2270 if (state != "on" && state != "off" && state != "auto") {
2272 docstring const msg =
2273 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2278 if (GuiToolbar * t = toolbar(name)) {
2279 bool const autovis = t->visibility() & Toolbars::AUTO;
2281 flag.setOnOff(t->isVisible() && !autovis);
2282 else if (state == "off")
2283 flag.setOnOff(!t->isVisible() && !autovis);
2284 else if (state == "auto")
2285 flag.setOnOff(autovis);
2288 docstring const msg =
2289 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2295 case LFUN_TOOLBAR_TOGGLE: {
2296 string const name = cmd.getArg(0);
2297 if (GuiToolbar * t = toolbar(name))
2298 flag.setOnOff(t->isVisible());
2301 docstring const msg =
2302 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2308 case LFUN_TOOLBAR_MOVABLE: {
2309 string const name = cmd.getArg(0);
2310 // use negation since locked == !movable
2312 // toolbar name * locks all toolbars
2313 flag.setOnOff(!toolbarsMovable_);
2314 else if (GuiToolbar * t = toolbar(name))
2315 flag.setOnOff(!(t->isMovable()));
2318 docstring const msg =
2319 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2325 case LFUN_ICON_SIZE:
2326 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2329 case LFUN_DROP_LAYOUTS_CHOICE:
2330 enable = buf != nullptr;
2333 case LFUN_UI_TOGGLE:
2334 if (cmd.argument() == "zoomslider") {
2335 enable = doc_buffer;
2336 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2338 flag.setOnOff(isFullScreen());
2341 case LFUN_DIALOG_DISCONNECT_INSET:
2344 case LFUN_DIALOG_HIDE:
2345 // FIXME: should we check if the dialog is shown?
2348 case LFUN_DIALOG_TOGGLE:
2349 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2352 case LFUN_DIALOG_SHOW: {
2353 string const name = cmd.getArg(0);
2355 enable = name == "aboutlyx"
2356 || name == "file" //FIXME: should be removed.
2357 || name == "lyxfiles"
2359 || name == "texinfo"
2360 || name == "progress"
2361 || name == "compare";
2362 else if (name == "character" || name == "symbols"
2363 || name == "mathdelimiter" || name == "mathmatrix") {
2364 if (!buf || buf->isReadonly())
2367 Cursor const & cur = currentBufferView()->cursor();
2368 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2371 else if (name == "latexlog")
2372 enable = FileName(doc_buffer->logName()).isReadableFile();
2373 else if (name == "spellchecker")
2374 enable = theSpellChecker()
2375 && !doc_buffer->text().empty();
2376 else if (name == "vclog")
2377 enable = doc_buffer->lyxvc().inUse();
2381 case LFUN_DIALOG_UPDATE: {
2382 string const name = cmd.getArg(0);
2384 enable = name == "prefs";
2388 case LFUN_COMMAND_EXECUTE:
2390 case LFUN_MENU_OPEN:
2391 // Nothing to check.
2394 case LFUN_COMPLETION_INLINE:
2395 if (!d.current_work_area_
2396 || !d.current_work_area_->completer().inlinePossible(
2397 currentBufferView()->cursor()))
2401 case LFUN_COMPLETION_POPUP:
2402 if (!d.current_work_area_
2403 || !d.current_work_area_->completer().popupPossible(
2404 currentBufferView()->cursor()))
2409 if (!d.current_work_area_
2410 || !d.current_work_area_->completer().inlinePossible(
2411 currentBufferView()->cursor()))
2415 case LFUN_COMPLETION_ACCEPT:
2416 if (!d.current_work_area_
2417 || (!d.current_work_area_->completer().popupVisible()
2418 && !d.current_work_area_->completer().inlineVisible()
2419 && !d.current_work_area_->completer().completionAvailable()))
2423 case LFUN_COMPLETION_CANCEL:
2424 if (!d.current_work_area_
2425 || (!d.current_work_area_->completer().popupVisible()
2426 && !d.current_work_area_->completer().inlineVisible()))
2430 case LFUN_BUFFER_ZOOM_OUT:
2431 case LFUN_BUFFER_ZOOM_IN: {
2432 // only diff between these two is that the default for ZOOM_OUT
2434 bool const neg_zoom =
2435 convert<int>(cmd.argument()) < 0 ||
2436 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2437 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2438 docstring const msg =
2439 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2443 enable = doc_buffer;
2447 case LFUN_BUFFER_ZOOM: {
2448 bool const less_than_min_zoom =
2449 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2450 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2451 docstring const msg =
2452 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2455 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2458 enable = doc_buffer;
2462 case LFUN_BUFFER_MOVE_NEXT:
2463 case LFUN_BUFFER_MOVE_PREVIOUS:
2464 // we do not cycle when moving
2465 case LFUN_BUFFER_NEXT:
2466 case LFUN_BUFFER_PREVIOUS:
2467 // because we cycle, it doesn't matter whether on first or last
2468 enable = (d.currentTabWorkArea()->count() > 1);
2470 case LFUN_BUFFER_SWITCH:
2471 // toggle on the current buffer, but do not toggle off
2472 // the other ones (is that a good idea?)
2474 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2475 flag.setOnOff(true);
2478 case LFUN_VC_REGISTER:
2479 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2481 case LFUN_VC_RENAME:
2482 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2485 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2487 case LFUN_VC_CHECK_IN:
2488 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2490 case LFUN_VC_CHECK_OUT:
2491 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2493 case LFUN_VC_LOCKING_TOGGLE:
2494 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2495 && doc_buffer->lyxvc().lockingToggleEnabled();
2496 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2498 case LFUN_VC_REVERT:
2499 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2500 && !doc_buffer->hasReadonlyFlag();
2502 case LFUN_VC_UNDO_LAST:
2503 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2505 case LFUN_VC_REPO_UPDATE:
2506 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2508 case LFUN_VC_COMMAND: {
2509 if (cmd.argument().empty())
2511 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2515 case LFUN_VC_COMPARE:
2516 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2519 case LFUN_SERVER_GOTO_FILE_ROW:
2520 case LFUN_LYX_ACTIVATE:
2521 case LFUN_WINDOW_RAISE:
2523 case LFUN_FORWARD_SEARCH:
2524 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2527 case LFUN_FILE_INSERT_PLAINTEXT:
2528 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2529 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2532 case LFUN_SPELLING_CONTINUOUSLY:
2533 flag.setOnOff(lyxrc.spellcheck_continuously);
2536 case LFUN_CITATION_OPEN:
2545 flag.setEnabled(false);
2551 static FileName selectTemplateFile()
2553 FileDialog dlg(qt_("Select template file"));
2554 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2555 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2557 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2558 QStringList(qt_("LyX Documents (*.lyx)")));
2560 if (result.first == FileDialog::Later)
2562 if (result.second.isEmpty())
2564 return FileName(fromqstr(result.second));
2568 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2572 Buffer * newBuffer = nullptr;
2574 newBuffer = checkAndLoadLyXFile(filename);
2575 } catch (ExceptionMessage const &) {
2582 message(_("Document not loaded."));
2586 setBuffer(newBuffer);
2587 newBuffer->errors("Parse");
2590 theSession().lastFiles().add(filename);
2591 theSession().writeFile();
2598 void GuiView::openDocument(string const & fname)
2600 string initpath = lyxrc.document_path;
2602 if (documentBufferView()) {
2603 string const trypath = documentBufferView()->buffer().filePath();
2604 // If directory is writeable, use this as default.
2605 if (FileName(trypath).isDirWritable())
2611 if (fname.empty()) {
2612 FileDialog dlg(qt_("Select document to open"));
2613 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2614 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2616 QStringList const filter({
2617 qt_("LyX Documents (*.lyx)"),
2618 qt_("LyX Document Backups (*.lyx~)"),
2619 qt_("All Files (*.*)")
2621 FileDialog::Result result =
2622 dlg.open(toqstr(initpath), filter);
2624 if (result.first == FileDialog::Later)
2627 filename = fromqstr(result.second);
2629 // check selected filename
2630 if (filename.empty()) {
2631 message(_("Canceled."));
2637 // get absolute path of file and add ".lyx" to the filename if
2639 FileName const fullname =
2640 fileSearch(string(), filename, "lyx", support::may_not_exist);
2641 if (!fullname.empty())
2642 filename = fullname.absFileName();
2644 if (!fullname.onlyPath().isDirectory()) {
2645 Alert::warning(_("Invalid filename"),
2646 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2647 from_utf8(fullname.absFileName())));
2651 // if the file doesn't exist and isn't already open (bug 6645),
2652 // let the user create one
2653 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2654 !LyXVC::file_not_found_hook(fullname)) {
2655 // the user specifically chose this name. Believe him.
2656 Buffer * const b = newFile(filename, string(), true);
2662 docstring const disp_fn = makeDisplayPath(filename);
2663 message(bformat(_("Opening document %1$s..."), disp_fn));
2666 Buffer * buf = loadDocument(fullname);
2668 str2 = bformat(_("Document %1$s opened."), disp_fn);
2669 if (buf->lyxvc().inUse())
2670 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2671 " " + _("Version control detected.");
2673 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2678 // FIXME: clean that
2679 static bool import(GuiView * lv, FileName const & filename,
2680 string const & format, ErrorList & errorList)
2682 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2684 string loader_format;
2685 vector<string> loaders = theConverters().loaders();
2686 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2687 for (string const & loader : loaders) {
2688 if (!theConverters().isReachable(format, loader))
2691 string const tofile =
2692 support::changeExtension(filename.absFileName(),
2693 theFormats().extension(loader));
2694 if (theConverters().convert(nullptr, filename, FileName(tofile),
2695 filename, format, loader, errorList) != Converters::SUCCESS)
2697 loader_format = loader;
2700 if (loader_format.empty()) {
2701 frontend::Alert::error(_("Couldn't import file"),
2702 bformat(_("No information for importing the format %1$s."),
2703 translateIfPossible(theFormats().prettyName(format))));
2707 loader_format = format;
2709 if (loader_format == "lyx") {
2710 Buffer * buf = lv->loadDocument(lyxfile);
2714 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2718 bool as_paragraphs = loader_format == "textparagraph";
2719 string filename2 = (loader_format == format) ? filename.absFileName()
2720 : support::changeExtension(filename.absFileName(),
2721 theFormats().extension(loader_format));
2722 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2724 guiApp->setCurrentView(lv);
2725 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2732 void GuiView::importDocument(string const & argument)
2735 string filename = split(argument, format, ' ');
2737 LYXERR(Debug::INFO, format << " file: " << filename);
2739 // need user interaction
2740 if (filename.empty()) {
2741 string initpath = lyxrc.document_path;
2742 if (documentBufferView()) {
2743 string const trypath = documentBufferView()->buffer().filePath();
2744 // If directory is writeable, use this as default.
2745 if (FileName(trypath).isDirWritable())
2749 docstring const text = bformat(_("Select %1$s file to import"),
2750 translateIfPossible(theFormats().prettyName(format)));
2752 FileDialog dlg(toqstr(text));
2753 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2754 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2756 docstring filter = translateIfPossible(theFormats().prettyName(format));
2759 filter += from_utf8(theFormats().extensions(format));
2762 FileDialog::Result result =
2763 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2765 if (result.first == FileDialog::Later)
2768 filename = fromqstr(result.second);
2770 // check selected filename
2771 if (filename.empty())
2772 message(_("Canceled."));
2775 if (filename.empty())
2778 // get absolute path of file
2779 FileName const fullname(support::makeAbsPath(filename));
2781 // Can happen if the user entered a path into the dialog
2783 if (fullname.onlyFileName().empty()) {
2784 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2785 "Aborting import."),
2786 from_utf8(fullname.absFileName()));
2787 frontend::Alert::error(_("File name error"), msg);
2788 message(_("Canceled."));
2793 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2795 // Check if the document already is open
2796 Buffer * buf = theBufferList().getBuffer(lyxfile);
2799 if (!closeBuffer()) {
2800 message(_("Canceled."));
2805 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2807 // if the file exists already, and we didn't do
2808 // -i lyx thefile.lyx, warn
2809 if (lyxfile.exists() && fullname != lyxfile) {
2811 docstring text = bformat(_("The document %1$s already exists.\n\n"
2812 "Do you want to overwrite that document?"), displaypath);
2813 int const ret = Alert::prompt(_("Overwrite document?"),
2814 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2817 message(_("Canceled."));
2822 message(bformat(_("Importing %1$s..."), displaypath));
2823 ErrorList errorList;
2824 if (import(this, fullname, format, errorList))
2825 message(_("imported."));
2827 message(_("file not imported!"));
2829 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2833 void GuiView::newDocument(string const & filename, string templatefile,
2836 FileName initpath(lyxrc.document_path);
2837 if (documentBufferView()) {
2838 FileName const trypath(documentBufferView()->buffer().filePath());
2839 // If directory is writeable, use this as default.
2840 if (trypath.isDirWritable())
2844 if (from_template) {
2845 if (templatefile.empty())
2846 templatefile = selectTemplateFile().absFileName();
2847 if (templatefile.empty())
2852 if (filename.empty())
2853 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2855 b = newFile(filename, templatefile, true);
2860 // If no new document could be created, it is unsure
2861 // whether there is a valid BufferView.
2862 if (currentBufferView())
2863 // Ensure the cursor is correctly positioned on screen.
2864 currentBufferView()->showCursor();
2868 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2870 BufferView * bv = documentBufferView();
2875 FileName filename(to_utf8(fname));
2876 if (filename.empty()) {
2877 // Launch a file browser
2879 string initpath = lyxrc.document_path;
2880 string const trypath = bv->buffer().filePath();
2881 // If directory is writeable, use this as default.
2882 if (FileName(trypath).isDirWritable())
2886 FileDialog dlg(qt_("Select LyX document to insert"));
2887 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2888 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2890 FileDialog::Result result = dlg.open(toqstr(initpath),
2891 QStringList(qt_("LyX Documents (*.lyx)")));
2893 if (result.first == FileDialog::Later)
2897 filename.set(fromqstr(result.second));
2899 // check selected filename
2900 if (filename.empty()) {
2901 // emit message signal.
2902 message(_("Canceled."));
2907 bv->insertLyXFile(filename, ignorelang);
2908 bv->buffer().errors("Parse");
2913 string const GuiView::getTemplatesPath(Buffer & b)
2915 // We start off with the user's templates path
2916 string result = addPath(package().user_support().absFileName(), "templates");
2917 // Check for the document language
2918 string const langcode = b.params().language->code();
2919 string const shortcode = langcode.substr(0, 2);
2920 if (!langcode.empty() && shortcode != "en") {
2921 string subpath = addPath(result, shortcode);
2922 string subpath_long = addPath(result, langcode);
2923 // If we have a subdirectory for the language already,
2925 FileName sp = FileName(subpath);
2926 if (sp.isDirectory())
2928 else if (FileName(subpath_long).isDirectory())
2929 result = subpath_long;
2931 // Ask whether we should create such a subdirectory
2932 docstring const text =
2933 bformat(_("It is suggested to save the template in a subdirectory\n"
2934 "appropriate to the document language (%1$s).\n"
2935 "This subdirectory does not exists yet.\n"
2936 "Do you want to create it?"),
2937 _(b.params().language->display()));
2938 if (Alert::prompt(_("Create Language Directory?"),
2939 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2940 // If the user agreed, we try to create it and report if this failed.
2941 if (!sp.createDirectory(0777))
2942 Alert::error(_("Subdirectory creation failed!"),
2943 _("Could not create subdirectory.\n"
2944 "The template will be saved in the parent directory."));
2950 // Do we have a layout category?
2951 string const cat = b.params().baseClass() ?
2952 b.params().baseClass()->category()
2955 string subpath = addPath(result, cat);
2956 // If we have a subdirectory for the category already,
2958 FileName sp = FileName(subpath);
2959 if (sp.isDirectory())
2962 // Ask whether we should create such a subdirectory
2963 docstring const text =
2964 bformat(_("It is suggested to save the template in a subdirectory\n"
2965 "appropriate to the layout category (%1$s).\n"
2966 "This subdirectory does not exists yet.\n"
2967 "Do you want to create it?"),
2969 if (Alert::prompt(_("Create Category Directory?"),
2970 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2971 // If the user agreed, we try to create it and report if this failed.
2972 if (!sp.createDirectory(0777))
2973 Alert::error(_("Subdirectory creation failed!"),
2974 _("Could not create subdirectory.\n"
2975 "The template will be saved in the parent directory."));
2985 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2987 FileName fname = b.fileName();
2988 FileName const oldname = fname;
2989 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2991 if (!newname.empty()) {
2994 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2996 fname = support::makeAbsPath(to_utf8(newname),
2997 oldname.onlyPath().absFileName());
2999 // Switch to this Buffer.
3002 // No argument? Ask user through dialog.
3004 QString const title = as_template ? qt_("Choose a filename to save template as")
3005 : qt_("Choose a filename to save document as");
3006 FileDialog dlg(title);
3007 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3008 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3010 if (!isLyXFileName(fname.absFileName()))
3011 fname.changeExtension(".lyx");
3013 string const path = as_template ?
3015 : fname.onlyPath().absFileName();
3016 FileDialog::Result result =
3017 dlg.save(toqstr(path),
3018 QStringList(qt_("LyX Documents (*.lyx)")),
3019 toqstr(fname.onlyFileName()));
3021 if (result.first == FileDialog::Later)
3024 fname.set(fromqstr(result.second));
3029 if (!isLyXFileName(fname.absFileName()))
3030 fname.changeExtension(".lyx");
3033 // fname is now the new Buffer location.
3035 // if there is already a Buffer open with this name, we do not want
3036 // to have another one. (the second test makes sure we're not just
3037 // trying to overwrite ourselves, which is fine.)
3038 if (theBufferList().exists(fname) && fname != oldname
3039 && theBufferList().getBuffer(fname) != &b) {
3040 docstring const text =
3041 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3042 "Please close it before attempting to overwrite it.\n"
3043 "Do you want to choose a new filename?"),
3044 from_utf8(fname.absFileName()));
3045 int const ret = Alert::prompt(_("Chosen File Already Open"),
3046 text, 0, 1, _("&Rename"), _("&Cancel"));
3048 case 0: return renameBuffer(b, docstring(), kind);
3049 case 1: return false;
3054 bool const existsLocal = fname.exists();
3055 bool const existsInVC = LyXVC::fileInVC(fname);
3056 if (existsLocal || existsInVC) {
3057 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3058 if (kind != LV_WRITE_AS && existsInVC) {
3059 // renaming to a name that is already in VC
3061 docstring text = bformat(_("The document %1$s "
3062 "is already registered.\n\n"
3063 "Do you want to choose a new name?"),
3065 docstring const title = (kind == LV_VC_RENAME) ?
3066 _("Rename document?") : _("Copy document?");
3067 docstring const button = (kind == LV_VC_RENAME) ?
3068 _("&Rename") : _("&Copy");
3069 int const ret = Alert::prompt(title, text, 0, 1,
3070 button, _("&Cancel"));
3072 case 0: return renameBuffer(b, docstring(), kind);
3073 case 1: return false;
3078 docstring text = bformat(_("The document %1$s "
3079 "already exists.\n\n"
3080 "Do you want to overwrite that document?"),
3082 int const ret = Alert::prompt(_("Overwrite document?"),
3083 text, 0, 2, _("&Overwrite"),
3084 _("&Rename"), _("&Cancel"));
3087 case 1: return renameBuffer(b, docstring(), kind);
3088 case 2: return false;
3094 case LV_VC_RENAME: {
3095 string msg = b.lyxvc().rename(fname);
3098 message(from_utf8(msg));
3102 string msg = b.lyxvc().copy(fname);
3105 message(from_utf8(msg));
3109 case LV_WRITE_AS_TEMPLATE:
3112 // LyXVC created the file already in case of LV_VC_RENAME or
3113 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3114 // relative paths of included stuff right if we moved e.g. from
3115 // /a/b.lyx to /a/c/b.lyx.
3117 bool const saved = saveBuffer(b, fname);
3124 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3126 FileName fname = b.fileName();
3128 FileDialog dlg(qt_("Choose a filename to export the document as"));
3129 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3132 QString const anyformat = qt_("Guess from extension (*.*)");
3135 vector<Format const *> export_formats;
3136 for (Format const & f : theFormats())
3137 if (f.documentFormat())
3138 export_formats.push_back(&f);
3139 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3140 map<QString, string> fmap;
3143 for (Format const * f : export_formats) {
3144 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3145 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3147 from_ascii(f->extension())));
3148 types << loc_filter;
3149 fmap[loc_filter] = f->name();
3150 if (from_ascii(f->name()) == iformat) {
3151 filter = loc_filter;
3152 ext = f->extension();
3155 string ofname = fname.onlyFileName();
3157 ofname = support::changeExtension(ofname, ext);
3158 FileDialog::Result result =
3159 dlg.save(toqstr(fname.onlyPath().absFileName()),
3163 if (result.first != FileDialog::Chosen)
3167 fname.set(fromqstr(result.second));
3168 if (filter == anyformat)
3169 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3171 fmt_name = fmap[filter];
3172 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3173 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3175 if (fmt_name.empty() || fname.empty())
3178 // fname is now the new Buffer location.
3179 if (fname.exists()) {
3180 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3181 docstring text = bformat(_("The document %1$s already "
3182 "exists.\n\nDo you want to "
3183 "overwrite that document?"),
3185 int const ret = Alert::prompt(_("Overwrite document?"),
3186 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3189 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3190 case 2: return false;
3194 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3197 return dr.dispatched();
3201 bool GuiView::saveBuffer(Buffer & b)
3203 return saveBuffer(b, FileName());
3207 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3209 if (workArea(b) && workArea(b)->inDialogMode())
3212 if (fn.empty() && b.isUnnamed())
3213 return renameBuffer(b, docstring());
3215 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3217 theSession().lastFiles().add(b.fileName());
3218 theSession().writeFile();
3222 // Switch to this Buffer.
3225 // FIXME: we don't tell the user *WHY* the save failed !!
3226 docstring const file = makeDisplayPath(b.absFileName(), 30);
3227 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3228 "Do you want to rename the document and "
3229 "try again?"), file);
3230 int const ret = Alert::prompt(_("Rename and save?"),
3231 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3234 if (!renameBuffer(b, docstring()))
3243 return saveBuffer(b, fn);
3247 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3249 return closeWorkArea(wa, false);
3253 // We only want to close the buffer if it is not visible in other workareas
3254 // of the same view, nor in other views, and if this is not a child
3255 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3257 Buffer & buf = wa->bufferView().buffer();
3259 bool last_wa = d.countWorkAreasOf(buf) == 1
3260 && !inOtherView(buf) && !buf.parent();
3262 bool close_buffer = last_wa;
3265 if (lyxrc.close_buffer_with_last_view == "yes")
3267 else if (lyxrc.close_buffer_with_last_view == "no")
3268 close_buffer = false;
3271 if (buf.isUnnamed())
3272 file = from_utf8(buf.fileName().onlyFileName());
3274 file = buf.fileName().displayName(30);
3275 docstring const text = bformat(
3276 _("Last view on document %1$s is being closed.\n"
3277 "Would you like to close or hide the document?\n"
3279 "Hidden documents can be displayed back through\n"
3280 "the menu: View->Hidden->...\n"
3282 "To remove this question, set your preference in:\n"
3283 " Tools->Preferences->Look&Feel->UserInterface\n"
3285 int ret = Alert::prompt(_("Close or hide document?"),
3286 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3289 close_buffer = (ret == 0);
3293 return closeWorkArea(wa, close_buffer);
3297 bool GuiView::closeBuffer()
3299 GuiWorkArea * wa = currentMainWorkArea();
3300 // coverity complained about this
3301 // it seems unnecessary, but perhaps is worth the check
3302 LASSERT(wa, return false);
3304 setCurrentWorkArea(wa);
3305 Buffer & buf = wa->bufferView().buffer();
3306 return closeWorkArea(wa, !buf.parent());
3310 void GuiView::writeSession() const {
3311 GuiWorkArea const * active_wa = currentMainWorkArea();
3312 for (int i = 0; i < d.splitter_->count(); ++i) {
3313 TabWorkArea * twa = d.tabWorkArea(i);
3314 for (int j = 0; j < twa->count(); ++j) {
3315 GuiWorkArea * wa = twa->workArea(j);
3316 Buffer & buf = wa->bufferView().buffer();
3317 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3323 bool GuiView::closeBufferAll()
3326 for (auto & buf : theBufferList()) {
3327 if (!saveBufferIfNeeded(*buf, false)) {
3328 // Closing has been cancelled, so abort.
3333 // Close the workareas in all other views
3334 QList<int> const ids = guiApp->viewIds();
3335 for (int i = 0; i != ids.size(); ++i) {
3336 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3340 // Close our own workareas
3341 if (!closeWorkAreaAll())
3348 bool GuiView::closeWorkAreaAll()
3350 setCurrentWorkArea(currentMainWorkArea());
3352 // We might be in a situation that there is still a tabWorkArea, but
3353 // there are no tabs anymore. This can happen when we get here after a
3354 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3355 // many TabWorkArea's have no documents anymore.
3358 // We have to call count() each time, because it can happen that
3359 // more than one splitter will disappear in one iteration (bug 5998).
3360 while (d.splitter_->count() > empty_twa) {
3361 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3363 if (twa->count() == 0)
3366 setCurrentWorkArea(twa->currentWorkArea());
3367 if (!closeTabWorkArea(twa))
3375 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3380 Buffer & buf = wa->bufferView().buffer();
3382 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3383 Alert::warning(_("Close document"),
3384 _("Document could not be closed because it is being processed by LyX."));
3389 return closeBuffer(buf);
3391 if (!inMultiTabs(wa))
3392 if (!saveBufferIfNeeded(buf, true))
3400 bool GuiView::closeBuffer(Buffer & buf)
3402 bool success = true;
3403 for (Buffer * child_buf : buf.getChildren()) {
3404 if (theBufferList().isOthersChild(&buf, child_buf)) {
3405 child_buf->setParent(nullptr);
3409 // FIXME: should we look in other tabworkareas?
3410 // ANSWER: I don't think so. I've tested, and if the child is
3411 // open in some other window, it closes without a problem.
3412 GuiWorkArea * child_wa = workArea(*child_buf);
3415 // If we are in a close_event all children will be closed in some time,
3416 // so no need to do it here. This will ensure that the children end up
3417 // in the session file in the correct order. If we close the master
3418 // buffer, we can close or release the child buffers here too.
3420 success = closeWorkArea(child_wa, true);
3424 // In this case the child buffer is open but hidden.
3425 // Even in this case, children can be dirty (e.g.,
3426 // after a label change in the master, see #11405).
3427 // Therefore, check this
3428 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3429 // If we are in a close_event all children will be closed in some time,
3430 // so no need to do it here. This will ensure that the children end up
3431 // in the session file in the correct order. If we close the master
3432 // buffer, we can close or release the child buffers here too.
3435 // Save dirty buffers also if closing_!
3436 if (saveBufferIfNeeded(*child_buf, false)) {
3437 child_buf->removeAutosaveFile();
3438 theBufferList().release(child_buf);
3440 // Saving of dirty children has been cancelled.
3441 // Cancel the whole process.
3448 // goto bookmark to update bookmark pit.
3449 // FIXME: we should update only the bookmarks related to this buffer!
3450 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3451 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3452 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3453 guiApp->gotoBookmark(i, false, false);
3455 if (saveBufferIfNeeded(buf, false)) {
3456 buf.removeAutosaveFile();
3457 theBufferList().release(&buf);
3461 // open all children again to avoid a crash because of dangling
3462 // pointers (bug 6603)
3468 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3470 while (twa == d.currentTabWorkArea()) {
3471 twa->setCurrentIndex(twa->count() - 1);
3473 GuiWorkArea * wa = twa->currentWorkArea();
3474 Buffer & b = wa->bufferView().buffer();
3476 // We only want to close the buffer if the same buffer is not visible
3477 // in another view, and if this is not a child and if we are closing
3478 // a view (not a tabgroup).
3479 bool const close_buffer =
3480 !inOtherView(b) && !b.parent() && closing_;
3482 if (!closeWorkArea(wa, close_buffer))
3489 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3491 if (buf.isClean() || buf.paragraphs().empty())
3494 // Switch to this Buffer.
3500 if (buf.isUnnamed()) {
3501 file = from_utf8(buf.fileName().onlyFileName());
3504 FileName filename = buf.fileName();
3506 file = filename.displayName(30);
3507 exists = filename.exists();
3510 // Bring this window to top before asking questions.
3515 if (hiding && buf.isUnnamed()) {
3516 docstring const text = bformat(_("The document %1$s has not been "
3517 "saved yet.\n\nDo you want to save "
3518 "the document?"), file);
3519 ret = Alert::prompt(_("Save new document?"),
3520 text, 0, 1, _("&Save"), _("&Cancel"));
3524 docstring const text = exists ?
3525 bformat(_("The document %1$s has unsaved changes."
3526 "\n\nDo you want to save the document or "
3527 "discard the changes?"), file) :
3528 bformat(_("The document %1$s has not been saved yet."
3529 "\n\nDo you want to save the document or "
3530 "discard it entirely?"), file);
3531 docstring const title = exists ?
3532 _("Save changed document?") : _("Save document?");
3533 ret = Alert::prompt(title, text, 0, 2,
3534 _("&Save"), _("&Discard"), _("&Cancel"));
3539 if (!saveBuffer(buf))
3543 // If we crash after this we could have no autosave file
3544 // but I guess this is really improbable (Jug).
3545 // Sometimes improbable things happen:
3546 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3547 // buf.removeAutosaveFile();
3549 // revert all changes
3560 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3562 Buffer & buf = wa->bufferView().buffer();
3564 for (int i = 0; i != d.splitter_->count(); ++i) {
3565 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3566 if (wa_ && wa_ != wa)
3569 return inOtherView(buf);
3573 bool GuiView::inOtherView(Buffer & buf)
3575 QList<int> const ids = guiApp->viewIds();
3577 for (int i = 0; i != ids.size(); ++i) {
3581 if (guiApp->view(ids[i]).workArea(buf))
3588 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3590 if (!documentBufferView())
3593 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3594 Buffer * const curbuf = &documentBufferView()->buffer();
3595 int nwa = twa->count();
3596 for (int i = 0; i < nwa; ++i) {
3597 if (&workArea(i)->bufferView().buffer() == curbuf) {
3599 if (np == NEXTBUFFER)
3600 next_index = (i == nwa - 1 ? 0 : i + 1);
3602 next_index = (i == 0 ? nwa - 1 : i - 1);
3604 twa->moveTab(i, next_index);
3606 setBuffer(&workArea(next_index)->bufferView().buffer());
3614 /// make sure the document is saved
3615 static bool ensureBufferClean(Buffer * buffer)
3617 LASSERT(buffer, return false);
3618 if (buffer->isClean() && !buffer->isUnnamed())
3621 docstring const file = buffer->fileName().displayName(30);
3624 if (!buffer->isUnnamed()) {
3625 text = bformat(_("The document %1$s has unsaved "
3626 "changes.\n\nDo you want to save "
3627 "the document?"), file);
3628 title = _("Save changed document?");
3631 text = bformat(_("The document %1$s has not been "
3632 "saved yet.\n\nDo you want to save "
3633 "the document?"), file);
3634 title = _("Save new document?");
3636 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3639 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3641 return buffer->isClean() && !buffer->isUnnamed();
3645 bool GuiView::reloadBuffer(Buffer & buf)
3647 currentBufferView()->cursor().reset();
3648 Buffer::ReadStatus status = buf.reload();
3649 return status == Buffer::ReadSuccess;
3653 void GuiView::checkExternallyModifiedBuffers()
3655 for (Buffer * buf : theBufferList()) {
3656 if (buf->fileName().exists() && buf->isChecksumModified()) {
3657 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3658 " Reload now? Any local changes will be lost."),
3659 from_utf8(buf->absFileName()));
3660 int const ret = Alert::prompt(_("Reload externally changed document?"),
3661 text, 0, 1, _("&Reload"), _("&Cancel"));
3669 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3671 Buffer * buffer = documentBufferView()
3672 ? &(documentBufferView()->buffer()) : nullptr;
3674 switch (cmd.action()) {
3675 case LFUN_VC_REGISTER:
3676 if (!buffer || !ensureBufferClean(buffer))
3678 if (!buffer->lyxvc().inUse()) {
3679 if (buffer->lyxvc().registrer()) {
3680 reloadBuffer(*buffer);
3681 dr.clearMessageUpdate();
3686 case LFUN_VC_RENAME:
3687 case LFUN_VC_COPY: {
3688 if (!buffer || !ensureBufferClean(buffer))
3690 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3691 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3692 // Some changes are not yet committed.
3693 // We test here and not in getStatus(), since
3694 // this test is expensive.
3696 LyXVC::CommandResult ret =
3697 buffer->lyxvc().checkIn(log);
3699 if (ret == LyXVC::ErrorCommand ||
3700 ret == LyXVC::VCSuccess)
3701 reloadBuffer(*buffer);
3702 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3703 frontend::Alert::error(
3704 _("Revision control error."),
3705 _("Document could not be checked in."));
3709 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3710 LV_VC_RENAME : LV_VC_COPY;
3711 renameBuffer(*buffer, cmd.argument(), kind);
3716 case LFUN_VC_CHECK_IN:
3717 if (!buffer || !ensureBufferClean(buffer))
3719 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3721 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3723 // Only skip reloading if the checkin was cancelled or
3724 // an error occurred before the real checkin VCS command
3725 // was executed, since the VCS might have changed the
3726 // file even if it could not checkin successfully.
3727 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3728 reloadBuffer(*buffer);
3732 case LFUN_VC_CHECK_OUT:
3733 if (!buffer || !ensureBufferClean(buffer))
3735 if (buffer->lyxvc().inUse()) {
3736 dr.setMessage(buffer->lyxvc().checkOut());
3737 reloadBuffer(*buffer);
3741 case LFUN_VC_LOCKING_TOGGLE:
3742 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3744 if (buffer->lyxvc().inUse()) {
3745 string res = buffer->lyxvc().lockingToggle();
3747 frontend::Alert::error(_("Revision control error."),
3748 _("Error when setting the locking property."));
3751 reloadBuffer(*buffer);
3756 case LFUN_VC_REVERT:
3759 if (buffer->lyxvc().revert()) {
3760 reloadBuffer(*buffer);
3761 dr.clearMessageUpdate();
3765 case LFUN_VC_UNDO_LAST:
3768 buffer->lyxvc().undoLast();
3769 reloadBuffer(*buffer);
3770 dr.clearMessageUpdate();
3773 case LFUN_VC_REPO_UPDATE:
3776 if (ensureBufferClean(buffer)) {
3777 dr.setMessage(buffer->lyxvc().repoUpdate());
3778 checkExternallyModifiedBuffers();
3782 case LFUN_VC_COMMAND: {
3783 string flag = cmd.getArg(0);
3784 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3787 if (contains(flag, 'M')) {
3788 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3791 string path = cmd.getArg(1);
3792 if (contains(path, "$$p") && buffer)
3793 path = subst(path, "$$p", buffer->filePath());
3794 LYXERR(Debug::LYXVC, "Directory: " << path);
3796 if (!pp.isReadableDirectory()) {
3797 lyxerr << _("Directory is not accessible.") << endl;
3800 support::PathChanger p(pp);
3802 string command = cmd.getArg(2);
3803 if (command.empty())
3806 command = subst(command, "$$i", buffer->absFileName());
3807 command = subst(command, "$$p", buffer->filePath());
3809 command = subst(command, "$$m", to_utf8(message));
3810 LYXERR(Debug::LYXVC, "Command: " << command);
3812 one.startscript(Systemcall::Wait, command);
3816 if (contains(flag, 'I'))
3817 buffer->markDirty();
3818 if (contains(flag, 'R'))
3819 reloadBuffer(*buffer);
3824 case LFUN_VC_COMPARE: {
3825 if (cmd.argument().empty()) {
3826 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3832 string rev1 = cmd.getArg(0);
3836 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3839 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3840 f2 = buffer->absFileName();
3842 string rev2 = cmd.getArg(1);
3846 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3850 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3851 f1 << "\n" << f2 << "\n" );
3852 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3853 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3863 void GuiView::openChildDocument(string const & fname)
3865 LASSERT(documentBufferView(), return);
3866 Buffer & buffer = documentBufferView()->buffer();
3867 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3868 documentBufferView()->saveBookmark(false);
3869 Buffer * child = nullptr;
3870 if (theBufferList().exists(filename)) {
3871 child = theBufferList().getBuffer(filename);
3874 message(bformat(_("Opening child document %1$s..."),
3875 makeDisplayPath(filename.absFileName())));
3876 child = loadDocument(filename, false);
3878 // Set the parent name of the child document.
3879 // This makes insertion of citations and references in the child work,
3880 // when the target is in the parent or another child document.
3882 child->setParent(&buffer);
3886 bool GuiView::goToFileRow(string const & argument)
3890 size_t i = argument.find_last_of(' ');
3891 if (i != string::npos) {
3892 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3893 istringstream is(argument.substr(i + 1));
3898 if (i == string::npos) {
3899 LYXERR0("Wrong argument: " << argument);
3902 Buffer * buf = nullptr;
3903 string const realtmp = package().temp_dir().realPath();
3904 // We have to use os::path_prefix_is() here, instead of
3905 // simply prefixIs(), because the file name comes from
3906 // an external application and may need case adjustment.
3907 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3908 buf = theBufferList().getBufferFromTmp(file_name, true);
3909 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3910 << (buf ? " success" : " failed"));
3912 // Must replace extension of the file to be .lyx
3913 // and get full path
3914 FileName const s = fileSearch(string(),
3915 support::changeExtension(file_name, ".lyx"), "lyx");
3916 // Either change buffer or load the file
3917 if (theBufferList().exists(s))
3918 buf = theBufferList().getBuffer(s);
3919 else if (s.exists()) {
3920 buf = loadDocument(s);
3925 _("File does not exist: %1$s"),
3926 makeDisplayPath(file_name)));
3932 _("No buffer for file: %1$s."),
3933 makeDisplayPath(file_name))
3938 bool success = documentBufferView()->setCursorFromRow(row);
3940 LYXERR(Debug::LATEX,
3941 "setCursorFromRow: invalid position for row " << row);
3942 frontend::Alert::error(_("Inverse Search Failed"),
3943 _("Invalid position requested by inverse search.\n"
3944 "You may need to update the viewed document."));
3950 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3952 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3953 menu->exec(QCursor::pos());
3958 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3959 Buffer const * orig, Buffer * clone, string const & format)
3961 Buffer::ExportStatus const status = func(format);
3963 // the cloning operation will have produced a clone of the entire set of
3964 // documents, starting from the master. so we must delete those.
3965 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3967 busyBuffers.remove(orig);
3972 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3973 Buffer const * orig, Buffer * clone, string const & format)
3975 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3977 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3981 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3982 Buffer const * orig, Buffer * clone, string const & format)
3984 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3986 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3990 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3991 Buffer const * orig, Buffer * clone, string const & format)
3993 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3995 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3999 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4000 Buffer const * used_buffer,
4001 docstring const & msg,
4002 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4003 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4004 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4005 bool allow_async, bool use_tmpdir)
4010 string format = argument;
4012 format = used_buffer->params().getDefaultOutputFormat();
4013 processing_format = format;
4015 progress_->clearMessages();
4018 #if EXPORT_in_THREAD
4020 GuiViewPrivate::busyBuffers.insert(used_buffer);
4021 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4022 if (!cloned_buffer) {
4023 Alert::error(_("Export Error"),
4024 _("Error cloning the Buffer."));
4027 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4032 setPreviewFuture(f);
4033 last_export_format = used_buffer->params().bufferFormat();
4036 // We are asynchronous, so we don't know here anything about the success
4039 Buffer::ExportStatus status;
4041 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4042 } else if (previewFunc) {
4043 status = (used_buffer->*previewFunc)(format);
4046 handleExportStatus(gv_, status, format);
4048 return (status == Buffer::ExportSuccess
4049 || status == Buffer::PreviewSuccess);
4053 Buffer::ExportStatus status;
4055 status = (used_buffer->*syncFunc)(format, true);
4056 } else if (previewFunc) {
4057 status = (used_buffer->*previewFunc)(format);
4060 handleExportStatus(gv_, status, format);
4062 return (status == Buffer::ExportSuccess
4063 || status == Buffer::PreviewSuccess);
4067 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4069 BufferView * bv = currentBufferView();
4070 LASSERT(bv, return);
4072 // Let the current BufferView dispatch its own actions.
4073 bv->dispatch(cmd, dr);
4074 if (dr.dispatched()) {
4075 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4076 updateDialog("document", "");
4080 // Try with the document BufferView dispatch if any.
4081 BufferView * doc_bv = documentBufferView();
4082 if (doc_bv && doc_bv != bv) {
4083 doc_bv->dispatch(cmd, dr);
4084 if (dr.dispatched()) {
4085 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4086 updateDialog("document", "");
4091 // Then let the current Cursor dispatch its own actions.
4092 bv->cursor().dispatch(cmd);
4094 // update completion. We do it here and not in
4095 // processKeySym to avoid another redraw just for a
4096 // changed inline completion
4097 if (cmd.origin() == FuncRequest::KEYBOARD) {
4098 if (cmd.action() == LFUN_SELF_INSERT
4099 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4100 updateCompletion(bv->cursor(), true, true);
4101 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4102 updateCompletion(bv->cursor(), false, true);
4104 updateCompletion(bv->cursor(), false, false);
4107 dr = bv->cursor().result();
4111 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4113 BufferView * bv = currentBufferView();
4114 // By default we won't need any update.
4115 dr.screenUpdate(Update::None);
4116 // assume cmd will be dispatched
4117 dr.dispatched(true);
4119 Buffer * doc_buffer = documentBufferView()
4120 ? &(documentBufferView()->buffer()) : nullptr;
4122 if (cmd.origin() == FuncRequest::TOC) {
4123 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4124 toc->doDispatch(bv->cursor(), cmd, dr);
4128 string const argument = to_utf8(cmd.argument());
4130 switch(cmd.action()) {
4131 case LFUN_BUFFER_CHILD_OPEN:
4132 openChildDocument(to_utf8(cmd.argument()));
4135 case LFUN_BUFFER_IMPORT:
4136 importDocument(to_utf8(cmd.argument()));
4139 case LFUN_MASTER_BUFFER_EXPORT:
4141 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4143 case LFUN_BUFFER_EXPORT: {
4146 // GCC only sees strfwd.h when building merged
4147 if (::lyx::operator==(cmd.argument(), "custom")) {
4148 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4149 // so the following test should not be needed.
4150 // In principle, we could try to switch to such a view...
4151 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4152 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4156 string const dest = cmd.getArg(1);
4157 FileName target_dir;
4158 if (!dest.empty() && FileName::isAbsolute(dest))
4159 target_dir = FileName(support::onlyPath(dest));
4161 target_dir = doc_buffer->fileName().onlyPath();
4163 string const format = (argument.empty() || argument == "default") ?
4164 doc_buffer->params().getDefaultOutputFormat() : argument;
4166 if ((dest.empty() && doc_buffer->isUnnamed())
4167 || !target_dir.isDirWritable()) {
4168 exportBufferAs(*doc_buffer, from_utf8(format));
4171 /* TODO/Review: Is it a problem to also export the children?
4172 See the update_unincluded flag */
4173 d.asyncBufferProcessing(format,
4176 &GuiViewPrivate::exportAndDestroy,
4178 nullptr, cmd.allowAsync());
4179 // TODO Inform user about success
4183 case LFUN_BUFFER_EXPORT_AS: {
4184 LASSERT(doc_buffer, break);
4185 docstring f = cmd.argument();
4187 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4188 exportBufferAs(*doc_buffer, f);
4192 case LFUN_BUFFER_UPDATE: {
4193 d.asyncBufferProcessing(argument,
4196 &GuiViewPrivate::compileAndDestroy,
4198 nullptr, cmd.allowAsync(), true);
4201 case LFUN_BUFFER_VIEW: {
4202 d.asyncBufferProcessing(argument,
4204 _("Previewing ..."),
4205 &GuiViewPrivate::previewAndDestroy,
4207 &Buffer::preview, cmd.allowAsync());
4210 case LFUN_MASTER_BUFFER_UPDATE: {
4211 d.asyncBufferProcessing(argument,
4212 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4214 &GuiViewPrivate::compileAndDestroy,
4216 nullptr, cmd.allowAsync(), true);
4219 case LFUN_MASTER_BUFFER_VIEW: {
4220 d.asyncBufferProcessing(argument,
4221 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4223 &GuiViewPrivate::previewAndDestroy,
4224 nullptr, &Buffer::preview, cmd.allowAsync());
4227 case LFUN_EXPORT_CANCEL: {
4228 Systemcall::killscript();
4231 case LFUN_BUFFER_SWITCH: {
4232 string const file_name = to_utf8(cmd.argument());
4233 if (!FileName::isAbsolute(file_name)) {
4235 dr.setMessage(_("Absolute filename expected."));
4239 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4242 dr.setMessage(_("Document not loaded"));
4246 // Do we open or switch to the buffer in this view ?
4247 if (workArea(*buffer)
4248 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4253 // Look for the buffer in other views
4254 QList<int> const ids = guiApp->viewIds();
4256 for (; i != ids.size(); ++i) {
4257 GuiView & gv = guiApp->view(ids[i]);
4258 if (gv.workArea(*buffer)) {
4260 gv.activateWindow();
4262 gv.setBuffer(buffer);
4267 // If necessary, open a new window as a last resort
4268 if (i == ids.size()) {
4269 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4275 case LFUN_BUFFER_NEXT:
4276 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4279 case LFUN_BUFFER_MOVE_NEXT:
4280 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4283 case LFUN_BUFFER_PREVIOUS:
4284 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4287 case LFUN_BUFFER_MOVE_PREVIOUS:
4288 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4291 case LFUN_BUFFER_CHKTEX:
4292 LASSERT(doc_buffer, break);
4293 doc_buffer->runChktex();
4296 case LFUN_COMMAND_EXECUTE: {
4297 command_execute_ = true;
4298 minibuffer_focus_ = true;
4301 case LFUN_DROP_LAYOUTS_CHOICE:
4302 d.layout_->showPopup();
4305 case LFUN_MENU_OPEN:
4306 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4307 menu->exec(QCursor::pos());
4310 case LFUN_FILE_INSERT: {
4311 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4312 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4313 dr.forceBufferUpdate();
4314 dr.screenUpdate(Update::Force);
4319 case LFUN_FILE_INSERT_PLAINTEXT:
4320 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4321 string const fname = to_utf8(cmd.argument());
4322 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4323 dr.setMessage(_("Absolute filename expected."));
4327 FileName filename(fname);
4328 if (fname.empty()) {
4329 FileDialog dlg(qt_("Select file to insert"));
4331 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4332 QStringList(qt_("All Files (*)")));
4334 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4335 dr.setMessage(_("Canceled."));
4339 filename.set(fromqstr(result.second));
4343 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4344 bv->dispatch(new_cmd, dr);
4349 case LFUN_BUFFER_RELOAD: {
4350 LASSERT(doc_buffer, break);
4353 bool drop = (cmd.argument() == "dump");
4356 if (!drop && !doc_buffer->isClean()) {
4357 docstring const file =
4358 makeDisplayPath(doc_buffer->absFileName(), 20);
4359 if (doc_buffer->notifiesExternalModification()) {
4360 docstring text = _("The current version will be lost. "
4361 "Are you sure you want to load the version on disk "
4362 "of the document %1$s?");
4363 ret = Alert::prompt(_("Reload saved document?"),
4364 bformat(text, file), 1, 1,
4365 _("&Reload"), _("&Cancel"));
4367 docstring text = _("Any changes will be lost. "
4368 "Are you sure you want to revert to the saved version "
4369 "of the document %1$s?");
4370 ret = Alert::prompt(_("Revert to saved document?"),
4371 bformat(text, file), 1, 1,
4372 _("&Revert"), _("&Cancel"));
4377 doc_buffer->markClean();
4378 reloadBuffer(*doc_buffer);
4379 dr.forceBufferUpdate();
4384 case LFUN_BUFFER_RESET_EXPORT:
4385 LASSERT(doc_buffer, break);
4386 doc_buffer->requireFreshStart(true);
4387 dr.setMessage(_("Buffer export reset."));
4390 case LFUN_BUFFER_WRITE:
4391 LASSERT(doc_buffer, break);
4392 saveBuffer(*doc_buffer);
4395 case LFUN_BUFFER_WRITE_AS:
4396 LASSERT(doc_buffer, break);
4397 renameBuffer(*doc_buffer, cmd.argument());
4400 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4401 LASSERT(doc_buffer, break);
4402 renameBuffer(*doc_buffer, cmd.argument(),
4403 LV_WRITE_AS_TEMPLATE);
4406 case LFUN_BUFFER_WRITE_ALL: {
4407 Buffer * first = theBufferList().first();
4410 message(_("Saving all documents..."));
4411 // We cannot use a for loop as the buffer list cycles.
4414 if (!b->isClean()) {
4416 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4418 b = theBufferList().next(b);
4419 } while (b != first);
4420 dr.setMessage(_("All documents saved."));
4424 case LFUN_MASTER_BUFFER_FORALL: {
4428 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4429 funcToRun.allowAsync(false);
4431 for (Buffer const * buf : doc_buffer->allRelatives()) {
4432 // Switch to other buffer view and resend cmd
4433 lyx::dispatch(FuncRequest(
4434 LFUN_BUFFER_SWITCH, buf->absFileName()));
4435 lyx::dispatch(funcToRun);
4438 lyx::dispatch(FuncRequest(
4439 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4443 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4444 LASSERT(doc_buffer, break);
4445 doc_buffer->clearExternalModification();
4448 case LFUN_BUFFER_CLOSE:
4452 case LFUN_BUFFER_CLOSE_ALL:
4456 case LFUN_DEVEL_MODE_TOGGLE:
4457 devel_mode_ = !devel_mode_;
4459 dr.setMessage(_("Developer mode is now enabled."));
4461 dr.setMessage(_("Developer mode is now disabled."));
4464 case LFUN_TOOLBAR_SET: {
4465 string const name = cmd.getArg(0);
4466 string const state = cmd.getArg(1);
4467 if (GuiToolbar * t = toolbar(name))
4472 case LFUN_TOOLBAR_TOGGLE: {
4473 string const name = cmd.getArg(0);
4474 if (GuiToolbar * t = toolbar(name))
4479 case LFUN_TOOLBAR_MOVABLE: {
4480 string const name = cmd.getArg(0);
4482 // toggle (all) toolbars movablility
4483 toolbarsMovable_ = !toolbarsMovable_;
4484 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4485 GuiToolbar * tb = toolbar(ti.name);
4486 if (tb && tb->isMovable() != toolbarsMovable_)
4487 // toggle toolbar movablity if it does not fit lock
4488 // (all) toolbars positions state silent = true, since
4489 // status bar notifications are slow
4492 if (toolbarsMovable_)
4493 dr.setMessage(_("Toolbars unlocked."));
4495 dr.setMessage(_("Toolbars locked."));
4496 } else if (GuiToolbar * tb = toolbar(name))
4497 // toggle current toolbar movablity
4499 // update lock (all) toolbars positions
4500 updateLockToolbars();
4504 case LFUN_ICON_SIZE: {
4505 QSize size = d.iconSize(cmd.argument());
4507 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4508 size.width(), size.height()));
4512 case LFUN_DIALOG_UPDATE: {
4513 string const name = to_utf8(cmd.argument());
4514 if (name == "prefs" || name == "document")
4515 updateDialog(name, string());
4516 else if (name == "paragraph")
4517 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4518 else if (currentBufferView()) {
4519 Inset * inset = currentBufferView()->editedInset(name);
4520 // Can only update a dialog connected to an existing inset
4522 // FIXME: get rid of this indirection; GuiView ask the inset
4523 // if he is kind enough to update itself...
4524 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4525 //FIXME: pass DispatchResult here?
4526 inset->dispatch(currentBufferView()->cursor(), fr);
4532 case LFUN_DIALOG_TOGGLE: {
4533 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4534 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4535 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4539 case LFUN_DIALOG_DISCONNECT_INSET:
4540 disconnectDialog(to_utf8(cmd.argument()));
4543 case LFUN_DIALOG_HIDE: {
4544 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4548 case LFUN_DIALOG_SHOW: {
4549 string const name = cmd.getArg(0);
4550 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4552 if (name == "latexlog") {
4553 // getStatus checks that
4554 LASSERT(doc_buffer, break);
4555 Buffer::LogType type;
4556 string const logfile = doc_buffer->logName(&type);
4558 case Buffer::latexlog:
4561 case Buffer::buildlog:
4562 sdata = "literate ";
4565 sdata += Lexer::quoteString(logfile);
4566 showDialog("log", sdata);
4567 } else if (name == "vclog") {
4568 // getStatus checks that
4569 LASSERT(doc_buffer, break);
4570 string const sdata2 = "vc " +
4571 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4572 showDialog("log", sdata2);
4573 } else if (name == "symbols") {
4574 sdata = bv->cursor().getEncoding()->name();
4576 showDialog("symbols", sdata);
4577 } else if (name == "findreplace") {
4578 sdata = to_utf8(bv->cursor().selectionAsString(false));
4579 showDialog(name, sdata);
4581 } else if (name == "prefs" && isFullScreen()) {
4582 lfunUiToggle("fullscreen");
4583 showDialog("prefs", sdata);
4585 showDialog(name, sdata);
4590 dr.setMessage(cmd.argument());
4593 case LFUN_UI_TOGGLE: {
4594 string arg = cmd.getArg(0);
4595 if (!lfunUiToggle(arg)) {
4596 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4597 dr.setMessage(bformat(msg, from_utf8(arg)));
4599 // Make sure the keyboard focus stays in the work area.
4604 case LFUN_VIEW_SPLIT: {
4605 LASSERT(doc_buffer, break);
4606 string const orientation = cmd.getArg(0);
4607 d.splitter_->setOrientation(orientation == "vertical"
4608 ? Qt::Vertical : Qt::Horizontal);
4609 TabWorkArea * twa = addTabWorkArea();
4610 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4611 setCurrentWorkArea(wa);
4614 case LFUN_TAB_GROUP_CLOSE:
4615 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4616 closeTabWorkArea(twa);
4617 d.current_work_area_ = nullptr;
4618 twa = d.currentTabWorkArea();
4619 // Switch to the next GuiWorkArea in the found TabWorkArea.
4621 // Make sure the work area is up to date.
4622 setCurrentWorkArea(twa->currentWorkArea());
4624 setCurrentWorkArea(nullptr);
4629 case LFUN_VIEW_CLOSE:
4630 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4631 closeWorkArea(twa->currentWorkArea());
4632 d.current_work_area_ = nullptr;
4633 twa = d.currentTabWorkArea();
4634 // Switch to the next GuiWorkArea in the found TabWorkArea.
4636 // Make sure the work area is up to date.
4637 setCurrentWorkArea(twa->currentWorkArea());
4639 setCurrentWorkArea(nullptr);
4644 case LFUN_COMPLETION_INLINE:
4645 if (d.current_work_area_)
4646 d.current_work_area_->completer().showInline();
4649 case LFUN_COMPLETION_POPUP:
4650 if (d.current_work_area_)
4651 d.current_work_area_->completer().showPopup();
4656 if (d.current_work_area_)
4657 d.current_work_area_->completer().tab();
4660 case LFUN_COMPLETION_CANCEL:
4661 if (d.current_work_area_) {
4662 if (d.current_work_area_->completer().popupVisible())
4663 d.current_work_area_->completer().hidePopup();
4665 d.current_work_area_->completer().hideInline();
4669 case LFUN_COMPLETION_ACCEPT:
4670 if (d.current_work_area_)
4671 d.current_work_area_->completer().activate();
4674 case LFUN_BUFFER_ZOOM_IN:
4675 case LFUN_BUFFER_ZOOM_OUT:
4676 case LFUN_BUFFER_ZOOM: {
4677 if (cmd.argument().empty()) {
4678 if (cmd.action() == LFUN_BUFFER_ZOOM)
4680 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4685 if (cmd.action() == LFUN_BUFFER_ZOOM)
4686 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4687 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4688 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4690 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4693 // Actual zoom value: default zoom + fractional extra value
4694 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4695 if (zoom < static_cast<int>(zoom_min_))
4698 setCurrentZoom(zoom);
4700 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4701 lyxrc.currentZoom, lyxrc.defaultZoom));
4703 guiApp->fontLoader().update();
4704 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4708 case LFUN_VC_REGISTER:
4709 case LFUN_VC_RENAME:
4711 case LFUN_VC_CHECK_IN:
4712 case LFUN_VC_CHECK_OUT:
4713 case LFUN_VC_REPO_UPDATE:
4714 case LFUN_VC_LOCKING_TOGGLE:
4715 case LFUN_VC_REVERT:
4716 case LFUN_VC_UNDO_LAST:
4717 case LFUN_VC_COMMAND:
4718 case LFUN_VC_COMPARE:
4719 dispatchVC(cmd, dr);
4722 case LFUN_SERVER_GOTO_FILE_ROW:
4723 if(goToFileRow(to_utf8(cmd.argument())))
4724 dr.screenUpdate(Update::Force | Update::FitCursor);
4727 case LFUN_LYX_ACTIVATE:
4731 case LFUN_WINDOW_RAISE:
4737 case LFUN_FORWARD_SEARCH: {
4738 // it seems safe to assume we have a document buffer, since
4739 // getStatus wants one.
4740 LASSERT(doc_buffer, break);
4741 Buffer const * doc_master = doc_buffer->masterBuffer();
4742 FileName const path(doc_master->temppath());
4743 string const texname = doc_master->isChild(doc_buffer)
4744 ? DocFileName(changeExtension(
4745 doc_buffer->absFileName(),
4746 "tex")).mangledFileName()
4747 : doc_buffer->latexName();
4748 string const fulltexname =
4749 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4750 string const mastername =
4751 removeExtension(doc_master->latexName());
4752 FileName const dviname(addName(path.absFileName(),
4753 addExtension(mastername, "dvi")));
4754 FileName const pdfname(addName(path.absFileName(),
4755 addExtension(mastername, "pdf")));
4756 bool const have_dvi = dviname.exists();
4757 bool const have_pdf = pdfname.exists();
4758 if (!have_dvi && !have_pdf) {
4759 dr.setMessage(_("Please, preview the document first."));
4762 string outname = dviname.onlyFileName();
4763 string command = lyxrc.forward_search_dvi;
4764 if (!have_dvi || (have_pdf &&
4765 pdfname.lastModified() > dviname.lastModified())) {
4766 outname = pdfname.onlyFileName();
4767 command = lyxrc.forward_search_pdf;
4770 DocIterator cur = bv->cursor();
4771 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4772 LYXERR(Debug::ACTION, "Forward search: row:" << row
4774 if (row == -1 || command.empty()) {
4775 dr.setMessage(_("Couldn't proceed."));
4778 string texrow = convert<string>(row);
4780 command = subst(command, "$$n", texrow);
4781 command = subst(command, "$$f", fulltexname);
4782 command = subst(command, "$$t", texname);
4783 command = subst(command, "$$o", outname);
4785 volatile PathChanger p(path);
4787 one.startscript(Systemcall::DontWait, command);
4791 case LFUN_SPELLING_CONTINUOUSLY:
4792 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4793 dr.screenUpdate(Update::Force);
4796 case LFUN_CITATION_OPEN: {
4798 if (theFormats().getFormat("pdf"))
4799 pdfv = theFormats().getFormat("pdf")->viewer();
4800 if (theFormats().getFormat("ps"))
4801 psv = theFormats().getFormat("ps")->viewer();
4802 frontend::showTarget(argument, pdfv, psv);
4807 // The LFUN must be for one of BufferView, Buffer or Cursor;
4809 dispatchToBufferView(cmd, dr);
4813 // Need to update bv because many LFUNs here might have destroyed it
4814 bv = currentBufferView();
4816 // Clear non-empty selections
4817 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4819 Cursor & cur = bv->cursor();
4820 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4821 cur.clearSelection();
4827 bool GuiView::lfunUiToggle(string const & ui_component)
4829 if (ui_component == "scrollbar") {
4830 // hide() is of no help
4831 if (d.current_work_area_->verticalScrollBarPolicy() ==
4832 Qt::ScrollBarAlwaysOff)
4834 d.current_work_area_->setVerticalScrollBarPolicy(
4835 Qt::ScrollBarAsNeeded);
4837 d.current_work_area_->setVerticalScrollBarPolicy(
4838 Qt::ScrollBarAlwaysOff);
4839 } else if (ui_component == "statusbar") {
4840 statusBar()->setVisible(!statusBar()->isVisible());
4841 } else if (ui_component == "menubar") {
4842 menuBar()->setVisible(!menuBar()->isVisible());
4843 } else if (ui_component == "zoomslider") {
4844 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4845 zoom_in_->setVisible(zoom_slider_->isVisible());
4846 zoom_out_->setVisible(zoom_slider_->isVisible());
4847 } else if (ui_component == "frame") {
4848 int const l = contentsMargins().left();
4850 //are the frames in default state?
4851 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4853 #if QT_VERSION > 0x050903
4854 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4856 setContentsMargins(-2, -2, -2, -2);
4858 #if QT_VERSION > 0x050903
4859 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4861 setContentsMargins(0, 0, 0, 0);
4864 if (ui_component == "fullscreen") {
4872 void GuiView::toggleFullScreen()
4874 setWindowState(windowState() ^ Qt::WindowFullScreen);
4878 Buffer const * GuiView::updateInset(Inset const * inset)
4883 Buffer const * inset_buffer = &(inset->buffer());
4885 for (int i = 0; i != d.splitter_->count(); ++i) {
4886 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4889 Buffer const * buffer = &(wa->bufferView().buffer());
4890 if (inset_buffer == buffer)
4891 wa->scheduleRedraw(true);
4893 return inset_buffer;
4897 void GuiView::restartCaret()
4899 /* When we move around, or type, it's nice to be able to see
4900 * the caret immediately after the keypress.
4902 if (d.current_work_area_)
4903 d.current_work_area_->startBlinkingCaret();
4905 // Take this occasion to update the other GUI elements.
4911 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4913 if (d.current_work_area_)
4914 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4919 // This list should be kept in sync with the list of insets in
4920 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4921 // dialog should have the same name as the inset.
4922 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4923 // docs in LyXAction.cpp.
4925 char const * const dialognames[] = {
4927 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4928 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4929 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4930 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4931 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4932 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4933 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4934 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4936 char const * const * const end_dialognames =
4937 dialognames + (sizeof(dialognames) / sizeof(char *));
4941 cmpCStr(char const * name) : name_(name) {}
4942 bool operator()(char const * other) {
4943 return strcmp(other, name_) == 0;
4950 bool isValidName(string const & name)
4952 return find_if(dialognames, end_dialognames,
4953 cmpCStr(name.c_str())) != end_dialognames;
4959 void GuiView::resetDialogs()
4961 // Make sure that no LFUN uses any GuiView.
4962 guiApp->setCurrentView(nullptr);
4966 constructToolbars();
4967 guiApp->menus().fillMenuBar(menuBar(), this, false);
4968 d.layout_->updateContents(true);
4969 // Now update controls with current buffer.
4970 guiApp->setCurrentView(this);
4976 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4978 for (QObject * child: widget->children()) {
4979 if (child->inherits("QGroupBox")) {
4980 QGroupBox * box = (QGroupBox*) child;
4983 flatGroupBoxes(child, flag);
4989 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4991 if (!isValidName(name))
4994 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4996 if (it != d.dialogs_.end()) {
4998 it->second->hideView();
4999 return it->second.get();
5002 Dialog * dialog = build(name);
5003 d.dialogs_[name].reset(dialog);
5004 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5005 // Force a uniform style for group boxes
5006 // On Mac non-flat works better, on Linux flat is standard
5007 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5009 if (lyxrc.allow_geometry_session)
5010 dialog->restoreSession();
5017 void GuiView::showDialog(string const & name, string const & sdata,
5020 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5024 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5030 const string name = fromqstr(qname);
5031 const string sdata = fromqstr(qdata);
5035 Dialog * dialog = findOrBuild(name, false);
5037 bool const visible = dialog->isVisibleView();
5038 dialog->showData(sdata);
5039 if (currentBufferView())
5040 currentBufferView()->editInset(name, inset);
5041 // We only set the focus to the new dialog if it was not yet
5042 // visible in order not to change the existing previous behaviour
5044 // activateWindow is needed for floating dockviews
5045 dialog->asQWidget()->raise();
5046 dialog->asQWidget()->activateWindow();
5047 if (dialog->wantInitialFocus())
5048 dialog->asQWidget()->setFocus();
5052 catch (ExceptionMessage const &) {
5060 bool GuiView::isDialogVisible(string const & name) const
5062 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5063 if (it == d.dialogs_.end())
5065 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5069 void GuiView::hideDialog(string const & name, Inset * inset)
5071 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5072 if (it == d.dialogs_.end())
5076 if (!currentBufferView())
5078 if (inset != currentBufferView()->editedInset(name))
5082 Dialog * const dialog = it->second.get();
5083 if (dialog->isVisibleView())
5085 if (currentBufferView())
5086 currentBufferView()->editInset(name, nullptr);
5090 void GuiView::disconnectDialog(string const & name)
5092 if (!isValidName(name))
5094 if (currentBufferView())
5095 currentBufferView()->editInset(name, nullptr);
5099 void GuiView::hideAll() const
5101 for(auto const & dlg_p : d.dialogs_)
5102 dlg_p.second->hideView();
5106 void GuiView::updateDialogs()
5108 for(auto const & dlg_p : d.dialogs_) {
5109 Dialog * dialog = dlg_p.second.get();
5111 if (dialog->needBufferOpen() && !documentBufferView())
5112 hideDialog(fromqstr(dialog->name()), nullptr);
5113 else if (dialog->isVisibleView())
5114 dialog->checkStatus();
5122 Dialog * GuiView::build(string const & name)
5124 return createDialog(*this, name);
5128 SEMenu::SEMenu(QWidget * parent)
5130 QAction * action = addAction(qt_("Disable Shell Escape"));
5131 connect(action, SIGNAL(triggered()),
5132 parent, SLOT(disableShellEscape()));
5136 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5138 if (event->button() == Qt::LeftButton) {
5143 } // namespace frontend
5146 #include "moc_GuiView.cpp"