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;
1045 void GuiView::constructToolbars()
1047 for (auto const & tb_p : d.toolbars_)
1049 d.toolbars_.clear();
1051 // I don't like doing this here, but the standard toolbar
1052 // destroys this object when it's destroyed itself (vfr)
1053 d.layout_ = new LayoutBox(*this);
1054 d.stack_widget_->addWidget(d.layout_);
1055 d.layout_->move(0,0);
1057 // extracts the toolbars from the backend
1058 for (ToolbarInfo const & inf : guiApp->toolbars())
1059 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1061 DynamicMenuButton::resetIconCache();
1065 void GuiView::initToolbars()
1067 // extracts the toolbars from the backend
1068 for (ToolbarInfo const & inf : guiApp->toolbars())
1069 initToolbar(inf.name);
1073 void GuiView::initToolbar(string const & name)
1075 GuiToolbar * tb = toolbar(name);
1078 int const visibility = guiApp->toolbars().defaultVisibility(name);
1079 bool newline = !(visibility & Toolbars::SAMEROW);
1080 tb->setVisible(false);
1081 tb->setVisibility(visibility);
1083 if (visibility & Toolbars::TOP) {
1085 addToolBarBreak(Qt::TopToolBarArea);
1086 addToolBar(Qt::TopToolBarArea, tb);
1089 if (visibility & Toolbars::BOTTOM) {
1091 addToolBarBreak(Qt::BottomToolBarArea);
1092 addToolBar(Qt::BottomToolBarArea, tb);
1095 if (visibility & Toolbars::LEFT) {
1097 addToolBarBreak(Qt::LeftToolBarArea);
1098 addToolBar(Qt::LeftToolBarArea, tb);
1101 if (visibility & Toolbars::RIGHT) {
1103 addToolBarBreak(Qt::RightToolBarArea);
1104 addToolBar(Qt::RightToolBarArea, tb);
1107 if (visibility & Toolbars::ON)
1108 tb->setVisible(true);
1110 tb->setMovable(true);
1114 TocModels & GuiView::tocModels()
1116 return d.toc_models_;
1120 void GuiView::setFocus()
1122 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1123 QMainWindow::setFocus();
1127 bool GuiView::hasFocus() const
1129 if (currentWorkArea())
1130 return currentWorkArea()->hasFocus();
1131 if (currentMainWorkArea())
1132 return currentMainWorkArea()->hasFocus();
1133 return d.bg_widget_->hasFocus();
1137 void GuiView::focusInEvent(QFocusEvent * e)
1139 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1140 QMainWindow::focusInEvent(e);
1141 // Make sure guiApp points to the correct view.
1142 guiApp->setCurrentView(this);
1143 if (currentWorkArea())
1144 currentWorkArea()->setFocus();
1145 else if (currentMainWorkArea())
1146 currentMainWorkArea()->setFocus();
1148 d.bg_widget_->setFocus();
1152 void GuiView::showEvent(QShowEvent * e)
1154 LYXERR(Debug::GUI, "Passed Geometry "
1155 << size().height() << "x" << size().width()
1156 << "+" << pos().x() << "+" << pos().y());
1158 if (d.splitter_->count() == 0)
1159 // No work area, switch to the background widget.
1163 QMainWindow::showEvent(e);
1167 bool GuiView::closeScheduled()
1174 bool GuiView::prepareAllBuffersForLogout()
1176 Buffer * first = theBufferList().first();
1180 // First, iterate over all buffers and ask the users if unsaved
1181 // changes should be saved.
1182 // We cannot use a for loop as the buffer list cycles.
1185 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1187 b = theBufferList().next(b);
1188 } while (b != first);
1190 // Next, save session state
1191 // When a view/window was closed before without quitting LyX, there
1192 // are already entries in the lastOpened list.
1193 theSession().lastOpened().clear();
1200 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1201 ** is responsibility of the container (e.g., dialog)
1203 void GuiView::closeEvent(QCloseEvent * close_event)
1205 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1207 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1208 Alert::warning(_("Exit LyX"),
1209 _("LyX could not be closed because documents are being processed by LyX."));
1210 close_event->setAccepted(false);
1214 // If the user pressed the x (so we didn't call closeView
1215 // programmatically), we want to clear all existing entries.
1217 theSession().lastOpened().clear();
1222 // it can happen that this event arrives without selecting the view,
1223 // e.g. when clicking the close button on a background window.
1225 if (!closeWorkAreaAll()) {
1227 close_event->ignore();
1231 // Make sure that nothing will use this to be closed View.
1232 guiApp->unregisterView(this);
1234 if (isFullScreen()) {
1235 // Switch off fullscreen before closing.
1240 // Make sure the timer time out will not trigger a statusbar update.
1241 d.statusbar_timer_.stop();
1243 // Saving fullscreen requires additional tweaks in the toolbar code.
1244 // It wouldn't also work under linux natively.
1245 if (lyxrc.allow_geometry_session) {
1250 close_event->accept();
1254 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1256 if (event->mimeData()->hasUrls())
1258 /// \todo Ask lyx-devel is this is enough:
1259 /// if (event->mimeData()->hasFormat("text/plain"))
1260 /// event->acceptProposedAction();
1264 void GuiView::dropEvent(QDropEvent * event)
1266 QList<QUrl> files = event->mimeData()->urls();
1267 if (files.isEmpty())
1270 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1271 for (int i = 0; i != files.size(); ++i) {
1272 string const file = os::internal_path(fromqstr(
1273 files.at(i).toLocalFile()));
1277 string const ext = support::getExtension(file);
1278 vector<const Format *> found_formats;
1280 // Find all formats that have the correct extension.
1281 for (const Format * fmt : theConverters().importableFormats())
1282 if (fmt->hasExtension(ext))
1283 found_formats.push_back(fmt);
1286 if (!found_formats.empty()) {
1287 if (found_formats.size() > 1) {
1288 //FIXME: show a dialog to choose the correct importable format
1289 LYXERR(Debug::FILES,
1290 "Multiple importable formats found, selecting first");
1292 string const arg = found_formats[0]->name() + " " + file;
1293 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1296 //FIXME: do we have to explicitly check whether it's a lyx file?
1297 LYXERR(Debug::FILES,
1298 "No formats found, trying to open it as a lyx file");
1299 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1301 // add the functions to the queue
1302 guiApp->addToFuncRequestQueue(cmd);
1305 // now process the collected functions. We perform the events
1306 // asynchronously. This prevents potential problems in case the
1307 // BufferView is closed within an event.
1308 guiApp->processFuncRequestQueueAsync();
1312 void GuiView::message(docstring const & str)
1314 if (ForkedProcess::iAmAChild())
1317 // call is moved to GUI-thread by GuiProgress
1318 d.progress_->appendMessage(toqstr(str));
1322 void GuiView::clearMessageText()
1324 message(docstring());
1328 void GuiView::updateStatusBarMessage(QString const & str)
1330 statusBar()->showMessage(str);
1331 d.statusbar_timer_.stop();
1332 d.statusbar_timer_.start(3000);
1336 void GuiView::clearMessage()
1338 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1339 // the hasFocus function mostly returns false, even if the focus is on
1340 // a workarea in this view.
1344 d.statusbar_timer_.stop();
1348 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1350 if (wa != d.current_work_area_
1351 || wa->bufferView().buffer().isInternal())
1353 Buffer const & buf = wa->bufferView().buffer();
1354 // Set the windows title
1355 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1356 if (buf.notifiesExternalModification()) {
1357 title = bformat(_("%1$s (modified externally)"), title);
1358 // If the external modification status has changed, then maybe the status of
1359 // buffer-save has changed too.
1363 title += from_ascii(" - LyX");
1365 setWindowTitle(toqstr(title));
1366 // Sets the path for the window: this is used by OSX to
1367 // allow a context click on the title bar showing a menu
1368 // with the path up to the file
1369 setWindowFilePath(toqstr(buf.absFileName()));
1370 // Tell Qt whether the current document is changed
1371 setWindowModified(!buf.isClean());
1373 if (buf.params().shell_escape)
1374 shell_escape_->show();
1376 shell_escape_->hide();
1378 if (buf.hasReadonlyFlag())
1383 if (buf.lyxvc().inUse()) {
1384 version_control_->show();
1385 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1387 version_control_->hide();
1391 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1393 if (d.current_work_area_)
1394 // disconnect the current work area from all slots
1395 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1397 disconnectBufferView();
1398 connectBufferView(wa->bufferView());
1399 connectBuffer(wa->bufferView().buffer());
1400 d.current_work_area_ = wa;
1401 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1402 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1403 QObject::connect(wa, SIGNAL(busy(bool)),
1404 this, SLOT(setBusy(bool)));
1405 // connection of a signal to a signal
1406 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1407 this, SIGNAL(bufferViewChanged()));
1408 Q_EMIT updateWindowTitle(wa);
1409 Q_EMIT bufferViewChanged();
1413 void GuiView::onBufferViewChanged()
1416 // Buffer-dependent dialogs must be updated. This is done here because
1417 // some dialogs require buffer()->text.
1419 zoom_slider_->setEnabled(currentBufferView());
1420 zoom_value_->setEnabled(currentBufferView());
1421 zoom_in_->setEnabled(currentBufferView());
1422 zoom_out_->setEnabled(currentBufferView());
1426 void GuiView::on_lastWorkAreaRemoved()
1429 // We already are in a close event. Nothing more to do.
1432 if (d.splitter_->count() > 1)
1433 // We have a splitter so don't close anything.
1436 // Reset and updates the dialogs.
1437 Q_EMIT bufferViewChanged();
1442 if (lyxrc.open_buffers_in_tabs)
1443 // Nothing more to do, the window should stay open.
1446 if (guiApp->viewIds().size() > 1) {
1452 // On Mac we also close the last window because the application stay
1453 // resident in memory. On other platforms we don't close the last
1454 // window because this would quit the application.
1460 void GuiView::updateStatusBar()
1462 // let the user see the explicit message
1463 if (d.statusbar_timer_.isActive())
1470 void GuiView::showMessage()
1474 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1475 if (msg.isEmpty()) {
1476 BufferView const * bv = currentBufferView();
1478 msg = toqstr(bv->cursor().currentState(devel_mode_));
1480 msg = qt_("Welcome to LyX!");
1482 statusBar()->showMessage(msg);
1486 bool GuiView::event(QEvent * e)
1490 // Useful debug code:
1491 //case QEvent::ActivationChange:
1492 //case QEvent::WindowDeactivate:
1493 //case QEvent::Paint:
1494 //case QEvent::Enter:
1495 //case QEvent::Leave:
1496 //case QEvent::HoverEnter:
1497 //case QEvent::HoverLeave:
1498 //case QEvent::HoverMove:
1499 //case QEvent::StatusTip:
1500 //case QEvent::DragEnter:
1501 //case QEvent::DragLeave:
1502 //case QEvent::Drop:
1505 case QEvent::WindowStateChange: {
1506 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1507 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1508 bool result = QMainWindow::event(e);
1509 bool nfstate = (windowState() & Qt::WindowFullScreen);
1510 if (!ofstate && nfstate) {
1511 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1512 // switch to full-screen state
1513 if (lyxrc.full_screen_statusbar)
1514 statusBar()->hide();
1515 if (lyxrc.full_screen_menubar)
1517 if (lyxrc.full_screen_toolbars) {
1518 for (auto const & tb_p : d.toolbars_)
1519 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1520 tb_p.second->hide();
1522 for (int i = 0; i != d.splitter_->count(); ++i)
1523 d.tabWorkArea(i)->setFullScreen(true);
1524 #if QT_VERSION > 0x050903
1525 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1526 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1528 setContentsMargins(-2, -2, -2, -2);
1530 hideDialogs("prefs", nullptr);
1531 } else if (ofstate && !nfstate) {
1532 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1533 // switch back from full-screen state
1534 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1535 statusBar()->show();
1536 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1538 if (lyxrc.full_screen_toolbars) {
1539 for (auto const & tb_p : d.toolbars_)
1540 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1541 tb_p.second->show();
1544 for (int i = 0; i != d.splitter_->count(); ++i)
1545 d.tabWorkArea(i)->setFullScreen(false);
1546 #if QT_VERSION > 0x050903
1547 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1549 setContentsMargins(0, 0, 0, 0);
1553 case QEvent::WindowActivate: {
1554 GuiView * old_view = guiApp->currentView();
1555 if (this == old_view) {
1557 return QMainWindow::event(e);
1559 if (old_view && old_view->currentBufferView()) {
1560 // save current selection to the selection buffer to allow
1561 // middle-button paste in this window.
1562 cap::saveSelection(old_view->currentBufferView()->cursor());
1564 guiApp->setCurrentView(this);
1565 if (d.current_work_area_)
1566 on_currentWorkAreaChanged(d.current_work_area_);
1570 return QMainWindow::event(e);
1573 case QEvent::ShortcutOverride: {
1575 if (isFullScreen() && menuBar()->isHidden()) {
1576 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1577 // FIXME: we should also try to detect special LyX shortcut such as
1578 // Alt-P and Alt-M. Right now there is a hack in
1579 // GuiWorkArea::processKeySym() that hides again the menubar for
1581 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1583 return QMainWindow::event(e);
1586 return QMainWindow::event(e);
1589 case QEvent::ApplicationPaletteChange: {
1590 // runtime switch from/to dark mode
1592 return QMainWindow::event(e);
1596 return QMainWindow::event(e);
1600 void GuiView::resetWindowTitle()
1602 setWindowTitle(qt_("LyX"));
1605 bool GuiView::focusNextPrevChild(bool /*next*/)
1612 bool GuiView::busy() const
1618 void GuiView::setBusy(bool busy)
1620 bool const busy_before = busy_ > 0;
1621 busy ? ++busy_ : --busy_;
1622 if ((busy_ > 0) == busy_before)
1623 // busy state didn't change
1627 QApplication::setOverrideCursor(Qt::WaitCursor);
1630 QApplication::restoreOverrideCursor();
1635 void GuiView::resetCommandExecute()
1637 command_execute_ = false;
1642 double GuiView::pixelRatio() const
1644 #if QT_VERSION >= 0x050000
1645 return qt_scale_factor * devicePixelRatio();
1652 GuiWorkArea * GuiView::workArea(int index)
1654 if (TabWorkArea * twa = d.currentTabWorkArea())
1655 if (index < twa->count())
1656 return twa->workArea(index);
1661 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1663 if (currentWorkArea()
1664 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1665 return currentWorkArea();
1666 if (TabWorkArea * twa = d.currentTabWorkArea())
1667 return twa->workArea(buffer);
1672 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1674 // Automatically create a TabWorkArea if there are none yet.
1675 TabWorkArea * tab_widget = d.splitter_->count()
1676 ? d.currentTabWorkArea() : addTabWorkArea();
1677 return tab_widget->addWorkArea(buffer, *this);
1681 TabWorkArea * GuiView::addTabWorkArea()
1683 TabWorkArea * twa = new TabWorkArea;
1684 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1685 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1686 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1687 this, SLOT(on_lastWorkAreaRemoved()));
1689 d.splitter_->addWidget(twa);
1690 d.stack_widget_->setCurrentWidget(d.splitter_);
1695 GuiWorkArea const * GuiView::currentWorkArea() const
1697 return d.current_work_area_;
1701 GuiWorkArea * GuiView::currentWorkArea()
1703 return d.current_work_area_;
1707 GuiWorkArea const * GuiView::currentMainWorkArea() const
1709 if (!d.currentTabWorkArea())
1711 return d.currentTabWorkArea()->currentWorkArea();
1715 GuiWorkArea * GuiView::currentMainWorkArea()
1717 if (!d.currentTabWorkArea())
1719 return d.currentTabWorkArea()->currentWorkArea();
1723 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1725 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1727 d.current_work_area_ = nullptr;
1729 Q_EMIT bufferViewChanged();
1733 // FIXME: I've no clue why this is here and why it accesses
1734 // theGuiApp()->currentView, which might be 0 (bug 6464).
1735 // See also 27525 (vfr).
1736 if (theGuiApp()->currentView() == this
1737 && theGuiApp()->currentView()->currentWorkArea() == wa)
1740 if (currentBufferView())
1741 cap::saveSelection(currentBufferView()->cursor());
1743 theGuiApp()->setCurrentView(this);
1744 d.current_work_area_ = wa;
1746 // We need to reset this now, because it will need to be
1747 // right if the tabWorkArea gets reset in the for loop. We
1748 // will change it back if we aren't in that case.
1749 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1750 d.current_main_work_area_ = wa;
1752 for (int i = 0; i != d.splitter_->count(); ++i) {
1753 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1754 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1755 << ", Current main wa: " << currentMainWorkArea());
1760 d.current_main_work_area_ = old_cmwa;
1762 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1763 on_currentWorkAreaChanged(wa);
1764 BufferView & bv = wa->bufferView();
1765 bv.cursor().fixIfBroken();
1767 wa->setUpdatesEnabled(true);
1768 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1772 void GuiView::removeWorkArea(GuiWorkArea * wa)
1774 LASSERT(wa, return);
1775 if (wa == d.current_work_area_) {
1777 disconnectBufferView();
1778 d.current_work_area_ = nullptr;
1779 d.current_main_work_area_ = nullptr;
1782 bool found_twa = false;
1783 for (int i = 0; i != d.splitter_->count(); ++i) {
1784 TabWorkArea * twa = d.tabWorkArea(i);
1785 if (twa->removeWorkArea(wa)) {
1786 // Found in this tab group, and deleted the GuiWorkArea.
1788 if (twa->count() != 0) {
1789 if (d.current_work_area_ == nullptr)
1790 // This means that we are closing the current GuiWorkArea, so
1791 // switch to the next GuiWorkArea in the found TabWorkArea.
1792 setCurrentWorkArea(twa->currentWorkArea());
1794 // No more WorkAreas in this tab group, so delete it.
1801 // It is not a tabbed work area (i.e., the search work area), so it
1802 // should be deleted by other means.
1803 LASSERT(found_twa, return);
1805 if (d.current_work_area_ == nullptr) {
1806 if (d.splitter_->count() != 0) {
1807 TabWorkArea * twa = d.currentTabWorkArea();
1808 setCurrentWorkArea(twa->currentWorkArea());
1810 // No more work areas, switch to the background widget.
1811 setCurrentWorkArea(nullptr);
1817 LayoutBox * GuiView::getLayoutDialog() const
1823 void GuiView::updateLayoutList()
1826 d.layout_->updateContents(false);
1830 void GuiView::updateToolbars()
1832 if (d.current_work_area_) {
1834 if (d.current_work_area_->bufferView().cursor().inMathed()
1835 && !d.current_work_area_->bufferView().cursor().inRegexped())
1836 context |= Toolbars::MATH;
1837 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1838 context |= Toolbars::TABLE;
1839 if (currentBufferView()->buffer().areChangesPresent()
1840 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1841 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1842 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1843 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1844 context |= Toolbars::REVIEW;
1845 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1846 context |= Toolbars::MATHMACROTEMPLATE;
1847 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1848 context |= Toolbars::IPA;
1849 if (command_execute_)
1850 context |= Toolbars::MINIBUFFER;
1851 if (minibuffer_focus_) {
1852 context |= Toolbars::MINIBUFFER_FOCUS;
1853 minibuffer_focus_ = false;
1856 for (auto const & tb_p : d.toolbars_)
1857 tb_p.second->update(context);
1859 for (auto const & tb_p : d.toolbars_)
1860 tb_p.second->update();
1864 void GuiView::refillToolbars()
1866 DynamicMenuButton::resetIconCache();
1867 for (auto const & tb_p : d.toolbars_)
1868 tb_p.second->refill();
1872 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1874 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1875 LASSERT(newBuffer, return);
1877 GuiWorkArea * wa = workArea(*newBuffer);
1878 if (wa == nullptr) {
1880 newBuffer->masterBuffer()->updateBuffer();
1882 wa = addWorkArea(*newBuffer);
1883 // scroll to the position when the BufferView was last closed
1884 if (lyxrc.use_lastfilepos) {
1885 LastFilePosSection::FilePos filepos =
1886 theSession().lastFilePos().load(newBuffer->fileName());
1887 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1890 //Disconnect the old buffer...there's no new one.
1893 connectBuffer(*newBuffer);
1894 connectBufferView(wa->bufferView());
1896 setCurrentWorkArea(wa);
1900 void GuiView::connectBuffer(Buffer & buf)
1902 buf.setGuiDelegate(this);
1906 void GuiView::disconnectBuffer()
1908 if (d.current_work_area_)
1909 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1913 void GuiView::connectBufferView(BufferView & bv)
1915 bv.setGuiDelegate(this);
1919 void GuiView::disconnectBufferView()
1921 if (d.current_work_area_)
1922 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1926 void GuiView::errors(string const & error_type, bool from_master)
1928 BufferView const * const bv = currentBufferView();
1932 ErrorList const & el = from_master ?
1933 bv->buffer().masterBuffer()->errorList(error_type) :
1934 bv->buffer().errorList(error_type);
1939 string err = error_type;
1941 err = "from_master|" + error_type;
1942 showDialog("errorlist", err);
1946 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1948 d.toc_models_.updateItem(toqstr(type), dit);
1952 void GuiView::structureChanged()
1954 // This is called from the Buffer, which has no way to ensure that cursors
1955 // in BufferView remain valid.
1956 if (documentBufferView())
1957 documentBufferView()->cursor().sanitize();
1958 // FIXME: This is slightly expensive, though less than the tocBackend update
1959 // (#9880). This also resets the view in the Toc Widget (#6675).
1960 d.toc_models_.reset(documentBufferView());
1961 // Navigator needs more than a simple update in this case. It needs to be
1963 updateDialog("toc", "");
1967 void GuiView::updateDialog(string const & name, string const & sdata)
1969 if (!isDialogVisible(name))
1972 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1973 if (it == d.dialogs_.end())
1976 Dialog * const dialog = it->second.get();
1977 if (dialog->isVisibleView())
1978 dialog->initialiseParams(sdata);
1982 BufferView * GuiView::documentBufferView()
1984 return currentMainWorkArea()
1985 ? ¤tMainWorkArea()->bufferView()
1990 BufferView const * GuiView::documentBufferView() const
1992 return currentMainWorkArea()
1993 ? ¤tMainWorkArea()->bufferView()
1998 BufferView * GuiView::currentBufferView()
2000 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2004 BufferView const * GuiView::currentBufferView() const
2006 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2010 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2011 Buffer const * orig, Buffer * clone)
2013 bool const success = clone->autoSave();
2015 busyBuffers.remove(orig);
2017 ? _("Automatic save done.")
2018 : _("Automatic save failed!");
2022 void GuiView::autoSave()
2024 LYXERR(Debug::INFO, "Running autoSave()");
2026 Buffer * buffer = documentBufferView()
2027 ? &documentBufferView()->buffer() : nullptr;
2029 resetAutosaveTimers();
2033 GuiViewPrivate::busyBuffers.insert(buffer);
2034 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2035 buffer, buffer->cloneBufferOnly());
2036 d.autosave_watcher_.setFuture(f);
2037 resetAutosaveTimers();
2041 void GuiView::resetAutosaveTimers()
2044 d.autosave_timeout_.restart();
2048 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2051 Buffer * buf = currentBufferView()
2052 ? ¤tBufferView()->buffer() : nullptr;
2053 Buffer * doc_buffer = documentBufferView()
2054 ? &(documentBufferView()->buffer()) : nullptr;
2057 /* In LyX/Mac, when a dialog is open, the menus of the
2058 application can still be accessed without giving focus to
2059 the main window. In this case, we want to disable the menu
2060 entries that are buffer-related.
2061 This code must not be used on Linux and Windows, since it
2062 would disable buffer-related entries when hovering over the
2063 menu (see bug #9574).
2065 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2071 // Check whether we need a buffer
2072 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2073 // no, exit directly
2074 flag.message(from_utf8(N_("Command not allowed with"
2075 "out any document open")));
2076 flag.setEnabled(false);
2080 if (cmd.origin() == FuncRequest::TOC) {
2081 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2082 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2083 flag.setEnabled(false);
2087 switch(cmd.action()) {
2088 case LFUN_BUFFER_IMPORT:
2091 case LFUN_MASTER_BUFFER_EXPORT:
2093 && (doc_buffer->parent() != nullptr
2094 || doc_buffer->hasChildren())
2095 && !d.processing_thread_watcher_.isRunning()
2096 // this launches a dialog, which would be in the wrong Buffer
2097 && !(::lyx::operator==(cmd.argument(), "custom"));
2100 case LFUN_MASTER_BUFFER_UPDATE:
2101 case LFUN_MASTER_BUFFER_VIEW:
2103 && (doc_buffer->parent() != nullptr
2104 || doc_buffer->hasChildren())
2105 && !d.processing_thread_watcher_.isRunning();
2108 case LFUN_BUFFER_UPDATE:
2109 case LFUN_BUFFER_VIEW: {
2110 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2114 string format = to_utf8(cmd.argument());
2115 if (cmd.argument().empty())
2116 format = doc_buffer->params().getDefaultOutputFormat();
2117 enable = doc_buffer->params().isExportable(format, true);
2121 case LFUN_BUFFER_RELOAD:
2122 enable = doc_buffer && !doc_buffer->isUnnamed()
2123 && doc_buffer->fileName().exists()
2124 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2127 case LFUN_BUFFER_RESET_EXPORT:
2128 enable = doc_buffer != nullptr;
2131 case LFUN_BUFFER_CHILD_OPEN:
2132 enable = doc_buffer != nullptr;
2135 case LFUN_MASTER_BUFFER_FORALL: {
2136 if (doc_buffer == nullptr) {
2137 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2141 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2142 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2143 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2148 for (Buffer * buf : doc_buffer->allRelatives()) {
2149 GuiWorkArea * wa = workArea(*buf);
2152 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2153 enable = flag.enabled();
2160 case LFUN_BUFFER_WRITE:
2161 enable = doc_buffer && (doc_buffer->isUnnamed()
2162 || (!doc_buffer->isClean()
2163 || cmd.argument() == "force"));
2166 //FIXME: This LFUN should be moved to GuiApplication.
2167 case LFUN_BUFFER_WRITE_ALL: {
2168 // We enable the command only if there are some modified buffers
2169 Buffer * first = theBufferList().first();
2174 // We cannot use a for loop as the buffer list is a cycle.
2176 if (!b->isClean()) {
2180 b = theBufferList().next(b);
2181 } while (b != first);
2185 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2186 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2189 case LFUN_BUFFER_EXPORT: {
2190 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2194 return doc_buffer->getStatus(cmd, flag);
2197 case LFUN_BUFFER_EXPORT_AS:
2198 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2203 case LFUN_BUFFER_WRITE_AS:
2204 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2205 enable = doc_buffer != nullptr;
2208 case LFUN_EXPORT_CANCEL:
2209 enable = d.processing_thread_watcher_.isRunning();
2212 case LFUN_BUFFER_CLOSE:
2213 case LFUN_VIEW_CLOSE:
2214 enable = doc_buffer != nullptr;
2217 case LFUN_BUFFER_CLOSE_ALL:
2218 enable = theBufferList().last() != theBufferList().first();
2221 case LFUN_BUFFER_CHKTEX: {
2222 // hide if we have no checktex command
2223 if (lyxrc.chktex_command.empty()) {
2224 flag.setUnknown(true);
2228 if (!doc_buffer || !doc_buffer->params().isLatex()
2229 || d.processing_thread_watcher_.isRunning()) {
2230 // grey out, don't hide
2238 case LFUN_VIEW_SPLIT:
2239 if (cmd.getArg(0) == "vertical")
2240 enable = doc_buffer && (d.splitter_->count() == 1 ||
2241 d.splitter_->orientation() == Qt::Vertical);
2243 enable = doc_buffer && (d.splitter_->count() == 1 ||
2244 d.splitter_->orientation() == Qt::Horizontal);
2247 case LFUN_TAB_GROUP_CLOSE:
2248 enable = d.tabWorkAreaCount() > 1;
2251 case LFUN_DEVEL_MODE_TOGGLE:
2252 flag.setOnOff(devel_mode_);
2255 case LFUN_TOOLBAR_SET: {
2256 string const name = cmd.getArg(0);
2257 string const state = cmd.getArg(1);
2258 if (name.empty() || state.empty()) {
2260 docstring const msg =
2261 _("Function toolbar-set requires two arguments!");
2265 if (state != "on" && state != "off" && state != "auto") {
2267 docstring const msg =
2268 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2273 if (GuiToolbar * t = toolbar(name)) {
2274 bool const autovis = t->visibility() & Toolbars::AUTO;
2276 flag.setOnOff(t->isVisible() && !autovis);
2277 else if (state == "off")
2278 flag.setOnOff(!t->isVisible() && !autovis);
2279 else if (state == "auto")
2280 flag.setOnOff(autovis);
2283 docstring const msg =
2284 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2290 case LFUN_TOOLBAR_TOGGLE: {
2291 string const name = cmd.getArg(0);
2292 if (GuiToolbar * t = toolbar(name))
2293 flag.setOnOff(t->isVisible());
2296 docstring const msg =
2297 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2303 case LFUN_TOOLBAR_MOVABLE: {
2304 string const name = cmd.getArg(0);
2305 // use negation since locked == !movable
2307 // toolbar name * locks all toolbars
2308 flag.setOnOff(!toolbarsMovable_);
2309 else if (GuiToolbar * t = toolbar(name))
2310 flag.setOnOff(!(t->isMovable()));
2313 docstring const msg =
2314 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2320 case LFUN_ICON_SIZE:
2321 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2324 case LFUN_DROP_LAYOUTS_CHOICE:
2325 enable = buf != nullptr;
2328 case LFUN_UI_TOGGLE:
2329 if (cmd.argument() == "zoomslider") {
2330 enable = doc_buffer;
2331 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2333 flag.setOnOff(isFullScreen());
2336 case LFUN_DIALOG_DISCONNECT_INSET:
2339 case LFUN_DIALOG_HIDE:
2340 // FIXME: should we check if the dialog is shown?
2343 case LFUN_DIALOG_TOGGLE:
2344 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2347 case LFUN_DIALOG_SHOW: {
2348 string const name = cmd.getArg(0);
2350 enable = name == "aboutlyx"
2351 || name == "file" //FIXME: should be removed.
2352 || name == "lyxfiles"
2354 || name == "texinfo"
2355 || name == "progress"
2356 || name == "compare";
2357 else if (name == "character" || name == "symbols"
2358 || name == "mathdelimiter" || name == "mathmatrix") {
2359 if (!buf || buf->isReadonly())
2362 Cursor const & cur = currentBufferView()->cursor();
2363 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2366 else if (name == "latexlog")
2367 enable = FileName(doc_buffer->logName()).isReadableFile();
2368 else if (name == "spellchecker")
2369 enable = theSpellChecker()
2370 && !doc_buffer->text().empty();
2371 else if (name == "vclog")
2372 enable = doc_buffer->lyxvc().inUse();
2376 case LFUN_DIALOG_UPDATE: {
2377 string const name = cmd.getArg(0);
2379 enable = name == "prefs";
2383 case LFUN_COMMAND_EXECUTE:
2385 case LFUN_MENU_OPEN:
2386 // Nothing to check.
2389 case LFUN_COMPLETION_INLINE:
2390 if (!d.current_work_area_
2391 || !d.current_work_area_->completer().inlinePossible(
2392 currentBufferView()->cursor()))
2396 case LFUN_COMPLETION_POPUP:
2397 if (!d.current_work_area_
2398 || !d.current_work_area_->completer().popupPossible(
2399 currentBufferView()->cursor()))
2404 if (!d.current_work_area_
2405 || !d.current_work_area_->completer().inlinePossible(
2406 currentBufferView()->cursor()))
2410 case LFUN_COMPLETION_ACCEPT:
2411 if (!d.current_work_area_
2412 || (!d.current_work_area_->completer().popupVisible()
2413 && !d.current_work_area_->completer().inlineVisible()
2414 && !d.current_work_area_->completer().completionAvailable()))
2418 case LFUN_COMPLETION_CANCEL:
2419 if (!d.current_work_area_
2420 || (!d.current_work_area_->completer().popupVisible()
2421 && !d.current_work_area_->completer().inlineVisible()))
2425 case LFUN_BUFFER_ZOOM_OUT:
2426 case LFUN_BUFFER_ZOOM_IN: {
2427 // only diff between these two is that the default for ZOOM_OUT
2429 bool const neg_zoom =
2430 convert<int>(cmd.argument()) < 0 ||
2431 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2432 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2433 docstring const msg =
2434 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2438 enable = doc_buffer;
2442 case LFUN_BUFFER_ZOOM: {
2443 bool const less_than_min_zoom =
2444 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2445 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2446 docstring const msg =
2447 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2450 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2453 enable = doc_buffer;
2457 case LFUN_BUFFER_MOVE_NEXT:
2458 case LFUN_BUFFER_MOVE_PREVIOUS:
2459 // we do not cycle when moving
2460 case LFUN_BUFFER_NEXT:
2461 case LFUN_BUFFER_PREVIOUS:
2462 // because we cycle, it doesn't matter whether on first or last
2463 enable = (d.currentTabWorkArea()->count() > 1);
2465 case LFUN_BUFFER_SWITCH:
2466 // toggle on the current buffer, but do not toggle off
2467 // the other ones (is that a good idea?)
2469 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2470 flag.setOnOff(true);
2473 case LFUN_VC_REGISTER:
2474 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2476 case LFUN_VC_RENAME:
2477 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2480 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2482 case LFUN_VC_CHECK_IN:
2483 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2485 case LFUN_VC_CHECK_OUT:
2486 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2488 case LFUN_VC_LOCKING_TOGGLE:
2489 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2490 && doc_buffer->lyxvc().lockingToggleEnabled();
2491 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2493 case LFUN_VC_REVERT:
2494 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2495 && !doc_buffer->hasReadonlyFlag();
2497 case LFUN_VC_UNDO_LAST:
2498 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2500 case LFUN_VC_REPO_UPDATE:
2501 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2503 case LFUN_VC_COMMAND: {
2504 if (cmd.argument().empty())
2506 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2510 case LFUN_VC_COMPARE:
2511 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2514 case LFUN_SERVER_GOTO_FILE_ROW:
2515 case LFUN_LYX_ACTIVATE:
2516 case LFUN_WINDOW_RAISE:
2518 case LFUN_FORWARD_SEARCH:
2519 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2522 case LFUN_FILE_INSERT_PLAINTEXT:
2523 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2524 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2527 case LFUN_SPELLING_CONTINUOUSLY:
2528 flag.setOnOff(lyxrc.spellcheck_continuously);
2531 case LFUN_CITATION_OPEN:
2540 flag.setEnabled(false);
2546 static FileName selectTemplateFile()
2548 FileDialog dlg(qt_("Select template file"));
2549 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2550 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2552 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2553 QStringList(qt_("LyX Documents (*.lyx)")));
2555 if (result.first == FileDialog::Later)
2557 if (result.second.isEmpty())
2559 return FileName(fromqstr(result.second));
2563 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2567 Buffer * newBuffer = nullptr;
2569 newBuffer = checkAndLoadLyXFile(filename);
2570 } catch (ExceptionMessage const &) {
2577 message(_("Document not loaded."));
2581 setBuffer(newBuffer);
2582 newBuffer->errors("Parse");
2585 theSession().lastFiles().add(filename);
2586 theSession().writeFile();
2593 void GuiView::openDocument(string const & fname)
2595 string initpath = lyxrc.document_path;
2597 if (documentBufferView()) {
2598 string const trypath = documentBufferView()->buffer().filePath();
2599 // If directory is writeable, use this as default.
2600 if (FileName(trypath).isDirWritable())
2606 if (fname.empty()) {
2607 FileDialog dlg(qt_("Select document to open"));
2608 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2609 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2611 QStringList const filter({
2612 qt_("LyX Documents (*.lyx)"),
2613 qt_("LyX Document Backups (*.lyx~)"),
2614 qt_("All Files (*.*)")
2616 FileDialog::Result result =
2617 dlg.open(toqstr(initpath), filter);
2619 if (result.first == FileDialog::Later)
2622 filename = fromqstr(result.second);
2624 // check selected filename
2625 if (filename.empty()) {
2626 message(_("Canceled."));
2632 // get absolute path of file and add ".lyx" to the filename if
2634 FileName const fullname =
2635 fileSearch(string(), filename, "lyx", support::may_not_exist);
2636 if (!fullname.empty())
2637 filename = fullname.absFileName();
2639 if (!fullname.onlyPath().isDirectory()) {
2640 Alert::warning(_("Invalid filename"),
2641 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2642 from_utf8(fullname.absFileName())));
2646 // if the file doesn't exist and isn't already open (bug 6645),
2647 // let the user create one
2648 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2649 !LyXVC::file_not_found_hook(fullname)) {
2650 // the user specifically chose this name. Believe him.
2651 Buffer * const b = newFile(filename, string(), true);
2657 docstring const disp_fn = makeDisplayPath(filename);
2658 message(bformat(_("Opening document %1$s..."), disp_fn));
2661 Buffer * buf = loadDocument(fullname);
2663 str2 = bformat(_("Document %1$s opened."), disp_fn);
2664 if (buf->lyxvc().inUse())
2665 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2666 " " + _("Version control detected.");
2668 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2673 // FIXME: clean that
2674 static bool import(GuiView * lv, FileName const & filename,
2675 string const & format, ErrorList & errorList)
2677 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2679 string loader_format;
2680 vector<string> loaders = theConverters().loaders();
2681 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2682 for (string const & loader : loaders) {
2683 if (!theConverters().isReachable(format, loader))
2686 string const tofile =
2687 support::changeExtension(filename.absFileName(),
2688 theFormats().extension(loader));
2689 if (theConverters().convert(nullptr, filename, FileName(tofile),
2690 filename, format, loader, errorList) != Converters::SUCCESS)
2692 loader_format = loader;
2695 if (loader_format.empty()) {
2696 frontend::Alert::error(_("Couldn't import file"),
2697 bformat(_("No information for importing the format %1$s."),
2698 translateIfPossible(theFormats().prettyName(format))));
2702 loader_format = format;
2704 if (loader_format == "lyx") {
2705 Buffer * buf = lv->loadDocument(lyxfile);
2709 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2713 bool as_paragraphs = loader_format == "textparagraph";
2714 string filename2 = (loader_format == format) ? filename.absFileName()
2715 : support::changeExtension(filename.absFileName(),
2716 theFormats().extension(loader_format));
2717 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2719 guiApp->setCurrentView(lv);
2720 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2727 void GuiView::importDocument(string const & argument)
2730 string filename = split(argument, format, ' ');
2732 LYXERR(Debug::INFO, format << " file: " << filename);
2734 // need user interaction
2735 if (filename.empty()) {
2736 string initpath = lyxrc.document_path;
2737 if (documentBufferView()) {
2738 string const trypath = documentBufferView()->buffer().filePath();
2739 // If directory is writeable, use this as default.
2740 if (FileName(trypath).isDirWritable())
2744 docstring const text = bformat(_("Select %1$s file to import"),
2745 translateIfPossible(theFormats().prettyName(format)));
2747 FileDialog dlg(toqstr(text));
2748 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2749 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2751 docstring filter = translateIfPossible(theFormats().prettyName(format));
2754 filter += from_utf8(theFormats().extensions(format));
2757 FileDialog::Result result =
2758 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2760 if (result.first == FileDialog::Later)
2763 filename = fromqstr(result.second);
2765 // check selected filename
2766 if (filename.empty())
2767 message(_("Canceled."));
2770 if (filename.empty())
2773 // get absolute path of file
2774 FileName const fullname(support::makeAbsPath(filename));
2776 // Can happen if the user entered a path into the dialog
2778 if (fullname.onlyFileName().empty()) {
2779 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2780 "Aborting import."),
2781 from_utf8(fullname.absFileName()));
2782 frontend::Alert::error(_("File name error"), msg);
2783 message(_("Canceled."));
2788 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2790 // Check if the document already is open
2791 Buffer * buf = theBufferList().getBuffer(lyxfile);
2794 if (!closeBuffer()) {
2795 message(_("Canceled."));
2800 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2802 // if the file exists already, and we didn't do
2803 // -i lyx thefile.lyx, warn
2804 if (lyxfile.exists() && fullname != lyxfile) {
2806 docstring text = bformat(_("The document %1$s already exists.\n\n"
2807 "Do you want to overwrite that document?"), displaypath);
2808 int const ret = Alert::prompt(_("Overwrite document?"),
2809 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2812 message(_("Canceled."));
2817 message(bformat(_("Importing %1$s..."), displaypath));
2818 ErrorList errorList;
2819 if (import(this, fullname, format, errorList))
2820 message(_("imported."));
2822 message(_("file not imported!"));
2824 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2828 void GuiView::newDocument(string const & filename, string templatefile,
2831 FileName initpath(lyxrc.document_path);
2832 if (documentBufferView()) {
2833 FileName const trypath(documentBufferView()->buffer().filePath());
2834 // If directory is writeable, use this as default.
2835 if (trypath.isDirWritable())
2839 if (from_template) {
2840 if (templatefile.empty())
2841 templatefile = selectTemplateFile().absFileName();
2842 if (templatefile.empty())
2847 if (filename.empty())
2848 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2850 b = newFile(filename, templatefile, true);
2855 // If no new document could be created, it is unsure
2856 // whether there is a valid BufferView.
2857 if (currentBufferView())
2858 // Ensure the cursor is correctly positioned on screen.
2859 currentBufferView()->showCursor();
2863 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2865 BufferView * bv = documentBufferView();
2870 FileName filename(to_utf8(fname));
2871 if (filename.empty()) {
2872 // Launch a file browser
2874 string initpath = lyxrc.document_path;
2875 string const trypath = bv->buffer().filePath();
2876 // If directory is writeable, use this as default.
2877 if (FileName(trypath).isDirWritable())
2881 FileDialog dlg(qt_("Select LyX document to insert"));
2882 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2883 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2885 FileDialog::Result result = dlg.open(toqstr(initpath),
2886 QStringList(qt_("LyX Documents (*.lyx)")));
2888 if (result.first == FileDialog::Later)
2892 filename.set(fromqstr(result.second));
2894 // check selected filename
2895 if (filename.empty()) {
2896 // emit message signal.
2897 message(_("Canceled."));
2902 bv->insertLyXFile(filename, ignorelang);
2903 bv->buffer().errors("Parse");
2908 string const GuiView::getTemplatesPath(Buffer & b)
2910 // We start off with the user's templates path
2911 string result = addPath(package().user_support().absFileName(), "templates");
2912 // Check for the document language
2913 string const langcode = b.params().language->code();
2914 string const shortcode = langcode.substr(0, 2);
2915 if (!langcode.empty() && shortcode != "en") {
2916 string subpath = addPath(result, shortcode);
2917 string subpath_long = addPath(result, langcode);
2918 // If we have a subdirectory for the language already,
2920 FileName sp = FileName(subpath);
2921 if (sp.isDirectory())
2923 else if (FileName(subpath_long).isDirectory())
2924 result = subpath_long;
2926 // Ask whether we should create such a subdirectory
2927 docstring const text =
2928 bformat(_("It is suggested to save the template in a subdirectory\n"
2929 "appropriate to the document language (%1$s).\n"
2930 "This subdirectory does not exists yet.\n"
2931 "Do you want to create it?"),
2932 _(b.params().language->display()));
2933 if (Alert::prompt(_("Create Language Directory?"),
2934 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2935 // If the user agreed, we try to create it and report if this failed.
2936 if (!sp.createDirectory(0777))
2937 Alert::error(_("Subdirectory creation failed!"),
2938 _("Could not create subdirectory.\n"
2939 "The template will be saved in the parent directory."));
2945 // Do we have a layout category?
2946 string const cat = b.params().baseClass() ?
2947 b.params().baseClass()->category()
2950 string subpath = addPath(result, cat);
2951 // If we have a subdirectory for the category already,
2953 FileName sp = FileName(subpath);
2954 if (sp.isDirectory())
2957 // Ask whether we should create such a subdirectory
2958 docstring const text =
2959 bformat(_("It is suggested to save the template in a subdirectory\n"
2960 "appropriate to the layout category (%1$s).\n"
2961 "This subdirectory does not exists yet.\n"
2962 "Do you want to create it?"),
2964 if (Alert::prompt(_("Create Category Directory?"),
2965 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2966 // If the user agreed, we try to create it and report if this failed.
2967 if (!sp.createDirectory(0777))
2968 Alert::error(_("Subdirectory creation failed!"),
2969 _("Could not create subdirectory.\n"
2970 "The template will be saved in the parent directory."));
2980 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2982 FileName fname = b.fileName();
2983 FileName const oldname = fname;
2984 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2986 if (!newname.empty()) {
2989 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2991 fname = support::makeAbsPath(to_utf8(newname),
2992 oldname.onlyPath().absFileName());
2994 // Switch to this Buffer.
2997 // No argument? Ask user through dialog.
2999 QString const title = as_template ? qt_("Choose a filename to save template as")
3000 : qt_("Choose a filename to save document as");
3001 FileDialog dlg(title);
3002 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3003 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3005 if (!isLyXFileName(fname.absFileName()))
3006 fname.changeExtension(".lyx");
3008 string const path = as_template ?
3010 : fname.onlyPath().absFileName();
3011 FileDialog::Result result =
3012 dlg.save(toqstr(path),
3013 QStringList(qt_("LyX Documents (*.lyx)")),
3014 toqstr(fname.onlyFileName()));
3016 if (result.first == FileDialog::Later)
3019 fname.set(fromqstr(result.second));
3024 if (!isLyXFileName(fname.absFileName()))
3025 fname.changeExtension(".lyx");
3028 // fname is now the new Buffer location.
3030 // if there is already a Buffer open with this name, we do not want
3031 // to have another one. (the second test makes sure we're not just
3032 // trying to overwrite ourselves, which is fine.)
3033 if (theBufferList().exists(fname) && fname != oldname
3034 && theBufferList().getBuffer(fname) != &b) {
3035 docstring const text =
3036 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3037 "Please close it before attempting to overwrite it.\n"
3038 "Do you want to choose a new filename?"),
3039 from_utf8(fname.absFileName()));
3040 int const ret = Alert::prompt(_("Chosen File Already Open"),
3041 text, 0, 1, _("&Rename"), _("&Cancel"));
3043 case 0: return renameBuffer(b, docstring(), kind);
3044 case 1: return false;
3049 bool const existsLocal = fname.exists();
3050 bool const existsInVC = LyXVC::fileInVC(fname);
3051 if (existsLocal || existsInVC) {
3052 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3053 if (kind != LV_WRITE_AS && existsInVC) {
3054 // renaming to a name that is already in VC
3056 docstring text = bformat(_("The document %1$s "
3057 "is already registered.\n\n"
3058 "Do you want to choose a new name?"),
3060 docstring const title = (kind == LV_VC_RENAME) ?
3061 _("Rename document?") : _("Copy document?");
3062 docstring const button = (kind == LV_VC_RENAME) ?
3063 _("&Rename") : _("&Copy");
3064 int const ret = Alert::prompt(title, text, 0, 1,
3065 button, _("&Cancel"));
3067 case 0: return renameBuffer(b, docstring(), kind);
3068 case 1: return false;
3073 docstring text = bformat(_("The document %1$s "
3074 "already exists.\n\n"
3075 "Do you want to overwrite that document?"),
3077 int const ret = Alert::prompt(_("Overwrite document?"),
3078 text, 0, 2, _("&Overwrite"),
3079 _("&Rename"), _("&Cancel"));
3082 case 1: return renameBuffer(b, docstring(), kind);
3083 case 2: return false;
3089 case LV_VC_RENAME: {
3090 string msg = b.lyxvc().rename(fname);
3093 message(from_utf8(msg));
3097 string msg = b.lyxvc().copy(fname);
3100 message(from_utf8(msg));
3104 case LV_WRITE_AS_TEMPLATE:
3107 // LyXVC created the file already in case of LV_VC_RENAME or
3108 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3109 // relative paths of included stuff right if we moved e.g. from
3110 // /a/b.lyx to /a/c/b.lyx.
3112 bool const saved = saveBuffer(b, fname);
3119 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3121 FileName fname = b.fileName();
3123 FileDialog dlg(qt_("Choose a filename to export the document as"));
3124 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3127 QString const anyformat = qt_("Guess from extension (*.*)");
3130 vector<Format const *> export_formats;
3131 for (Format const & f : theFormats())
3132 if (f.documentFormat())
3133 export_formats.push_back(&f);
3134 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3135 map<QString, string> fmap;
3138 for (Format const * f : export_formats) {
3139 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3140 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3142 from_ascii(f->extension())));
3143 types << loc_filter;
3144 fmap[loc_filter] = f->name();
3145 if (from_ascii(f->name()) == iformat) {
3146 filter = loc_filter;
3147 ext = f->extension();
3150 string ofname = fname.onlyFileName();
3152 ofname = support::changeExtension(ofname, ext);
3153 FileDialog::Result result =
3154 dlg.save(toqstr(fname.onlyPath().absFileName()),
3158 if (result.first != FileDialog::Chosen)
3162 fname.set(fromqstr(result.second));
3163 if (filter == anyformat)
3164 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3166 fmt_name = fmap[filter];
3167 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3168 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3170 if (fmt_name.empty() || fname.empty())
3173 // fname is now the new Buffer location.
3174 if (FileName(fname).exists()) {
3175 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3176 docstring text = bformat(_("The document %1$s already "
3177 "exists.\n\nDo you want to "
3178 "overwrite that document?"),
3180 int const ret = Alert::prompt(_("Overwrite document?"),
3181 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3184 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3185 case 2: return false;
3189 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3192 return dr.dispatched();
3196 bool GuiView::saveBuffer(Buffer & b)
3198 return saveBuffer(b, FileName());
3202 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3204 if (workArea(b) && workArea(b)->inDialogMode())
3207 if (fn.empty() && b.isUnnamed())
3208 return renameBuffer(b, docstring());
3210 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3212 theSession().lastFiles().add(b.fileName());
3213 theSession().writeFile();
3217 // Switch to this Buffer.
3220 // FIXME: we don't tell the user *WHY* the save failed !!
3221 docstring const file = makeDisplayPath(b.absFileName(), 30);
3222 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3223 "Do you want to rename the document and "
3224 "try again?"), file);
3225 int const ret = Alert::prompt(_("Rename and save?"),
3226 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3229 if (!renameBuffer(b, docstring()))
3238 return saveBuffer(b, fn);
3242 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3244 return closeWorkArea(wa, false);
3248 // We only want to close the buffer if it is not visible in other workareas
3249 // of the same view, nor in other views, and if this is not a child
3250 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3252 Buffer & buf = wa->bufferView().buffer();
3254 bool last_wa = d.countWorkAreasOf(buf) == 1
3255 && !inOtherView(buf) && !buf.parent();
3257 bool close_buffer = last_wa;
3260 if (lyxrc.close_buffer_with_last_view == "yes")
3262 else if (lyxrc.close_buffer_with_last_view == "no")
3263 close_buffer = false;
3266 if (buf.isUnnamed())
3267 file = from_utf8(buf.fileName().onlyFileName());
3269 file = buf.fileName().displayName(30);
3270 docstring const text = bformat(
3271 _("Last view on document %1$s is being closed.\n"
3272 "Would you like to close or hide the document?\n"
3274 "Hidden documents can be displayed back through\n"
3275 "the menu: View->Hidden->...\n"
3277 "To remove this question, set your preference in:\n"
3278 " Tools->Preferences->Look&Feel->UserInterface\n"
3280 int ret = Alert::prompt(_("Close or hide document?"),
3281 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3284 close_buffer = (ret == 0);
3288 return closeWorkArea(wa, close_buffer);
3292 bool GuiView::closeBuffer()
3294 GuiWorkArea * wa = currentMainWorkArea();
3295 // coverity complained about this
3296 // it seems unnecessary, but perhaps is worth the check
3297 LASSERT(wa, return false);
3299 setCurrentWorkArea(wa);
3300 Buffer & buf = wa->bufferView().buffer();
3301 return closeWorkArea(wa, !buf.parent());
3305 void GuiView::writeSession() const {
3306 GuiWorkArea const * active_wa = currentMainWorkArea();
3307 for (int i = 0; i < d.splitter_->count(); ++i) {
3308 TabWorkArea * twa = d.tabWorkArea(i);
3309 for (int j = 0; j < twa->count(); ++j) {
3310 GuiWorkArea * wa = twa->workArea(j);
3311 Buffer & buf = wa->bufferView().buffer();
3312 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3318 bool GuiView::closeBufferAll()
3321 for (auto & buf : theBufferList()) {
3322 if (!saveBufferIfNeeded(*buf, false)) {
3323 // Closing has been cancelled, so abort.
3328 // Close the workareas in all other views
3329 QList<int> const ids = guiApp->viewIds();
3330 for (int i = 0; i != ids.size(); ++i) {
3331 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3335 // Close our own workareas
3336 if (!closeWorkAreaAll())
3343 bool GuiView::closeWorkAreaAll()
3345 setCurrentWorkArea(currentMainWorkArea());
3347 // We might be in a situation that there is still a tabWorkArea, but
3348 // there are no tabs anymore. This can happen when we get here after a
3349 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3350 // many TabWorkArea's have no documents anymore.
3353 // We have to call count() each time, because it can happen that
3354 // more than one splitter will disappear in one iteration (bug 5998).
3355 while (d.splitter_->count() > empty_twa) {
3356 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3358 if (twa->count() == 0)
3361 setCurrentWorkArea(twa->currentWorkArea());
3362 if (!closeTabWorkArea(twa))
3370 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3375 Buffer & buf = wa->bufferView().buffer();
3377 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3378 Alert::warning(_("Close document"),
3379 _("Document could not be closed because it is being processed by LyX."));
3384 return closeBuffer(buf);
3386 if (!inMultiTabs(wa))
3387 if (!saveBufferIfNeeded(buf, true))
3395 bool GuiView::closeBuffer(Buffer & buf)
3397 bool success = true;
3398 for (Buffer * child_buf : buf.getChildren()) {
3399 if (theBufferList().isOthersChild(&buf, child_buf)) {
3400 child_buf->setParent(nullptr);
3404 // FIXME: should we look in other tabworkareas?
3405 // ANSWER: I don't think so. I've tested, and if the child is
3406 // open in some other window, it closes without a problem.
3407 GuiWorkArea * child_wa = workArea(*child_buf);
3410 // If we are in a close_event all children will be closed in some time,
3411 // so no need to do it here. This will ensure that the children end up
3412 // in the session file in the correct order. If we close the master
3413 // buffer, we can close or release the child buffers here too.
3415 success = closeWorkArea(child_wa, true);
3419 // In this case the child buffer is open but hidden.
3420 // Even in this case, children can be dirty (e.g.,
3421 // after a label change in the master, see #11405).
3422 // Therefore, check this
3423 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3424 // If we are in a close_event all children will be closed in some time,
3425 // so no need to do it here. This will ensure that the children end up
3426 // in the session file in the correct order. If we close the master
3427 // buffer, we can close or release the child buffers here too.
3430 // Save dirty buffers also if closing_!
3431 if (saveBufferIfNeeded(*child_buf, false)) {
3432 child_buf->removeAutosaveFile();
3433 theBufferList().release(child_buf);
3435 // Saving of dirty children has been cancelled.
3436 // Cancel the whole process.
3443 // goto bookmark to update bookmark pit.
3444 // FIXME: we should update only the bookmarks related to this buffer!
3445 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3446 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3447 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3448 guiApp->gotoBookmark(i, false, false);
3450 if (saveBufferIfNeeded(buf, false)) {
3451 buf.removeAutosaveFile();
3452 theBufferList().release(&buf);
3456 // open all children again to avoid a crash because of dangling
3457 // pointers (bug 6603)
3463 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3465 while (twa == d.currentTabWorkArea()) {
3466 twa->setCurrentIndex(twa->count() - 1);
3468 GuiWorkArea * wa = twa->currentWorkArea();
3469 Buffer & b = wa->bufferView().buffer();
3471 // We only want to close the buffer if the same buffer is not visible
3472 // in another view, and if this is not a child and if we are closing
3473 // a view (not a tabgroup).
3474 bool const close_buffer =
3475 !inOtherView(b) && !b.parent() && closing_;
3477 if (!closeWorkArea(wa, close_buffer))
3484 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3486 if (buf.isClean() || buf.paragraphs().empty())
3489 // Switch to this Buffer.
3495 if (buf.isUnnamed()) {
3496 file = from_utf8(buf.fileName().onlyFileName());
3499 FileName filename = buf.fileName();
3501 file = filename.displayName(30);
3502 exists = filename.exists();
3505 // Bring this window to top before asking questions.
3510 if (hiding && buf.isUnnamed()) {
3511 docstring const text = bformat(_("The document %1$s has not been "
3512 "saved yet.\n\nDo you want to save "
3513 "the document?"), file);
3514 ret = Alert::prompt(_("Save new document?"),
3515 text, 0, 1, _("&Save"), _("&Cancel"));
3519 docstring const text = exists ?
3520 bformat(_("The document %1$s has unsaved changes."
3521 "\n\nDo you want to save the document or "
3522 "discard the changes?"), file) :
3523 bformat(_("The document %1$s has not been saved yet."
3524 "\n\nDo you want to save the document or "
3525 "discard it entirely?"), file);
3526 docstring const title = exists ?
3527 _("Save changed document?") : _("Save document?");
3528 ret = Alert::prompt(title, text, 0, 2,
3529 _("&Save"), _("&Discard"), _("&Cancel"));
3534 if (!saveBuffer(buf))
3538 // If we crash after this we could have no autosave file
3539 // but I guess this is really improbable (Jug).
3540 // Sometimes improbable things happen:
3541 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3542 // buf.removeAutosaveFile();
3544 // revert all changes
3555 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3557 Buffer & buf = wa->bufferView().buffer();
3559 for (int i = 0; i != d.splitter_->count(); ++i) {
3560 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3561 if (wa_ && wa_ != wa)
3564 return inOtherView(buf);
3568 bool GuiView::inOtherView(Buffer & buf)
3570 QList<int> const ids = guiApp->viewIds();
3572 for (int i = 0; i != ids.size(); ++i) {
3576 if (guiApp->view(ids[i]).workArea(buf))
3583 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3585 if (!documentBufferView())
3588 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3589 Buffer * const curbuf = &documentBufferView()->buffer();
3590 int nwa = twa->count();
3591 for (int i = 0; i < nwa; ++i) {
3592 if (&workArea(i)->bufferView().buffer() == curbuf) {
3594 if (np == NEXTBUFFER)
3595 next_index = (i == nwa - 1 ? 0 : i + 1);
3597 next_index = (i == 0 ? nwa - 1 : i - 1);
3599 twa->moveTab(i, next_index);
3601 setBuffer(&workArea(next_index)->bufferView().buffer());
3609 /// make sure the document is saved
3610 static bool ensureBufferClean(Buffer * buffer)
3612 LASSERT(buffer, return false);
3613 if (buffer->isClean() && !buffer->isUnnamed())
3616 docstring const file = buffer->fileName().displayName(30);
3619 if (!buffer->isUnnamed()) {
3620 text = bformat(_("The document %1$s has unsaved "
3621 "changes.\n\nDo you want to save "
3622 "the document?"), file);
3623 title = _("Save changed document?");
3626 text = bformat(_("The document %1$s has not been "
3627 "saved yet.\n\nDo you want to save "
3628 "the document?"), file);
3629 title = _("Save new document?");
3631 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3634 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3636 return buffer->isClean() && !buffer->isUnnamed();
3640 bool GuiView::reloadBuffer(Buffer & buf)
3642 currentBufferView()->cursor().reset();
3643 Buffer::ReadStatus status = buf.reload();
3644 return status == Buffer::ReadSuccess;
3648 void GuiView::checkExternallyModifiedBuffers()
3650 for (Buffer * buf : theBufferList()) {
3651 if (buf->fileName().exists() && buf->isChecksumModified()) {
3652 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3653 " Reload now? Any local changes will be lost."),
3654 from_utf8(buf->absFileName()));
3655 int const ret = Alert::prompt(_("Reload externally changed document?"),
3656 text, 0, 1, _("&Reload"), _("&Cancel"));
3664 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3666 Buffer * buffer = documentBufferView()
3667 ? &(documentBufferView()->buffer()) : nullptr;
3669 switch (cmd.action()) {
3670 case LFUN_VC_REGISTER:
3671 if (!buffer || !ensureBufferClean(buffer))
3673 if (!buffer->lyxvc().inUse()) {
3674 if (buffer->lyxvc().registrer()) {
3675 reloadBuffer(*buffer);
3676 dr.clearMessageUpdate();
3681 case LFUN_VC_RENAME:
3682 case LFUN_VC_COPY: {
3683 if (!buffer || !ensureBufferClean(buffer))
3685 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3686 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3687 // Some changes are not yet committed.
3688 // We test here and not in getStatus(), since
3689 // this test is expensive.
3691 LyXVC::CommandResult ret =
3692 buffer->lyxvc().checkIn(log);
3694 if (ret == LyXVC::ErrorCommand ||
3695 ret == LyXVC::VCSuccess)
3696 reloadBuffer(*buffer);
3697 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3698 frontend::Alert::error(
3699 _("Revision control error."),
3700 _("Document could not be checked in."));
3704 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3705 LV_VC_RENAME : LV_VC_COPY;
3706 renameBuffer(*buffer, cmd.argument(), kind);
3711 case LFUN_VC_CHECK_IN:
3712 if (!buffer || !ensureBufferClean(buffer))
3714 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3716 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3718 // Only skip reloading if the checkin was cancelled or
3719 // an error occurred before the real checkin VCS command
3720 // was executed, since the VCS might have changed the
3721 // file even if it could not checkin successfully.
3722 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3723 reloadBuffer(*buffer);
3727 case LFUN_VC_CHECK_OUT:
3728 if (!buffer || !ensureBufferClean(buffer))
3730 if (buffer->lyxvc().inUse()) {
3731 dr.setMessage(buffer->lyxvc().checkOut());
3732 reloadBuffer(*buffer);
3736 case LFUN_VC_LOCKING_TOGGLE:
3737 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3739 if (buffer->lyxvc().inUse()) {
3740 string res = buffer->lyxvc().lockingToggle();
3742 frontend::Alert::error(_("Revision control error."),
3743 _("Error when setting the locking property."));
3746 reloadBuffer(*buffer);
3751 case LFUN_VC_REVERT:
3754 if (buffer->lyxvc().revert()) {
3755 reloadBuffer(*buffer);
3756 dr.clearMessageUpdate();
3760 case LFUN_VC_UNDO_LAST:
3763 buffer->lyxvc().undoLast();
3764 reloadBuffer(*buffer);
3765 dr.clearMessageUpdate();
3768 case LFUN_VC_REPO_UPDATE:
3771 if (ensureBufferClean(buffer)) {
3772 dr.setMessage(buffer->lyxvc().repoUpdate());
3773 checkExternallyModifiedBuffers();
3777 case LFUN_VC_COMMAND: {
3778 string flag = cmd.getArg(0);
3779 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3782 if (contains(flag, 'M')) {
3783 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3786 string path = cmd.getArg(1);
3787 if (contains(path, "$$p") && buffer)
3788 path = subst(path, "$$p", buffer->filePath());
3789 LYXERR(Debug::LYXVC, "Directory: " << path);
3791 if (!pp.isReadableDirectory()) {
3792 lyxerr << _("Directory is not accessible.") << endl;
3795 support::PathChanger p(pp);
3797 string command = cmd.getArg(2);
3798 if (command.empty())
3801 command = subst(command, "$$i", buffer->absFileName());
3802 command = subst(command, "$$p", buffer->filePath());
3804 command = subst(command, "$$m", to_utf8(message));
3805 LYXERR(Debug::LYXVC, "Command: " << command);
3807 one.startscript(Systemcall::Wait, command);
3811 if (contains(flag, 'I'))
3812 buffer->markDirty();
3813 if (contains(flag, 'R'))
3814 reloadBuffer(*buffer);
3819 case LFUN_VC_COMPARE: {
3820 if (cmd.argument().empty()) {
3821 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3827 string rev1 = cmd.getArg(0);
3831 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3834 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3835 f2 = buffer->absFileName();
3837 string rev2 = cmd.getArg(1);
3841 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3845 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3846 f1 << "\n" << f2 << "\n" );
3847 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3848 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3858 void GuiView::openChildDocument(string const & fname)
3860 LASSERT(documentBufferView(), return);
3861 Buffer & buffer = documentBufferView()->buffer();
3862 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3863 documentBufferView()->saveBookmark(false);
3864 Buffer * child = nullptr;
3865 if (theBufferList().exists(filename)) {
3866 child = theBufferList().getBuffer(filename);
3869 message(bformat(_("Opening child document %1$s..."),
3870 makeDisplayPath(filename.absFileName())));
3871 child = loadDocument(filename, false);
3873 // Set the parent name of the child document.
3874 // This makes insertion of citations and references in the child work,
3875 // when the target is in the parent or another child document.
3877 child->setParent(&buffer);
3881 bool GuiView::goToFileRow(string const & argument)
3885 size_t i = argument.find_last_of(' ');
3886 if (i != string::npos) {
3887 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3888 istringstream is(argument.substr(i + 1));
3893 if (i == string::npos) {
3894 LYXERR0("Wrong argument: " << argument);
3897 Buffer * buf = nullptr;
3898 string const realtmp = package().temp_dir().realPath();
3899 // We have to use os::path_prefix_is() here, instead of
3900 // simply prefixIs(), because the file name comes from
3901 // an external application and may need case adjustment.
3902 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3903 buf = theBufferList().getBufferFromTmp(file_name, true);
3904 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3905 << (buf ? " success" : " failed"));
3907 // Must replace extension of the file to be .lyx
3908 // and get full path
3909 FileName const s = fileSearch(string(),
3910 support::changeExtension(file_name, ".lyx"), "lyx");
3911 // Either change buffer or load the file
3912 if (theBufferList().exists(s))
3913 buf = theBufferList().getBuffer(s);
3914 else if (s.exists()) {
3915 buf = loadDocument(s);
3920 _("File does not exist: %1$s"),
3921 makeDisplayPath(file_name)));
3927 _("No buffer for file: %1$s."),
3928 makeDisplayPath(file_name))
3933 bool success = documentBufferView()->setCursorFromRow(row);
3935 LYXERR(Debug::LATEX,
3936 "setCursorFromRow: invalid position for row " << row);
3937 frontend::Alert::error(_("Inverse Search Failed"),
3938 _("Invalid position requested by inverse search.\n"
3939 "You may need to update the viewed document."));
3945 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3947 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3948 menu->exec(QCursor::pos());
3953 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3954 Buffer const * orig, Buffer * clone, string const & format)
3956 Buffer::ExportStatus const status = func(format);
3958 // the cloning operation will have produced a clone of the entire set of
3959 // documents, starting from the master. so we must delete those.
3960 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3962 busyBuffers.remove(orig);
3967 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3968 Buffer const * orig, Buffer * clone, string const & format)
3970 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3972 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3976 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3977 Buffer const * orig, Buffer * clone, string const & format)
3979 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3981 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3985 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3986 Buffer const * orig, Buffer * clone, string const & format)
3988 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3990 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3994 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3995 Buffer const * used_buffer,
3996 docstring const & msg,
3997 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3998 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3999 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4000 bool allow_async, bool use_tmpdir)
4005 string format = argument;
4007 format = used_buffer->params().getDefaultOutputFormat();
4008 processing_format = format;
4010 progress_->clearMessages();
4013 #if EXPORT_in_THREAD
4015 GuiViewPrivate::busyBuffers.insert(used_buffer);
4016 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4017 if (!cloned_buffer) {
4018 Alert::error(_("Export Error"),
4019 _("Error cloning the Buffer."));
4022 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4027 setPreviewFuture(f);
4028 last_export_format = used_buffer->params().bufferFormat();
4031 // We are asynchronous, so we don't know here anything about the success
4034 Buffer::ExportStatus status;
4036 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4037 } else if (previewFunc) {
4038 status = (used_buffer->*previewFunc)(format);
4041 handleExportStatus(gv_, status, format);
4043 return (status == Buffer::ExportSuccess
4044 || status == Buffer::PreviewSuccess);
4048 Buffer::ExportStatus status;
4050 status = (used_buffer->*syncFunc)(format, true);
4051 } else if (previewFunc) {
4052 status = (used_buffer->*previewFunc)(format);
4055 handleExportStatus(gv_, status, format);
4057 return (status == Buffer::ExportSuccess
4058 || status == Buffer::PreviewSuccess);
4062 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4064 BufferView * bv = currentBufferView();
4065 LASSERT(bv, return);
4067 // Let the current BufferView dispatch its own actions.
4068 bv->dispatch(cmd, dr);
4069 if (dr.dispatched()) {
4070 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4071 updateDialog("document", "");
4075 // Try with the document BufferView dispatch if any.
4076 BufferView * doc_bv = documentBufferView();
4077 if (doc_bv && doc_bv != bv) {
4078 doc_bv->dispatch(cmd, dr);
4079 if (dr.dispatched()) {
4080 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4081 updateDialog("document", "");
4086 // Then let the current Cursor dispatch its own actions.
4087 bv->cursor().dispatch(cmd);
4089 // update completion. We do it here and not in
4090 // processKeySym to avoid another redraw just for a
4091 // changed inline completion
4092 if (cmd.origin() == FuncRequest::KEYBOARD) {
4093 if (cmd.action() == LFUN_SELF_INSERT
4094 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4095 updateCompletion(bv->cursor(), true, true);
4096 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4097 updateCompletion(bv->cursor(), false, true);
4099 updateCompletion(bv->cursor(), false, false);
4102 dr = bv->cursor().result();
4106 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4108 BufferView * bv = currentBufferView();
4109 // By default we won't need any update.
4110 dr.screenUpdate(Update::None);
4111 // assume cmd will be dispatched
4112 dr.dispatched(true);
4114 Buffer * doc_buffer = documentBufferView()
4115 ? &(documentBufferView()->buffer()) : nullptr;
4117 if (cmd.origin() == FuncRequest::TOC) {
4118 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4119 toc->doDispatch(bv->cursor(), cmd, dr);
4123 string const argument = to_utf8(cmd.argument());
4125 switch(cmd.action()) {
4126 case LFUN_BUFFER_CHILD_OPEN:
4127 openChildDocument(to_utf8(cmd.argument()));
4130 case LFUN_BUFFER_IMPORT:
4131 importDocument(to_utf8(cmd.argument()));
4134 case LFUN_MASTER_BUFFER_EXPORT:
4136 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4138 case LFUN_BUFFER_EXPORT: {
4141 // GCC only sees strfwd.h when building merged
4142 if (::lyx::operator==(cmd.argument(), "custom")) {
4143 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4144 // so the following test should not be needed.
4145 // In principle, we could try to switch to such a view...
4146 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4147 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4151 string const dest = cmd.getArg(1);
4152 FileName target_dir;
4153 if (!dest.empty() && FileName::isAbsolute(dest))
4154 target_dir = FileName(support::onlyPath(dest));
4156 target_dir = doc_buffer->fileName().onlyPath();
4158 string const format = (argument.empty() || argument == "default") ?
4159 doc_buffer->params().getDefaultOutputFormat() : argument;
4161 if ((dest.empty() && doc_buffer->isUnnamed())
4162 || !target_dir.isDirWritable()) {
4163 exportBufferAs(*doc_buffer, from_utf8(format));
4166 /* TODO/Review: Is it a problem to also export the children?
4167 See the update_unincluded flag */
4168 d.asyncBufferProcessing(format,
4171 &GuiViewPrivate::exportAndDestroy,
4173 nullptr, cmd.allowAsync());
4174 // TODO Inform user about success
4178 case LFUN_BUFFER_EXPORT_AS: {
4179 LASSERT(doc_buffer, break);
4180 docstring f = cmd.argument();
4182 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4183 exportBufferAs(*doc_buffer, f);
4187 case LFUN_BUFFER_UPDATE: {
4188 d.asyncBufferProcessing(argument,
4191 &GuiViewPrivate::compileAndDestroy,
4193 nullptr, cmd.allowAsync(), true);
4196 case LFUN_BUFFER_VIEW: {
4197 d.asyncBufferProcessing(argument,
4199 _("Previewing ..."),
4200 &GuiViewPrivate::previewAndDestroy,
4202 &Buffer::preview, cmd.allowAsync());
4205 case LFUN_MASTER_BUFFER_UPDATE: {
4206 d.asyncBufferProcessing(argument,
4207 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4209 &GuiViewPrivate::compileAndDestroy,
4211 nullptr, cmd.allowAsync(), true);
4214 case LFUN_MASTER_BUFFER_VIEW: {
4215 d.asyncBufferProcessing(argument,
4216 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4218 &GuiViewPrivate::previewAndDestroy,
4219 nullptr, &Buffer::preview, cmd.allowAsync());
4222 case LFUN_EXPORT_CANCEL: {
4223 Systemcall::killscript();
4226 case LFUN_BUFFER_SWITCH: {
4227 string const file_name = to_utf8(cmd.argument());
4228 if (!FileName::isAbsolute(file_name)) {
4230 dr.setMessage(_("Absolute filename expected."));
4234 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4237 dr.setMessage(_("Document not loaded"));
4241 // Do we open or switch to the buffer in this view ?
4242 if (workArea(*buffer)
4243 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4248 // Look for the buffer in other views
4249 QList<int> const ids = guiApp->viewIds();
4251 for (; i != ids.size(); ++i) {
4252 GuiView & gv = guiApp->view(ids[i]);
4253 if (gv.workArea(*buffer)) {
4255 gv.activateWindow();
4257 gv.setBuffer(buffer);
4262 // If necessary, open a new window as a last resort
4263 if (i == ids.size()) {
4264 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4270 case LFUN_BUFFER_NEXT:
4271 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4274 case LFUN_BUFFER_MOVE_NEXT:
4275 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4278 case LFUN_BUFFER_PREVIOUS:
4279 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4282 case LFUN_BUFFER_MOVE_PREVIOUS:
4283 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4286 case LFUN_BUFFER_CHKTEX:
4287 LASSERT(doc_buffer, break);
4288 doc_buffer->runChktex();
4291 case LFUN_COMMAND_EXECUTE: {
4292 command_execute_ = true;
4293 minibuffer_focus_ = true;
4296 case LFUN_DROP_LAYOUTS_CHOICE:
4297 d.layout_->showPopup();
4300 case LFUN_MENU_OPEN:
4301 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4302 menu->exec(QCursor::pos());
4305 case LFUN_FILE_INSERT: {
4306 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4307 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4308 dr.forceBufferUpdate();
4309 dr.screenUpdate(Update::Force);
4314 case LFUN_FILE_INSERT_PLAINTEXT:
4315 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4316 string const fname = to_utf8(cmd.argument());
4317 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4318 dr.setMessage(_("Absolute filename expected."));
4322 FileName filename(fname);
4323 if (fname.empty()) {
4324 FileDialog dlg(qt_("Select file to insert"));
4326 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4327 QStringList(qt_("All Files (*)")));
4329 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4330 dr.setMessage(_("Canceled."));
4334 filename.set(fromqstr(result.second));
4338 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4339 bv->dispatch(new_cmd, dr);
4344 case LFUN_BUFFER_RELOAD: {
4345 LASSERT(doc_buffer, break);
4348 bool drop = (cmd.argument() == "dump");
4351 if (!drop && !doc_buffer->isClean()) {
4352 docstring const file =
4353 makeDisplayPath(doc_buffer->absFileName(), 20);
4354 if (doc_buffer->notifiesExternalModification()) {
4355 docstring text = _("The current version will be lost. "
4356 "Are you sure you want to load the version on disk "
4357 "of the document %1$s?");
4358 ret = Alert::prompt(_("Reload saved document?"),
4359 bformat(text, file), 1, 1,
4360 _("&Reload"), _("&Cancel"));
4362 docstring text = _("Any changes will be lost. "
4363 "Are you sure you want to revert to the saved version "
4364 "of the document %1$s?");
4365 ret = Alert::prompt(_("Revert to saved document?"),
4366 bformat(text, file), 1, 1,
4367 _("&Revert"), _("&Cancel"));
4372 doc_buffer->markClean();
4373 reloadBuffer(*doc_buffer);
4374 dr.forceBufferUpdate();
4379 case LFUN_BUFFER_RESET_EXPORT:
4380 LASSERT(doc_buffer, break);
4381 doc_buffer->requireFreshStart(true);
4382 dr.setMessage(_("Buffer export reset."));
4385 case LFUN_BUFFER_WRITE:
4386 LASSERT(doc_buffer, break);
4387 saveBuffer(*doc_buffer);
4390 case LFUN_BUFFER_WRITE_AS:
4391 LASSERT(doc_buffer, break);
4392 renameBuffer(*doc_buffer, cmd.argument());
4395 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4396 LASSERT(doc_buffer, break);
4397 renameBuffer(*doc_buffer, cmd.argument(),
4398 LV_WRITE_AS_TEMPLATE);
4401 case LFUN_BUFFER_WRITE_ALL: {
4402 Buffer * first = theBufferList().first();
4405 message(_("Saving all documents..."));
4406 // We cannot use a for loop as the buffer list cycles.
4409 if (!b->isClean()) {
4411 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4413 b = theBufferList().next(b);
4414 } while (b != first);
4415 dr.setMessage(_("All documents saved."));
4419 case LFUN_MASTER_BUFFER_FORALL: {
4423 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4424 funcToRun.allowAsync(false);
4426 for (Buffer const * buf : doc_buffer->allRelatives()) {
4427 // Switch to other buffer view and resend cmd
4428 lyx::dispatch(FuncRequest(
4429 LFUN_BUFFER_SWITCH, buf->absFileName()));
4430 lyx::dispatch(funcToRun);
4433 lyx::dispatch(FuncRequest(
4434 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4438 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4439 LASSERT(doc_buffer, break);
4440 doc_buffer->clearExternalModification();
4443 case LFUN_BUFFER_CLOSE:
4447 case LFUN_BUFFER_CLOSE_ALL:
4451 case LFUN_DEVEL_MODE_TOGGLE:
4452 devel_mode_ = !devel_mode_;
4454 dr.setMessage(_("Developer mode is now enabled."));
4456 dr.setMessage(_("Developer mode is now disabled."));
4459 case LFUN_TOOLBAR_SET: {
4460 string const name = cmd.getArg(0);
4461 string const state = cmd.getArg(1);
4462 if (GuiToolbar * t = toolbar(name))
4467 case LFUN_TOOLBAR_TOGGLE: {
4468 string const name = cmd.getArg(0);
4469 if (GuiToolbar * t = toolbar(name))
4474 case LFUN_TOOLBAR_MOVABLE: {
4475 string const name = cmd.getArg(0);
4477 // toggle (all) toolbars movablility
4478 toolbarsMovable_ = !toolbarsMovable_;
4479 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4480 GuiToolbar * tb = toolbar(ti.name);
4481 if (tb && tb->isMovable() != toolbarsMovable_)
4482 // toggle toolbar movablity if it does not fit lock
4483 // (all) toolbars positions state silent = true, since
4484 // status bar notifications are slow
4487 if (toolbarsMovable_)
4488 dr.setMessage(_("Toolbars unlocked."));
4490 dr.setMessage(_("Toolbars locked."));
4491 } else if (GuiToolbar * t = toolbar(name)) {
4492 // toggle current toolbar movablity
4494 // update lock (all) toolbars positions
4495 updateLockToolbars();
4500 case LFUN_ICON_SIZE: {
4501 QSize size = d.iconSize(cmd.argument());
4503 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4504 size.width(), size.height()));
4508 case LFUN_DIALOG_UPDATE: {
4509 string const name = to_utf8(cmd.argument());
4510 if (name == "prefs" || name == "document")
4511 updateDialog(name, string());
4512 else if (name == "paragraph")
4513 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4514 else if (currentBufferView()) {
4515 Inset * inset = currentBufferView()->editedInset(name);
4516 // Can only update a dialog connected to an existing inset
4518 // FIXME: get rid of this indirection; GuiView ask the inset
4519 // if he is kind enough to update itself...
4520 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4521 //FIXME: pass DispatchResult here?
4522 inset->dispatch(currentBufferView()->cursor(), fr);
4528 case LFUN_DIALOG_TOGGLE: {
4529 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4530 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4531 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4535 case LFUN_DIALOG_DISCONNECT_INSET:
4536 disconnectDialog(to_utf8(cmd.argument()));
4539 case LFUN_DIALOG_HIDE: {
4540 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4544 case LFUN_DIALOG_SHOW: {
4545 string const name = cmd.getArg(0);
4546 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4548 if (name == "latexlog") {
4549 // getStatus checks that
4550 LASSERT(doc_buffer, break);
4551 Buffer::LogType type;
4552 string const logfile = doc_buffer->logName(&type);
4554 case Buffer::latexlog:
4557 case Buffer::buildlog:
4558 sdata = "literate ";
4561 sdata += Lexer::quoteString(logfile);
4562 showDialog("log", sdata);
4563 } else if (name == "vclog") {
4564 // getStatus checks that
4565 LASSERT(doc_buffer, break);
4566 string const sdata2 = "vc " +
4567 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4568 showDialog("log", sdata2);
4569 } else if (name == "symbols") {
4570 sdata = bv->cursor().getEncoding()->name();
4572 showDialog("symbols", sdata);
4573 } else if (name == "findreplace") {
4574 sdata = to_utf8(bv->cursor().selectionAsString(false));
4575 showDialog(name, sdata);
4577 } else if (name == "prefs" && isFullScreen()) {
4578 lfunUiToggle("fullscreen");
4579 showDialog("prefs", sdata);
4581 showDialog(name, sdata);
4586 dr.setMessage(cmd.argument());
4589 case LFUN_UI_TOGGLE: {
4590 string arg = cmd.getArg(0);
4591 if (!lfunUiToggle(arg)) {
4592 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4593 dr.setMessage(bformat(msg, from_utf8(arg)));
4595 // Make sure the keyboard focus stays in the work area.
4600 case LFUN_VIEW_SPLIT: {
4601 LASSERT(doc_buffer, break);
4602 string const orientation = cmd.getArg(0);
4603 d.splitter_->setOrientation(orientation == "vertical"
4604 ? Qt::Vertical : Qt::Horizontal);
4605 TabWorkArea * twa = addTabWorkArea();
4606 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4607 setCurrentWorkArea(wa);
4610 case LFUN_TAB_GROUP_CLOSE:
4611 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4612 closeTabWorkArea(twa);
4613 d.current_work_area_ = nullptr;
4614 twa = d.currentTabWorkArea();
4615 // Switch to the next GuiWorkArea in the found TabWorkArea.
4617 // Make sure the work area is up to date.
4618 setCurrentWorkArea(twa->currentWorkArea());
4620 setCurrentWorkArea(nullptr);
4625 case LFUN_VIEW_CLOSE:
4626 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4627 closeWorkArea(twa->currentWorkArea());
4628 d.current_work_area_ = nullptr;
4629 twa = d.currentTabWorkArea();
4630 // Switch to the next GuiWorkArea in the found TabWorkArea.
4632 // Make sure the work area is up to date.
4633 setCurrentWorkArea(twa->currentWorkArea());
4635 setCurrentWorkArea(nullptr);
4640 case LFUN_COMPLETION_INLINE:
4641 if (d.current_work_area_)
4642 d.current_work_area_->completer().showInline();
4645 case LFUN_COMPLETION_POPUP:
4646 if (d.current_work_area_)
4647 d.current_work_area_->completer().showPopup();
4652 if (d.current_work_area_)
4653 d.current_work_area_->completer().tab();
4656 case LFUN_COMPLETION_CANCEL:
4657 if (d.current_work_area_) {
4658 if (d.current_work_area_->completer().popupVisible())
4659 d.current_work_area_->completer().hidePopup();
4661 d.current_work_area_->completer().hideInline();
4665 case LFUN_COMPLETION_ACCEPT:
4666 if (d.current_work_area_)
4667 d.current_work_area_->completer().activate();
4670 case LFUN_BUFFER_ZOOM_IN:
4671 case LFUN_BUFFER_ZOOM_OUT:
4672 case LFUN_BUFFER_ZOOM: {
4673 if (cmd.argument().empty()) {
4674 if (cmd.action() == LFUN_BUFFER_ZOOM)
4676 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4681 if (cmd.action() == LFUN_BUFFER_ZOOM)
4682 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4683 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4684 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4686 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4689 // Actual zoom value: default zoom + fractional extra value
4690 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4691 if (zoom < static_cast<int>(zoom_min_))
4694 setCurrentZoom(zoom);
4696 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4697 lyxrc.currentZoom, lyxrc.defaultZoom));
4699 guiApp->fontLoader().update();
4700 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4704 case LFUN_VC_REGISTER:
4705 case LFUN_VC_RENAME:
4707 case LFUN_VC_CHECK_IN:
4708 case LFUN_VC_CHECK_OUT:
4709 case LFUN_VC_REPO_UPDATE:
4710 case LFUN_VC_LOCKING_TOGGLE:
4711 case LFUN_VC_REVERT:
4712 case LFUN_VC_UNDO_LAST:
4713 case LFUN_VC_COMMAND:
4714 case LFUN_VC_COMPARE:
4715 dispatchVC(cmd, dr);
4718 case LFUN_SERVER_GOTO_FILE_ROW:
4719 if(goToFileRow(to_utf8(cmd.argument())))
4720 dr.screenUpdate(Update::Force | Update::FitCursor);
4723 case LFUN_LYX_ACTIVATE:
4727 case LFUN_WINDOW_RAISE:
4733 case LFUN_FORWARD_SEARCH: {
4734 // it seems safe to assume we have a document buffer, since
4735 // getStatus wants one.
4736 LASSERT(doc_buffer, break);
4737 Buffer const * doc_master = doc_buffer->masterBuffer();
4738 FileName const path(doc_master->temppath());
4739 string const texname = doc_master->isChild(doc_buffer)
4740 ? DocFileName(changeExtension(
4741 doc_buffer->absFileName(),
4742 "tex")).mangledFileName()
4743 : doc_buffer->latexName();
4744 string const fulltexname =
4745 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4746 string const mastername =
4747 removeExtension(doc_master->latexName());
4748 FileName const dviname(addName(path.absFileName(),
4749 addExtension(mastername, "dvi")));
4750 FileName const pdfname(addName(path.absFileName(),
4751 addExtension(mastername, "pdf")));
4752 bool const have_dvi = dviname.exists();
4753 bool const have_pdf = pdfname.exists();
4754 if (!have_dvi && !have_pdf) {
4755 dr.setMessage(_("Please, preview the document first."));
4758 string outname = dviname.onlyFileName();
4759 string command = lyxrc.forward_search_dvi;
4760 if (!have_dvi || (have_pdf &&
4761 pdfname.lastModified() > dviname.lastModified())) {
4762 outname = pdfname.onlyFileName();
4763 command = lyxrc.forward_search_pdf;
4766 DocIterator cur = bv->cursor();
4767 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4768 LYXERR(Debug::ACTION, "Forward search: row:" << row
4770 if (row == -1 || command.empty()) {
4771 dr.setMessage(_("Couldn't proceed."));
4774 string texrow = convert<string>(row);
4776 command = subst(command, "$$n", texrow);
4777 command = subst(command, "$$f", fulltexname);
4778 command = subst(command, "$$t", texname);
4779 command = subst(command, "$$o", outname);
4781 volatile PathChanger p(path);
4783 one.startscript(Systemcall::DontWait, command);
4787 case LFUN_SPELLING_CONTINUOUSLY:
4788 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4789 dr.screenUpdate(Update::Force);
4792 case LFUN_CITATION_OPEN: {
4794 if (theFormats().getFormat("pdf"))
4795 pdfv = theFormats().getFormat("pdf")->viewer();
4796 if (theFormats().getFormat("ps"))
4797 psv = theFormats().getFormat("ps")->viewer();
4798 frontend::showTarget(argument, pdfv, psv);
4803 // The LFUN must be for one of BufferView, Buffer or Cursor;
4805 dispatchToBufferView(cmd, dr);
4809 // Need to update bv because many LFUNs here might have destroyed it
4810 bv = currentBufferView();
4812 // Clear non-empty selections
4813 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4815 Cursor & cur = bv->cursor();
4816 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4817 cur.clearSelection();
4823 bool GuiView::lfunUiToggle(string const & ui_component)
4825 if (ui_component == "scrollbar") {
4826 // hide() is of no help
4827 if (d.current_work_area_->verticalScrollBarPolicy() ==
4828 Qt::ScrollBarAlwaysOff)
4830 d.current_work_area_->setVerticalScrollBarPolicy(
4831 Qt::ScrollBarAsNeeded);
4833 d.current_work_area_->setVerticalScrollBarPolicy(
4834 Qt::ScrollBarAlwaysOff);
4835 } else if (ui_component == "statusbar") {
4836 statusBar()->setVisible(!statusBar()->isVisible());
4837 } else if (ui_component == "menubar") {
4838 menuBar()->setVisible(!menuBar()->isVisible());
4839 } else if (ui_component == "zoomslider") {
4840 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4841 zoom_in_->setVisible(zoom_slider_->isVisible());
4842 zoom_out_->setVisible(zoom_slider_->isVisible());
4843 } else if (ui_component == "frame") {
4844 int const l = contentsMargins().left();
4846 //are the frames in default state?
4847 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4849 #if QT_VERSION > 0x050903
4850 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4852 setContentsMargins(-2, -2, -2, -2);
4854 #if QT_VERSION > 0x050903
4855 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4857 setContentsMargins(0, 0, 0, 0);
4860 if (ui_component == "fullscreen") {
4868 void GuiView::toggleFullScreen()
4870 setWindowState(windowState() ^ Qt::WindowFullScreen);
4874 Buffer const * GuiView::updateInset(Inset const * inset)
4879 Buffer const * inset_buffer = &(inset->buffer());
4881 for (int i = 0; i != d.splitter_->count(); ++i) {
4882 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4885 Buffer const * buffer = &(wa->bufferView().buffer());
4886 if (inset_buffer == buffer)
4887 wa->scheduleRedraw(true);
4889 return inset_buffer;
4893 void GuiView::restartCaret()
4895 /* When we move around, or type, it's nice to be able to see
4896 * the caret immediately after the keypress.
4898 if (d.current_work_area_)
4899 d.current_work_area_->startBlinkingCaret();
4901 // Take this occasion to update the other GUI elements.
4907 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4909 if (d.current_work_area_)
4910 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4915 // This list should be kept in sync with the list of insets in
4916 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4917 // dialog should have the same name as the inset.
4918 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4919 // docs in LyXAction.cpp.
4921 char const * const dialognames[] = {
4923 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4924 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4925 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4926 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4927 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4928 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4929 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4930 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4932 char const * const * const end_dialognames =
4933 dialognames + (sizeof(dialognames) / sizeof(char *));
4937 cmpCStr(char const * name) : name_(name) {}
4938 bool operator()(char const * other) {
4939 return strcmp(other, name_) == 0;
4946 bool isValidName(string const & name)
4948 return find_if(dialognames, end_dialognames,
4949 cmpCStr(name.c_str())) != end_dialognames;
4955 void GuiView::resetDialogs()
4957 // Make sure that no LFUN uses any GuiView.
4958 guiApp->setCurrentView(nullptr);
4962 constructToolbars();
4963 guiApp->menus().fillMenuBar(menuBar(), this, false);
4964 d.layout_->updateContents(true);
4965 // Now update controls with current buffer.
4966 guiApp->setCurrentView(this);
4972 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4974 for (QObject * child: widget->children()) {
4975 if (child->inherits("QGroupBox")) {
4976 QGroupBox * box = (QGroupBox*) child;
4979 flatGroupBoxes(child, flag);
4985 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4987 if (!isValidName(name))
4990 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4992 if (it != d.dialogs_.end()) {
4994 it->second->hideView();
4995 return it->second.get();
4998 Dialog * dialog = build(name);
4999 d.dialogs_[name].reset(dialog);
5000 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5001 // Force a uniform style for group boxes
5002 // On Mac non-flat works better, on Linux flat is standard
5003 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5005 if (lyxrc.allow_geometry_session)
5006 dialog->restoreSession();
5013 void GuiView::showDialog(string const & name, string const & sdata,
5016 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5020 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5026 const string name = fromqstr(qname);
5027 const string sdata = fromqstr(qdata);
5031 Dialog * dialog = findOrBuild(name, false);
5033 bool const visible = dialog->isVisibleView();
5034 dialog->showData(sdata);
5035 if (currentBufferView())
5036 currentBufferView()->editInset(name, inset);
5037 // We only set the focus to the new dialog if it was not yet
5038 // visible in order not to change the existing previous behaviour
5040 // activateWindow is needed for floating dockviews
5041 dialog->asQWidget()->raise();
5042 dialog->asQWidget()->activateWindow();
5043 if (dialog->wantInitialFocus())
5044 dialog->asQWidget()->setFocus();
5048 catch (ExceptionMessage const &) {
5056 bool GuiView::isDialogVisible(string const & name) const
5058 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5059 if (it == d.dialogs_.end())
5061 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5065 void GuiView::hideDialog(string const & name, Inset * inset)
5067 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5068 if (it == d.dialogs_.end())
5072 if (!currentBufferView())
5074 if (inset != currentBufferView()->editedInset(name))
5078 Dialog * const dialog = it->second.get();
5079 if (dialog->isVisibleView())
5081 if (currentBufferView())
5082 currentBufferView()->editInset(name, nullptr);
5086 void GuiView::disconnectDialog(string const & name)
5088 if (!isValidName(name))
5090 if (currentBufferView())
5091 currentBufferView()->editInset(name, nullptr);
5095 void GuiView::hideAll() const
5097 for(auto const & dlg_p : d.dialogs_)
5098 dlg_p.second->hideView();
5102 void GuiView::updateDialogs()
5104 for(auto const & dlg_p : d.dialogs_) {
5105 Dialog * dialog = dlg_p.second.get();
5107 if (dialog->needBufferOpen() && !documentBufferView())
5108 hideDialog(fromqstr(dialog->name()), nullptr);
5109 else if (dialog->isVisibleView())
5110 dialog->checkStatus();
5118 Dialog * GuiView::build(string const & name)
5120 return createDialog(*this, name);
5124 SEMenu::SEMenu(QWidget * parent)
5126 QAction * action = addAction(qt_("Disable Shell Escape"));
5127 connect(action, SIGNAL(triggered()),
5128 parent, SLOT(disableShellEscape()));
5132 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5134 if (event->button() == Qt::LeftButton) {
5139 } // namespace frontend
5142 #include "moc_GuiView.cpp"