3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
116 // sync with GuiAlert.cpp
117 #define EXPORT_in_THREAD 1
120 #include "support/bind.h"
124 #ifdef HAVE_SYS_TIME_H
125 # include <sys/time.h>
133 using namespace lyx::support;
137 using support::addExtension;
138 using support::changeExtension;
139 using support::removeExtension;
145 class BackgroundWidget : public QWidget
148 BackgroundWidget(int width, int height)
149 : width_(width), height_(height)
151 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
152 if (!lyxrc.show_banner)
154 /// The text to be written on top of the pixmap
155 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
156 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
157 /// The text to be written on top of the pixmap
158 QString const text = lyx_version ?
159 qt_("version ") + lyx_version : qt_("unknown version");
160 #if QT_VERSION >= 0x050000
161 QString imagedir = "images/";
162 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
163 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
164 if (svgRenderer.isValid()) {
165 splash_ = QPixmap(splashSize());
166 QPainter painter(&splash_);
167 svgRenderer.render(&painter);
168 splash_.setDevicePixelRatio(pixelRatio());
170 splash_ = getPixmap("images/", "banner", "png");
173 splash_ = getPixmap("images/", "banner", "svgz,png");
176 QPainter pain(&splash_);
177 pain.setPen(QColor(0, 0, 0));
178 qreal const fsize = fontSize();
181 qreal locscale = htextsize.toFloat(&ok);
184 QPointF const position = textPosition(false);
185 QPointF const hposition = textPosition(true);
186 QRectF const hrect(hposition, splashSize());
188 "widget pixel ratio: " << pixelRatio() <<
189 " splash pixel ratio: " << splashPixelRatio() <<
190 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
192 // The font used to display the version info
193 font.setStyleHint(QFont::SansSerif);
194 font.setWeight(QFont::Bold);
195 font.setPointSizeF(fsize);
197 pain.drawText(position, text);
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Normal);
201 font.setPointSizeF(hfsize);
202 // Check how long the logo gets with the current font
203 // and adapt if the font is running wider than what
205 GuiFontMetrics fm(font);
206 // Split the title into lines to measure the longest line
207 // in the current l7n.
208 QStringList titlesegs = htext.split('\n');
210 int hline = fm.maxHeight();
211 QStringList::const_iterator sit;
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
616 statusBar()->addPermanentWidget(busylabel);
617 search_mode mode = theGuiApp()->imageSearchMode();
618 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
619 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
620 busylabel->setMovie(busyanim);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busylabel, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busylabel, SLOT(hide()));
628 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
633 // Small size slider for macOS to prevent the status bar from enlarging
634 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
635 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
636 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
638 zoom_slider_->setFixedWidth(fm.width('x') * 15);
640 // Make the defaultZoom center
641 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
642 // Initialize proper zoom value
644 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
645 // Actual zoom value: default zoom + fractional offset
646 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
647 if (zoom < static_cast<int>(zoom_min_))
649 zoom_slider_->setValue(zoom);
650 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
652 // Buttons to change zoom stepwise
653 zoom_in_ = new QPushButton(statusBar());
654 zoom_in_->setText("+");
655 zoom_in_->setFlat(true);
656 zoom_in_->setFixedSize(QSize(fm.height(), fm.height()));
657 zoom_out_ = new QPushButton(statusBar());
658 zoom_out_->setText(QString(0x2212));
659 zoom_out_->setFixedSize(QSize(fm.height(), fm.height()));
660 zoom_out_->setFlat(true);
662 statusBar()->addPermanentWidget(zoom_out_);
663 zoom_out_->setEnabled(currentBufferView());
664 statusBar()->addPermanentWidget(zoom_slider_);
665 zoom_slider_->setEnabled(currentBufferView());
666 zoom_in_->setEnabled(currentBufferView());
667 statusBar()->addPermanentWidget(zoom_in_);
669 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
670 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
671 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
672 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
673 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
675 zoom_value_ = new QToolButton(statusBar());
676 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
677 zoom_value_->setToolButtonStyle(Qt::ToolButtonTextOnly);
678 zoom_value_->setAutoRaise(true);
679 zoom_value_->setPopupMode(QToolButton::InstantPopup);
680 zoom_value_->setFixedHeight(fm.height());
681 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
682 zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
684 zoom_value_->setMinimumWidth(fm.width("000%"));
686 statusBar()->addPermanentWidget(zoom_value_);
687 zoom_value_->setEnabled(currentBufferView());
689 act_zoom_default_ = new QAction(toqstr(bformat(_("&Reset to default (%1$d%)"),
690 lyxrc.defaultZoom)), this);
691 act_zoom_in_ = new QAction(qt_("Zoom &in"), this);
692 act_zoom_out_ = new QAction(qt_("Zoom &out"), this);
693 zoom_value_->addAction(act_zoom_default_);
694 zoom_value_->addAction(act_zoom_in_);
695 zoom_value_->addAction(act_zoom_out_);
697 connect(act_zoom_default_, SIGNAL(triggered()),
698 this, SLOT(resetDefaultZoom()));
699 connect(act_zoom_in_, SIGNAL(triggered()),
700 this, SLOT(zoomInPressed()));
701 connect(act_zoom_out_, SIGNAL(triggered()),
702 this, SLOT(zoomOutPressed()));
704 int const iconheight = max(int(d.normalIconSize), fm.height());
705 QSize const iconsize(iconheight, iconheight);
707 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
708 shell_escape_ = new QLabel(statusBar());
709 shell_escape_->setPixmap(shellescape);
710 shell_escape_->setAlignment(Qt::AlignCenter);
711 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
712 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
713 "external commands for this document. "
714 "Right click to change."));
715 SEMenu * menu = new SEMenu(this);
716 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
717 menu, SLOT(showMenu(QPoint)));
718 shell_escape_->hide();
719 statusBar()->addPermanentWidget(shell_escape_);
721 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
722 read_only_ = new QLabel(statusBar());
723 read_only_->setPixmap(readonly);
724 read_only_->setAlignment(Qt::AlignCenter);
726 statusBar()->addPermanentWidget(read_only_);
728 version_control_ = new QLabel(statusBar());
729 version_control_->setAlignment(Qt::AlignCenter);
730 version_control_->setFrameStyle(QFrame::StyledPanel);
731 version_control_->hide();
732 statusBar()->addPermanentWidget(version_control_);
734 statusBar()->setSizeGripEnabled(true);
737 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
738 SLOT(autoSaveThreadFinished()));
740 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
741 SLOT(processingThreadStarted()));
742 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
743 SLOT(processingThreadFinished()));
745 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
746 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
748 // set custom application bars context menu, e.g. tool bar and menu bar
749 setContextMenuPolicy(Qt::CustomContextMenu);
750 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
751 SLOT(toolBarPopup(const QPoint &)));
753 // Forbid too small unresizable window because it can happen
754 // with some window manager under X11.
755 setMinimumSize(300, 200);
757 if (lyxrc.allow_geometry_session) {
758 // Now take care of session management.
763 // no session handling, default to a sane size.
764 setGeometry(50, 50, 690, 510);
767 // clear session data if any.
768 settings.remove("views");
778 void GuiView::disableShellEscape()
780 BufferView * bv = documentBufferView();
783 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
784 bv->buffer().params().shell_escape = false;
785 bv->processUpdateFlags(Update::Force);
789 void GuiView::checkCancelBackground()
791 docstring const ttl = _("Cancel Export?");
792 docstring const msg = _("Do you want to cancel the background export process?");
794 Alert::prompt(ttl, msg, 1, 1,
795 _("&Cancel export"), _("Co&ntinue"));
797 Systemcall::killscript();
801 void GuiView::enableZoomOptions()
803 act_zoom_default_->setEnabled(zoom_slider_->value() != lyxrc.defaultZoom);
805 act_zoom_in_->setEnabled(getStatus(FuncRequest(LFUN_BUFFER_ZOOM_IN), status));
806 act_zoom_out_->setEnabled(getStatus(FuncRequest(LFUN_BUFFER_ZOOM_OUT), status));
810 void GuiView::zoomSliderMoved(int value)
813 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
814 currentWorkArea()->scheduleRedraw(true);
815 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
819 void GuiView::zoomValueChanged(int value)
821 if (value != lyxrc.currentZoom)
822 zoomSliderMoved(value);
827 void GuiView::zoomInPressed()
830 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
831 currentWorkArea()->scheduleRedraw(true);
835 void GuiView::zoomOutPressed()
838 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
839 currentWorkArea()->scheduleRedraw(true);
843 void GuiView::resetDefaultZoom()
845 zoomValueChanged(lyxrc.defaultZoom);
850 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
852 QVector<GuiWorkArea*> areas;
853 for (int i = 0; i < tabWorkAreaCount(); i++) {
854 TabWorkArea* ta = tabWorkArea(i);
855 for (int u = 0; u < ta->count(); u++) {
856 areas << ta->workArea(u);
862 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
863 string const & format)
865 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
868 case Buffer::ExportSuccess:
869 msg = bformat(_("Successful export to format: %1$s"), fmt);
871 case Buffer::ExportCancel:
872 msg = _("Document export cancelled.");
874 case Buffer::ExportError:
875 case Buffer::ExportNoPathToFormat:
876 case Buffer::ExportTexPathHasSpaces:
877 case Buffer::ExportConverterError:
878 msg = bformat(_("Error while exporting format: %1$s"), fmt);
880 case Buffer::PreviewSuccess:
881 msg = bformat(_("Successful preview of format: %1$s"), fmt);
883 case Buffer::PreviewError:
884 msg = bformat(_("Error while previewing format: %1$s"), fmt);
886 case Buffer::ExportKilled:
887 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
894 void GuiView::processingThreadStarted()
899 void GuiView::processingThreadFinished()
901 QFutureWatcher<Buffer::ExportStatus> const * watcher =
902 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
904 Buffer::ExportStatus const status = watcher->result();
905 handleExportStatus(this, status, d.processing_format);
908 BufferView const * const bv = currentBufferView();
909 if (bv && !bv->buffer().errorList("Export").empty()) {
914 bool const error = (status != Buffer::ExportSuccess &&
915 status != Buffer::PreviewSuccess &&
916 status != Buffer::ExportCancel);
918 ErrorList & el = bv->buffer().errorList(d.last_export_format);
919 // at this point, we do not know if buffer-view or
920 // master-buffer-view was called. If there was an export error,
921 // and the current buffer's error log is empty, we guess that
922 // it must be master-buffer-view that was called so we set
924 errors(d.last_export_format, el.empty());
929 void GuiView::autoSaveThreadFinished()
931 QFutureWatcher<docstring> const * watcher =
932 static_cast<QFutureWatcher<docstring> const *>(sender());
933 message(watcher->result());
938 void GuiView::saveLayout() const
941 settings.setValue("zoom_ratio", zoom_ratio_);
942 settings.setValue("devel_mode", devel_mode_);
943 settings.beginGroup("views");
944 settings.beginGroup(QString::number(id_));
945 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
946 settings.setValue("pos", pos());
947 settings.setValue("size", size());
949 settings.setValue("geometry", saveGeometry());
950 settings.setValue("layout", saveState(0));
951 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
955 void GuiView::saveUISettings() const
959 // Save the toolbar private states
960 for (auto const & tb_p : d.toolbars_)
961 tb_p.second->saveSession(settings);
962 // Now take care of all other dialogs
963 for (auto const & dlg_p : d.dialogs_)
964 dlg_p.second->saveSession(settings);
968 void GuiView::setCurrentZoom(const int v)
970 lyxrc.currentZoom = v;
971 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
972 Q_EMIT currentZoomChanged(v);
976 bool GuiView::restoreLayout()
979 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
980 // Actual zoom value: default zoom + fractional offset
981 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
982 if (zoom < static_cast<int>(zoom_min_))
984 setCurrentZoom(zoom);
985 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
986 settings.beginGroup("views");
987 settings.beginGroup(QString::number(id_));
988 QString const icon_key = "icon_size";
989 if (!settings.contains(icon_key))
992 //code below is skipped when when ~/.config/LyX is (re)created
993 setIconSize(d.iconSize(settings.value(icon_key).toString()));
995 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
996 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
997 QSize size = settings.value("size", QSize(690, 510)).toSize();
1001 // Work-around for bug #6034: the window ends up in an undetermined
1002 // state when trying to restore a maximized window when it is
1003 // already maximized.
1004 if (!(windowState() & Qt::WindowMaximized))
1005 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1006 setGeometry(50, 50, 690, 510);
1009 // Make sure layout is correctly oriented.
1010 setLayoutDirection(qApp->layoutDirection());
1012 // Allow the toc and view-source dock widget to be restored if needed.
1014 if ((dialog = findOrBuild("toc", true)))
1015 // see bug 5082. At least setup title and enabled state.
1016 // Visibility will be adjusted by restoreState below.
1017 dialog->prepareView();
1018 if ((dialog = findOrBuild("view-source", true)))
1019 dialog->prepareView();
1020 if ((dialog = findOrBuild("progress", true)))
1021 dialog->prepareView();
1023 if (!restoreState(settings.value("layout").toByteArray(), 0))
1026 // init the toolbars that have not been restored
1027 for (auto const & tb_p : guiApp->toolbars()) {
1028 GuiToolbar * tb = toolbar(tb_p.name);
1029 if (tb && !tb->isRestored())
1030 initToolbar(tb_p.name);
1033 // update lock (all) toolbars positions
1034 updateLockToolbars();
1041 GuiToolbar * GuiView::toolbar(string const & name)
1043 ToolbarMap::iterator it = d.toolbars_.find(name);
1044 if (it != d.toolbars_.end())
1047 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1052 void GuiView::updateLockToolbars()
1054 toolbarsMovable_ = false;
1055 for (ToolbarInfo const & info : guiApp->toolbars()) {
1056 GuiToolbar * tb = toolbar(info.name);
1057 if (tb && tb->isMovable())
1058 toolbarsMovable_ = true;
1063 void GuiView::constructToolbars()
1065 for (auto const & tb_p : d.toolbars_)
1067 d.toolbars_.clear();
1069 // I don't like doing this here, but the standard toolbar
1070 // destroys this object when it's destroyed itself (vfr)
1071 d.layout_ = new LayoutBox(*this);
1072 d.stack_widget_->addWidget(d.layout_);
1073 d.layout_->move(0,0);
1075 // extracts the toolbars from the backend
1076 for (ToolbarInfo const & inf : guiApp->toolbars())
1077 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1081 void GuiView::initToolbars()
1083 // extracts the toolbars from the backend
1084 for (ToolbarInfo const & inf : guiApp->toolbars())
1085 initToolbar(inf.name);
1089 void GuiView::initToolbar(string const & name)
1091 GuiToolbar * tb = toolbar(name);
1094 int const visibility = guiApp->toolbars().defaultVisibility(name);
1095 bool newline = !(visibility & Toolbars::SAMEROW);
1096 tb->setVisible(false);
1097 tb->setVisibility(visibility);
1099 if (visibility & Toolbars::TOP) {
1101 addToolBarBreak(Qt::TopToolBarArea);
1102 addToolBar(Qt::TopToolBarArea, tb);
1105 if (visibility & Toolbars::BOTTOM) {
1107 addToolBarBreak(Qt::BottomToolBarArea);
1108 addToolBar(Qt::BottomToolBarArea, tb);
1111 if (visibility & Toolbars::LEFT) {
1113 addToolBarBreak(Qt::LeftToolBarArea);
1114 addToolBar(Qt::LeftToolBarArea, tb);
1117 if (visibility & Toolbars::RIGHT) {
1119 addToolBarBreak(Qt::RightToolBarArea);
1120 addToolBar(Qt::RightToolBarArea, tb);
1123 if (visibility & Toolbars::ON)
1124 tb->setVisible(true);
1126 tb->setMovable(true);
1130 TocModels & GuiView::tocModels()
1132 return d.toc_models_;
1136 void GuiView::setFocus()
1138 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1139 QMainWindow::setFocus();
1143 bool GuiView::hasFocus() const
1145 if (currentWorkArea())
1146 return currentWorkArea()->hasFocus();
1147 if (currentMainWorkArea())
1148 return currentMainWorkArea()->hasFocus();
1149 return d.bg_widget_->hasFocus();
1153 void GuiView::focusInEvent(QFocusEvent * e)
1155 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1156 QMainWindow::focusInEvent(e);
1157 // Make sure guiApp points to the correct view.
1158 guiApp->setCurrentView(this);
1159 if (currentWorkArea())
1160 currentWorkArea()->setFocus();
1161 else if (currentMainWorkArea())
1162 currentMainWorkArea()->setFocus();
1164 d.bg_widget_->setFocus();
1168 void GuiView::showEvent(QShowEvent * e)
1170 LYXERR(Debug::GUI, "Passed Geometry "
1171 << size().height() << "x" << size().width()
1172 << "+" << pos().x() << "+" << pos().y());
1174 if (d.splitter_->count() == 0)
1175 // No work area, switch to the background widget.
1179 QMainWindow::showEvent(e);
1183 bool GuiView::closeScheduled()
1190 bool GuiView::prepareAllBuffersForLogout()
1192 Buffer * first = theBufferList().first();
1196 // First, iterate over all buffers and ask the users if unsaved
1197 // changes should be saved.
1198 // We cannot use a for loop as the buffer list cycles.
1201 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1203 b = theBufferList().next(b);
1204 } while (b != first);
1206 // Next, save session state
1207 // When a view/window was closed before without quitting LyX, there
1208 // are already entries in the lastOpened list.
1209 theSession().lastOpened().clear();
1216 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1217 ** is responsibility of the container (e.g., dialog)
1219 void GuiView::closeEvent(QCloseEvent * close_event)
1221 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1223 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1224 Alert::warning(_("Exit LyX"),
1225 _("LyX could not be closed because documents are being processed by LyX."));
1226 close_event->setAccepted(false);
1230 // If the user pressed the x (so we didn't call closeView
1231 // programmatically), we want to clear all existing entries.
1233 theSession().lastOpened().clear();
1238 // it can happen that this event arrives without selecting the view,
1239 // e.g. when clicking the close button on a background window.
1241 if (!closeWorkAreaAll()) {
1243 close_event->ignore();
1247 // Make sure that nothing will use this to be closed View.
1248 guiApp->unregisterView(this);
1250 if (isFullScreen()) {
1251 // Switch off fullscreen before closing.
1256 // Make sure the timer time out will not trigger a statusbar update.
1257 d.statusbar_timer_.stop();
1259 // Saving fullscreen requires additional tweaks in the toolbar code.
1260 // It wouldn't also work under linux natively.
1261 if (lyxrc.allow_geometry_session) {
1266 close_event->accept();
1270 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1272 if (event->mimeData()->hasUrls())
1274 /// \todo Ask lyx-devel is this is enough:
1275 /// if (event->mimeData()->hasFormat("text/plain"))
1276 /// event->acceptProposedAction();
1280 void GuiView::dropEvent(QDropEvent * event)
1282 QList<QUrl> files = event->mimeData()->urls();
1283 if (files.isEmpty())
1286 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1287 for (int i = 0; i != files.size(); ++i) {
1288 string const file = os::internal_path(fromqstr(
1289 files.at(i).toLocalFile()));
1293 string const ext = support::getExtension(file);
1294 vector<const Format *> found_formats;
1296 // Find all formats that have the correct extension.
1297 for (const Format * fmt : theConverters().importableFormats())
1298 if (fmt->hasExtension(ext))
1299 found_formats.push_back(fmt);
1302 if (!found_formats.empty()) {
1303 if (found_formats.size() > 1) {
1304 //FIXME: show a dialog to choose the correct importable format
1305 LYXERR(Debug::FILES,
1306 "Multiple importable formats found, selecting first");
1308 string const arg = found_formats[0]->name() + " " + file;
1309 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1312 //FIXME: do we have to explicitly check whether it's a lyx file?
1313 LYXERR(Debug::FILES,
1314 "No formats found, trying to open it as a lyx file");
1315 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1317 // add the functions to the queue
1318 guiApp->addToFuncRequestQueue(cmd);
1321 // now process the collected functions. We perform the events
1322 // asynchronously. This prevents potential problems in case the
1323 // BufferView is closed within an event.
1324 guiApp->processFuncRequestQueueAsync();
1328 void GuiView::message(docstring const & str)
1330 if (ForkedProcess::iAmAChild())
1333 // call is moved to GUI-thread by GuiProgress
1334 d.progress_->appendMessage(toqstr(str));
1338 void GuiView::clearMessageText()
1340 message(docstring());
1344 void GuiView::updateStatusBarMessage(QString const & str)
1346 statusBar()->showMessage(str);
1347 d.statusbar_timer_.stop();
1348 d.statusbar_timer_.start(3000);
1352 void GuiView::clearMessage()
1354 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1355 // the hasFocus function mostly returns false, even if the focus is on
1356 // a workarea in this view.
1360 d.statusbar_timer_.stop();
1364 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1366 if (wa != d.current_work_area_
1367 || wa->bufferView().buffer().isInternal())
1369 Buffer const & buf = wa->bufferView().buffer();
1370 // Set the windows title
1371 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1372 if (buf.notifiesExternalModification()) {
1373 title = bformat(_("%1$s (modified externally)"), title);
1374 // If the external modification status has changed, then maybe the status of
1375 // buffer-save has changed too.
1379 title += from_ascii(" - LyX");
1381 setWindowTitle(toqstr(title));
1382 // Sets the path for the window: this is used by OSX to
1383 // allow a context click on the title bar showing a menu
1384 // with the path up to the file
1385 setWindowFilePath(toqstr(buf.absFileName()));
1386 // Tell Qt whether the current document is changed
1387 setWindowModified(!buf.isClean());
1389 if (buf.params().shell_escape)
1390 shell_escape_->show();
1392 shell_escape_->hide();
1394 if (buf.hasReadonlyFlag())
1399 if (buf.lyxvc().inUse()) {
1400 version_control_->show();
1401 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1403 version_control_->hide();
1407 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1409 if (d.current_work_area_)
1410 // disconnect the current work area from all slots
1411 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1413 disconnectBufferView();
1414 connectBufferView(wa->bufferView());
1415 connectBuffer(wa->bufferView().buffer());
1416 d.current_work_area_ = wa;
1417 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1418 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1419 QObject::connect(wa, SIGNAL(busy(bool)),
1420 this, SLOT(setBusy(bool)));
1421 // connection of a signal to a signal
1422 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1423 this, SIGNAL(bufferViewChanged()));
1424 Q_EMIT updateWindowTitle(wa);
1425 Q_EMIT bufferViewChanged();
1429 void GuiView::onBufferViewChanged()
1432 // Buffer-dependent dialogs must be updated. This is done here because
1433 // some dialogs require buffer()->text.
1435 zoom_slider_->setEnabled(currentBufferView());
1436 zoom_value_->setEnabled(currentBufferView());
1437 zoom_in_->setEnabled(currentBufferView());
1438 zoom_out_->setEnabled(currentBufferView());
1442 void GuiView::on_lastWorkAreaRemoved()
1445 // We already are in a close event. Nothing more to do.
1448 if (d.splitter_->count() > 1)
1449 // We have a splitter so don't close anything.
1452 // Reset and updates the dialogs.
1453 Q_EMIT bufferViewChanged();
1458 if (lyxrc.open_buffers_in_tabs)
1459 // Nothing more to do, the window should stay open.
1462 if (guiApp->viewIds().size() > 1) {
1468 // On Mac we also close the last window because the application stay
1469 // resident in memory. On other platforms we don't close the last
1470 // window because this would quit the application.
1476 void GuiView::updateStatusBar()
1478 // let the user see the explicit message
1479 if (d.statusbar_timer_.isActive())
1486 void GuiView::showMessage()
1490 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1491 if (msg.isEmpty()) {
1492 BufferView const * bv = currentBufferView();
1494 msg = toqstr(bv->cursor().currentState(devel_mode_));
1496 msg = qt_("Welcome to LyX!");
1498 statusBar()->showMessage(msg);
1502 bool GuiView::event(QEvent * e)
1506 // Useful debug code:
1507 //case QEvent::ActivationChange:
1508 //case QEvent::WindowDeactivate:
1509 //case QEvent::Paint:
1510 //case QEvent::Enter:
1511 //case QEvent::Leave:
1512 //case QEvent::HoverEnter:
1513 //case QEvent::HoverLeave:
1514 //case QEvent::HoverMove:
1515 //case QEvent::StatusTip:
1516 //case QEvent::DragEnter:
1517 //case QEvent::DragLeave:
1518 //case QEvent::Drop:
1521 case QEvent::WindowStateChange: {
1522 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1523 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1524 bool result = QMainWindow::event(e);
1525 bool nfstate = (windowState() & Qt::WindowFullScreen);
1526 if (!ofstate && nfstate) {
1527 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1528 // switch to full-screen state
1529 if (lyxrc.full_screen_statusbar)
1530 statusBar()->hide();
1531 if (lyxrc.full_screen_menubar)
1533 if (lyxrc.full_screen_toolbars) {
1534 for (auto const & tb_p : d.toolbars_)
1535 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1536 tb_p.second->hide();
1538 for (int i = 0; i != d.splitter_->count(); ++i)
1539 d.tabWorkArea(i)->setFullScreen(true);
1540 #if QT_VERSION > 0x050903
1541 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1542 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1544 setContentsMargins(-2, -2, -2, -2);
1546 hideDialogs("prefs", nullptr);
1547 } else if (ofstate && !nfstate) {
1548 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1549 // switch back from full-screen state
1550 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1551 statusBar()->show();
1552 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1554 if (lyxrc.full_screen_toolbars) {
1555 for (auto const & tb_p : d.toolbars_)
1556 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1557 tb_p.second->show();
1560 for (int i = 0; i != d.splitter_->count(); ++i)
1561 d.tabWorkArea(i)->setFullScreen(false);
1562 #if QT_VERSION > 0x050903
1563 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1565 setContentsMargins(0, 0, 0, 0);
1569 case QEvent::WindowActivate: {
1570 GuiView * old_view = guiApp->currentView();
1571 if (this == old_view) {
1573 return QMainWindow::event(e);
1575 if (old_view && old_view->currentBufferView()) {
1576 // save current selection to the selection buffer to allow
1577 // middle-button paste in this window.
1578 cap::saveSelection(old_view->currentBufferView()->cursor());
1580 guiApp->setCurrentView(this);
1581 if (d.current_work_area_)
1582 on_currentWorkAreaChanged(d.current_work_area_);
1586 return QMainWindow::event(e);
1589 case QEvent::ShortcutOverride: {
1591 if (isFullScreen() && menuBar()->isHidden()) {
1592 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1593 // FIXME: we should also try to detect special LyX shortcut such as
1594 // Alt-P and Alt-M. Right now there is a hack in
1595 // GuiWorkArea::processKeySym() that hides again the menubar for
1597 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1599 return QMainWindow::event(e);
1602 return QMainWindow::event(e);
1605 case QEvent::ApplicationPaletteChange: {
1606 // runtime switch from/to dark mode
1608 return QMainWindow::event(e);
1612 return QMainWindow::event(e);
1616 void GuiView::resetWindowTitle()
1618 setWindowTitle(qt_("LyX"));
1621 bool GuiView::focusNextPrevChild(bool /*next*/)
1628 bool GuiView::busy() const
1634 void GuiView::setBusy(bool busy)
1636 bool const busy_before = busy_ > 0;
1637 busy ? ++busy_ : --busy_;
1638 if ((busy_ > 0) == busy_before)
1639 // busy state didn't change
1643 QApplication::setOverrideCursor(Qt::WaitCursor);
1646 QApplication::restoreOverrideCursor();
1651 void GuiView::resetCommandExecute()
1653 command_execute_ = false;
1658 double GuiView::pixelRatio() const
1660 #if QT_VERSION >= 0x050000
1661 return qt_scale_factor * devicePixelRatio();
1668 GuiWorkArea * GuiView::workArea(int index)
1670 if (TabWorkArea * twa = d.currentTabWorkArea())
1671 if (index < twa->count())
1672 return twa->workArea(index);
1677 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1679 if (currentWorkArea()
1680 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1681 return currentWorkArea();
1682 if (TabWorkArea * twa = d.currentTabWorkArea())
1683 return twa->workArea(buffer);
1688 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1690 // Automatically create a TabWorkArea if there are none yet.
1691 TabWorkArea * tab_widget = d.splitter_->count()
1692 ? d.currentTabWorkArea() : addTabWorkArea();
1693 return tab_widget->addWorkArea(buffer, *this);
1697 TabWorkArea * GuiView::addTabWorkArea()
1699 TabWorkArea * twa = new TabWorkArea;
1700 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1701 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1702 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1703 this, SLOT(on_lastWorkAreaRemoved()));
1705 d.splitter_->addWidget(twa);
1706 d.stack_widget_->setCurrentWidget(d.splitter_);
1711 GuiWorkArea const * GuiView::currentWorkArea() const
1713 return d.current_work_area_;
1717 GuiWorkArea * GuiView::currentWorkArea()
1719 return d.current_work_area_;
1723 GuiWorkArea const * GuiView::currentMainWorkArea() const
1725 if (!d.currentTabWorkArea())
1727 return d.currentTabWorkArea()->currentWorkArea();
1731 GuiWorkArea * GuiView::currentMainWorkArea()
1733 if (!d.currentTabWorkArea())
1735 return d.currentTabWorkArea()->currentWorkArea();
1739 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1741 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1743 d.current_work_area_ = nullptr;
1745 Q_EMIT bufferViewChanged();
1749 // FIXME: I've no clue why this is here and why it accesses
1750 // theGuiApp()->currentView, which might be 0 (bug 6464).
1751 // See also 27525 (vfr).
1752 if (theGuiApp()->currentView() == this
1753 && theGuiApp()->currentView()->currentWorkArea() == wa)
1756 if (currentBufferView())
1757 cap::saveSelection(currentBufferView()->cursor());
1759 theGuiApp()->setCurrentView(this);
1760 d.current_work_area_ = wa;
1762 // We need to reset this now, because it will need to be
1763 // right if the tabWorkArea gets reset in the for loop. We
1764 // will change it back if we aren't in that case.
1765 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1766 d.current_main_work_area_ = wa;
1768 for (int i = 0; i != d.splitter_->count(); ++i) {
1769 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1770 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1771 << ", Current main wa: " << currentMainWorkArea());
1776 d.current_main_work_area_ = old_cmwa;
1778 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1779 on_currentWorkAreaChanged(wa);
1780 BufferView & bv = wa->bufferView();
1781 bv.cursor().fixIfBroken();
1783 wa->setUpdatesEnabled(true);
1784 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1788 void GuiView::removeWorkArea(GuiWorkArea * wa)
1790 LASSERT(wa, return);
1791 if (wa == d.current_work_area_) {
1793 disconnectBufferView();
1794 d.current_work_area_ = nullptr;
1795 d.current_main_work_area_ = nullptr;
1798 bool found_twa = false;
1799 for (int i = 0; i != d.splitter_->count(); ++i) {
1800 TabWorkArea * twa = d.tabWorkArea(i);
1801 if (twa->removeWorkArea(wa)) {
1802 // Found in this tab group, and deleted the GuiWorkArea.
1804 if (twa->count() != 0) {
1805 if (d.current_work_area_ == nullptr)
1806 // This means that we are closing the current GuiWorkArea, so
1807 // switch to the next GuiWorkArea in the found TabWorkArea.
1808 setCurrentWorkArea(twa->currentWorkArea());
1810 // No more WorkAreas in this tab group, so delete it.
1817 // It is not a tabbed work area (i.e., the search work area), so it
1818 // should be deleted by other means.
1819 LASSERT(found_twa, return);
1821 if (d.current_work_area_ == nullptr) {
1822 if (d.splitter_->count() != 0) {
1823 TabWorkArea * twa = d.currentTabWorkArea();
1824 setCurrentWorkArea(twa->currentWorkArea());
1826 // No more work areas, switch to the background widget.
1827 setCurrentWorkArea(nullptr);
1833 LayoutBox * GuiView::getLayoutDialog() const
1839 void GuiView::updateLayoutList()
1842 d.layout_->updateContents(false);
1846 void GuiView::updateToolbars()
1848 if (d.current_work_area_) {
1850 if (d.current_work_area_->bufferView().cursor().inMathed()
1851 && !d.current_work_area_->bufferView().cursor().inRegexped())
1852 context |= Toolbars::MATH;
1853 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1854 context |= Toolbars::TABLE;
1855 if (currentBufferView()->buffer().areChangesPresent()
1856 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1857 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1858 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1859 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1860 context |= Toolbars::REVIEW;
1861 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1862 context |= Toolbars::MATHMACROTEMPLATE;
1863 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1864 context |= Toolbars::IPA;
1865 if (command_execute_)
1866 context |= Toolbars::MINIBUFFER;
1867 if (minibuffer_focus_) {
1868 context |= Toolbars::MINIBUFFER_FOCUS;
1869 minibuffer_focus_ = false;
1872 for (auto const & tb_p : d.toolbars_)
1873 tb_p.second->update(context);
1875 for (auto const & tb_p : d.toolbars_)
1876 tb_p.second->update();
1880 void GuiView::refillToolbars()
1882 for (auto const & tb_p : d.toolbars_)
1883 tb_p.second->refill();
1887 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1889 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1890 LASSERT(newBuffer, return);
1892 GuiWorkArea * wa = workArea(*newBuffer);
1893 if (wa == nullptr) {
1895 newBuffer->masterBuffer()->updateBuffer();
1897 wa = addWorkArea(*newBuffer);
1898 // scroll to the position when the BufferView was last closed
1899 if (lyxrc.use_lastfilepos) {
1900 LastFilePosSection::FilePos filepos =
1901 theSession().lastFilePos().load(newBuffer->fileName());
1902 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1905 //Disconnect the old buffer...there's no new one.
1908 connectBuffer(*newBuffer);
1909 connectBufferView(wa->bufferView());
1911 setCurrentWorkArea(wa);
1915 void GuiView::connectBuffer(Buffer & buf)
1917 buf.setGuiDelegate(this);
1921 void GuiView::disconnectBuffer()
1923 if (d.current_work_area_)
1924 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1928 void GuiView::connectBufferView(BufferView & bv)
1930 bv.setGuiDelegate(this);
1934 void GuiView::disconnectBufferView()
1936 if (d.current_work_area_)
1937 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1941 void GuiView::errors(string const & error_type, bool from_master)
1943 BufferView const * const bv = currentBufferView();
1947 ErrorList const & el = from_master ?
1948 bv->buffer().masterBuffer()->errorList(error_type) :
1949 bv->buffer().errorList(error_type);
1954 string err = error_type;
1956 err = "from_master|" + error_type;
1957 showDialog("errorlist", err);
1961 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1963 d.toc_models_.updateItem(toqstr(type), dit);
1967 void GuiView::structureChanged()
1969 // This is called from the Buffer, which has no way to ensure that cursors
1970 // in BufferView remain valid.
1971 if (documentBufferView())
1972 documentBufferView()->cursor().sanitize();
1973 // FIXME: This is slightly expensive, though less than the tocBackend update
1974 // (#9880). This also resets the view in the Toc Widget (#6675).
1975 d.toc_models_.reset(documentBufferView());
1976 // Navigator needs more than a simple update in this case. It needs to be
1978 updateDialog("toc", "");
1982 void GuiView::updateDialog(string const & name, string const & sdata)
1984 if (!isDialogVisible(name))
1987 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1988 if (it == d.dialogs_.end())
1991 Dialog * const dialog = it->second.get();
1992 if (dialog->isVisibleView())
1993 dialog->initialiseParams(sdata);
1997 BufferView * GuiView::documentBufferView()
1999 return currentMainWorkArea()
2000 ? ¤tMainWorkArea()->bufferView()
2005 BufferView const * GuiView::documentBufferView() const
2007 return currentMainWorkArea()
2008 ? ¤tMainWorkArea()->bufferView()
2013 BufferView * GuiView::currentBufferView()
2015 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2019 BufferView const * GuiView::currentBufferView() const
2021 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2025 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2026 Buffer const * orig, Buffer * clone)
2028 bool const success = clone->autoSave();
2030 busyBuffers.remove(orig);
2032 ? _("Automatic save done.")
2033 : _("Automatic save failed!");
2037 void GuiView::autoSave()
2039 LYXERR(Debug::INFO, "Running autoSave()");
2041 Buffer * buffer = documentBufferView()
2042 ? &documentBufferView()->buffer() : nullptr;
2044 resetAutosaveTimers();
2048 GuiViewPrivate::busyBuffers.insert(buffer);
2049 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2050 buffer, buffer->cloneBufferOnly());
2051 d.autosave_watcher_.setFuture(f);
2052 resetAutosaveTimers();
2056 void GuiView::resetAutosaveTimers()
2059 d.autosave_timeout_.restart();
2063 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2066 Buffer * buf = currentBufferView()
2067 ? ¤tBufferView()->buffer() : nullptr;
2068 Buffer * doc_buffer = documentBufferView()
2069 ? &(documentBufferView()->buffer()) : nullptr;
2072 /* In LyX/Mac, when a dialog is open, the menus of the
2073 application can still be accessed without giving focus to
2074 the main window. In this case, we want to disable the menu
2075 entries that are buffer-related.
2076 This code must not be used on Linux and Windows, since it
2077 would disable buffer-related entries when hovering over the
2078 menu (see bug #9574).
2080 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2086 // Check whether we need a buffer
2087 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2088 // no, exit directly
2089 flag.message(from_utf8(N_("Command not allowed with"
2090 "out any document open")));
2091 flag.setEnabled(false);
2095 if (cmd.origin() == FuncRequest::TOC) {
2096 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2097 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2098 flag.setEnabled(false);
2102 switch(cmd.action()) {
2103 case LFUN_BUFFER_IMPORT:
2106 case LFUN_MASTER_BUFFER_EXPORT:
2108 && (doc_buffer->parent() != nullptr
2109 || doc_buffer->hasChildren())
2110 && !d.processing_thread_watcher_.isRunning()
2111 // this launches a dialog, which would be in the wrong Buffer
2112 && !(::lyx::operator==(cmd.argument(), "custom"));
2115 case LFUN_MASTER_BUFFER_UPDATE:
2116 case LFUN_MASTER_BUFFER_VIEW:
2118 && (doc_buffer->parent() != nullptr
2119 || doc_buffer->hasChildren())
2120 && !d.processing_thread_watcher_.isRunning();
2123 case LFUN_BUFFER_UPDATE:
2124 case LFUN_BUFFER_VIEW: {
2125 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2129 string format = to_utf8(cmd.argument());
2130 if (cmd.argument().empty())
2131 format = doc_buffer->params().getDefaultOutputFormat();
2132 enable = doc_buffer->params().isExportable(format, true);
2136 case LFUN_BUFFER_RELOAD:
2137 enable = doc_buffer && !doc_buffer->isUnnamed()
2138 && doc_buffer->fileName().exists()
2139 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2142 case LFUN_BUFFER_RESET_EXPORT:
2143 enable = doc_buffer != nullptr;
2146 case LFUN_BUFFER_CHILD_OPEN:
2147 enable = doc_buffer != nullptr;
2150 case LFUN_MASTER_BUFFER_FORALL: {
2151 if (doc_buffer == nullptr) {
2152 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2156 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2157 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2158 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2163 for (Buffer * buf : doc_buffer->allRelatives()) {
2164 GuiWorkArea * wa = workArea(*buf);
2167 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2168 enable = flag.enabled();
2175 case LFUN_BUFFER_WRITE:
2176 enable = doc_buffer && (doc_buffer->isUnnamed()
2177 || (!doc_buffer->isClean()
2178 || cmd.argument() == "force"));
2181 //FIXME: This LFUN should be moved to GuiApplication.
2182 case LFUN_BUFFER_WRITE_ALL: {
2183 // We enable the command only if there are some modified buffers
2184 Buffer * first = theBufferList().first();
2189 // We cannot use a for loop as the buffer list is a cycle.
2191 if (!b->isClean()) {
2195 b = theBufferList().next(b);
2196 } while (b != first);
2200 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2201 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2204 case LFUN_BUFFER_EXPORT: {
2205 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2209 return doc_buffer->getStatus(cmd, flag);
2212 case LFUN_BUFFER_EXPORT_AS:
2213 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2218 case LFUN_BUFFER_WRITE_AS:
2219 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2220 enable = doc_buffer != nullptr;
2223 case LFUN_EXPORT_CANCEL:
2224 enable = d.processing_thread_watcher_.isRunning();
2227 case LFUN_BUFFER_CLOSE:
2228 case LFUN_VIEW_CLOSE:
2229 enable = doc_buffer != nullptr;
2232 case LFUN_BUFFER_CLOSE_ALL:
2233 enable = theBufferList().last() != theBufferList().first();
2236 case LFUN_BUFFER_CHKTEX: {
2237 // hide if we have no checktex command
2238 if (lyxrc.chktex_command.empty()) {
2239 flag.setUnknown(true);
2243 if (!doc_buffer || !doc_buffer->params().isLatex()
2244 || d.processing_thread_watcher_.isRunning()) {
2245 // grey out, don't hide
2253 case LFUN_VIEW_SPLIT:
2254 if (cmd.getArg(0) == "vertical")
2255 enable = doc_buffer && (d.splitter_->count() == 1 ||
2256 d.splitter_->orientation() == Qt::Vertical);
2258 enable = doc_buffer && (d.splitter_->count() == 1 ||
2259 d.splitter_->orientation() == Qt::Horizontal);
2262 case LFUN_TAB_GROUP_CLOSE:
2263 enable = d.tabWorkAreaCount() > 1;
2266 case LFUN_DEVEL_MODE_TOGGLE:
2267 flag.setOnOff(devel_mode_);
2270 case LFUN_TOOLBAR_SET: {
2271 string const name = cmd.getArg(0);
2272 string const state = cmd.getArg(1);
2273 if (name.empty() || state.empty()) {
2275 docstring const msg =
2276 _("Function toolbar-set requires two arguments!");
2280 if (state != "on" && state != "off" && state != "auto") {
2282 docstring const msg =
2283 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2288 if (GuiToolbar * t = toolbar(name)) {
2289 bool const autovis = t->visibility() & Toolbars::AUTO;
2291 flag.setOnOff(t->isVisible() && !autovis);
2292 else if (state == "off")
2293 flag.setOnOff(!t->isVisible() && !autovis);
2294 else if (state == "auto")
2295 flag.setOnOff(autovis);
2298 docstring const msg =
2299 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2305 case LFUN_TOOLBAR_TOGGLE: {
2306 string const name = cmd.getArg(0);
2307 if (GuiToolbar * t = toolbar(name))
2308 flag.setOnOff(t->isVisible());
2311 docstring const msg =
2312 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2318 case LFUN_TOOLBAR_MOVABLE: {
2319 string const name = cmd.getArg(0);
2320 // use negation since locked == !movable
2322 // toolbar name * locks all toolbars
2323 flag.setOnOff(!toolbarsMovable_);
2324 else if (GuiToolbar * t = toolbar(name))
2325 flag.setOnOff(!(t->isMovable()));
2328 docstring const msg =
2329 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2335 case LFUN_ICON_SIZE:
2336 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2339 case LFUN_DROP_LAYOUTS_CHOICE:
2340 enable = buf != nullptr;
2343 case LFUN_UI_TOGGLE:
2344 flag.setOnOff(isFullScreen());
2347 case LFUN_DIALOG_DISCONNECT_INSET:
2350 case LFUN_DIALOG_HIDE:
2351 // FIXME: should we check if the dialog is shown?
2354 case LFUN_DIALOG_TOGGLE:
2355 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2358 case LFUN_DIALOG_SHOW: {
2359 string const name = cmd.getArg(0);
2361 enable = name == "aboutlyx"
2362 || name == "file" //FIXME: should be removed.
2363 || name == "lyxfiles"
2365 || name == "texinfo"
2366 || name == "progress"
2367 || name == "compare";
2368 else if (name == "character" || name == "symbols"
2369 || name == "mathdelimiter" || name == "mathmatrix") {
2370 if (!buf || buf->isReadonly())
2373 Cursor const & cur = currentBufferView()->cursor();
2374 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2377 else if (name == "latexlog")
2378 enable = FileName(doc_buffer->logName()).isReadableFile();
2379 else if (name == "spellchecker")
2380 enable = theSpellChecker()
2381 && !doc_buffer->text().empty();
2382 else if (name == "vclog")
2383 enable = doc_buffer->lyxvc().inUse();
2387 case LFUN_DIALOG_UPDATE: {
2388 string const name = cmd.getArg(0);
2390 enable = name == "prefs";
2394 case LFUN_COMMAND_EXECUTE:
2396 case LFUN_MENU_OPEN:
2397 // Nothing to check.
2400 case LFUN_COMPLETION_INLINE:
2401 if (!d.current_work_area_
2402 || !d.current_work_area_->completer().inlinePossible(
2403 currentBufferView()->cursor()))
2407 case LFUN_COMPLETION_POPUP:
2408 if (!d.current_work_area_
2409 || !d.current_work_area_->completer().popupPossible(
2410 currentBufferView()->cursor()))
2415 if (!d.current_work_area_
2416 || !d.current_work_area_->completer().inlinePossible(
2417 currentBufferView()->cursor()))
2421 case LFUN_COMPLETION_ACCEPT:
2422 if (!d.current_work_area_
2423 || (!d.current_work_area_->completer().popupVisible()
2424 && !d.current_work_area_->completer().inlineVisible()
2425 && !d.current_work_area_->completer().completionAvailable()))
2429 case LFUN_COMPLETION_CANCEL:
2430 if (!d.current_work_area_
2431 || (!d.current_work_area_->completer().popupVisible()
2432 && !d.current_work_area_->completer().inlineVisible()))
2436 case LFUN_BUFFER_ZOOM_OUT:
2437 case LFUN_BUFFER_ZOOM_IN: {
2438 // only diff between these two is that the default for ZOOM_OUT
2440 bool const neg_zoom =
2441 convert<int>(cmd.argument()) < 0 ||
2442 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2443 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2444 docstring const msg =
2445 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2449 enable = doc_buffer;
2453 case LFUN_BUFFER_ZOOM: {
2454 bool const less_than_min_zoom =
2455 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2456 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2457 docstring const msg =
2458 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2463 enable = doc_buffer;
2467 case LFUN_BUFFER_MOVE_NEXT:
2468 case LFUN_BUFFER_MOVE_PREVIOUS:
2469 // we do not cycle when moving
2470 case LFUN_BUFFER_NEXT:
2471 case LFUN_BUFFER_PREVIOUS:
2472 // because we cycle, it doesn't matter whether on first or last
2473 enable = (d.currentTabWorkArea()->count() > 1);
2475 case LFUN_BUFFER_SWITCH:
2476 // toggle on the current buffer, but do not toggle off
2477 // the other ones (is that a good idea?)
2479 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2480 flag.setOnOff(true);
2483 case LFUN_VC_REGISTER:
2484 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2486 case LFUN_VC_RENAME:
2487 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2490 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2492 case LFUN_VC_CHECK_IN:
2493 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2495 case LFUN_VC_CHECK_OUT:
2496 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2498 case LFUN_VC_LOCKING_TOGGLE:
2499 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2500 && doc_buffer->lyxvc().lockingToggleEnabled();
2501 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2503 case LFUN_VC_REVERT:
2504 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2505 && !doc_buffer->hasReadonlyFlag();
2507 case LFUN_VC_UNDO_LAST:
2508 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2510 case LFUN_VC_REPO_UPDATE:
2511 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2513 case LFUN_VC_COMMAND: {
2514 if (cmd.argument().empty())
2516 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2520 case LFUN_VC_COMPARE:
2521 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2524 case LFUN_SERVER_GOTO_FILE_ROW:
2525 case LFUN_LYX_ACTIVATE:
2526 case LFUN_WINDOW_RAISE:
2528 case LFUN_FORWARD_SEARCH:
2529 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2532 case LFUN_FILE_INSERT_PLAINTEXT:
2533 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2534 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2537 case LFUN_SPELLING_CONTINUOUSLY:
2538 flag.setOnOff(lyxrc.spellcheck_continuously);
2541 case LFUN_CITATION_OPEN:
2550 flag.setEnabled(false);
2556 static FileName selectTemplateFile()
2558 FileDialog dlg(qt_("Select template file"));
2559 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2560 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2562 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2563 QStringList(qt_("LyX Documents (*.lyx)")));
2565 if (result.first == FileDialog::Later)
2567 if (result.second.isEmpty())
2569 return FileName(fromqstr(result.second));
2573 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2577 Buffer * newBuffer = nullptr;
2579 newBuffer = checkAndLoadLyXFile(filename);
2580 } catch (ExceptionMessage const &) {
2587 message(_("Document not loaded."));
2591 setBuffer(newBuffer);
2592 newBuffer->errors("Parse");
2595 theSession().lastFiles().add(filename);
2596 theSession().writeFile();
2603 void GuiView::openDocument(string const & fname)
2605 string initpath = lyxrc.document_path;
2607 if (documentBufferView()) {
2608 string const trypath = documentBufferView()->buffer().filePath();
2609 // If directory is writeable, use this as default.
2610 if (FileName(trypath).isDirWritable())
2616 if (fname.empty()) {
2617 FileDialog dlg(qt_("Select document to open"));
2618 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2619 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2621 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2622 FileDialog::Result result =
2623 dlg.open(toqstr(initpath), filter);
2625 if (result.first == FileDialog::Later)
2628 filename = fromqstr(result.second);
2630 // check selected filename
2631 if (filename.empty()) {
2632 message(_("Canceled."));
2638 // get absolute path of file and add ".lyx" to the filename if
2640 FileName const fullname =
2641 fileSearch(string(), filename, "lyx", support::may_not_exist);
2642 if (!fullname.empty())
2643 filename = fullname.absFileName();
2645 if (!fullname.onlyPath().isDirectory()) {
2646 Alert::warning(_("Invalid filename"),
2647 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2648 from_utf8(fullname.absFileName())));
2652 // if the file doesn't exist and isn't already open (bug 6645),
2653 // let the user create one
2654 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2655 !LyXVC::file_not_found_hook(fullname)) {
2656 // the user specifically chose this name. Believe him.
2657 Buffer * const b = newFile(filename, string(), true);
2663 docstring const disp_fn = makeDisplayPath(filename);
2664 message(bformat(_("Opening document %1$s..."), disp_fn));
2667 Buffer * buf = loadDocument(fullname);
2669 str2 = bformat(_("Document %1$s opened."), disp_fn);
2670 if (buf->lyxvc().inUse())
2671 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2672 " " + _("Version control detected.");
2674 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2679 // FIXME: clean that
2680 static bool import(GuiView * lv, FileName const & filename,
2681 string const & format, ErrorList & errorList)
2683 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2685 string loader_format;
2686 vector<string> loaders = theConverters().loaders();
2687 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2688 for (string const & loader : loaders) {
2689 if (!theConverters().isReachable(format, loader))
2692 string const tofile =
2693 support::changeExtension(filename.absFileName(),
2694 theFormats().extension(loader));
2695 if (theConverters().convert(nullptr, filename, FileName(tofile),
2696 filename, format, loader, errorList) != Converters::SUCCESS)
2698 loader_format = loader;
2701 if (loader_format.empty()) {
2702 frontend::Alert::error(_("Couldn't import file"),
2703 bformat(_("No information for importing the format %1$s."),
2704 translateIfPossible(theFormats().prettyName(format))));
2708 loader_format = format;
2710 if (loader_format == "lyx") {
2711 Buffer * buf = lv->loadDocument(lyxfile);
2715 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2719 bool as_paragraphs = loader_format == "textparagraph";
2720 string filename2 = (loader_format == format) ? filename.absFileName()
2721 : support::changeExtension(filename.absFileName(),
2722 theFormats().extension(loader_format));
2723 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2725 guiApp->setCurrentView(lv);
2726 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2733 void GuiView::importDocument(string const & argument)
2736 string filename = split(argument, format, ' ');
2738 LYXERR(Debug::INFO, format << " file: " << filename);
2740 // need user interaction
2741 if (filename.empty()) {
2742 string initpath = lyxrc.document_path;
2743 if (documentBufferView()) {
2744 string const trypath = documentBufferView()->buffer().filePath();
2745 // If directory is writeable, use this as default.
2746 if (FileName(trypath).isDirWritable())
2750 docstring const text = bformat(_("Select %1$s file to import"),
2751 translateIfPossible(theFormats().prettyName(format)));
2753 FileDialog dlg(toqstr(text));
2754 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2755 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2757 docstring filter = translateIfPossible(theFormats().prettyName(format));
2760 filter += from_utf8(theFormats().extensions(format));
2763 FileDialog::Result result =
2764 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2766 if (result.first == FileDialog::Later)
2769 filename = fromqstr(result.second);
2771 // check selected filename
2772 if (filename.empty())
2773 message(_("Canceled."));
2776 if (filename.empty())
2779 // get absolute path of file
2780 FileName const fullname(support::makeAbsPath(filename));
2782 // Can happen if the user entered a path into the dialog
2784 if (fullname.onlyFileName().empty()) {
2785 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2786 "Aborting import."),
2787 from_utf8(fullname.absFileName()));
2788 frontend::Alert::error(_("File name error"), msg);
2789 message(_("Canceled."));
2794 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2796 // Check if the document already is open
2797 Buffer * buf = theBufferList().getBuffer(lyxfile);
2800 if (!closeBuffer()) {
2801 message(_("Canceled."));
2806 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2808 // if the file exists already, and we didn't do
2809 // -i lyx thefile.lyx, warn
2810 if (lyxfile.exists() && fullname != lyxfile) {
2812 docstring text = bformat(_("The document %1$s already exists.\n\n"
2813 "Do you want to overwrite that document?"), displaypath);
2814 int const ret = Alert::prompt(_("Overwrite document?"),
2815 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2818 message(_("Canceled."));
2823 message(bformat(_("Importing %1$s..."), displaypath));
2824 ErrorList errorList;
2825 if (import(this, fullname, format, errorList))
2826 message(_("imported."));
2828 message(_("file not imported!"));
2830 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2834 void GuiView::newDocument(string const & filename, string templatefile,
2837 FileName initpath(lyxrc.document_path);
2838 if (documentBufferView()) {
2839 FileName const trypath(documentBufferView()->buffer().filePath());
2840 // If directory is writeable, use this as default.
2841 if (trypath.isDirWritable())
2845 if (from_template) {
2846 if (templatefile.empty())
2847 templatefile = selectTemplateFile().absFileName();
2848 if (templatefile.empty())
2853 if (filename.empty())
2854 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2856 b = newFile(filename, templatefile, true);
2861 // If no new document could be created, it is unsure
2862 // whether there is a valid BufferView.
2863 if (currentBufferView())
2864 // Ensure the cursor is correctly positioned on screen.
2865 currentBufferView()->showCursor();
2869 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2871 BufferView * bv = documentBufferView();
2876 FileName filename(to_utf8(fname));
2877 if (filename.empty()) {
2878 // Launch a file browser
2880 string initpath = lyxrc.document_path;
2881 string const trypath = bv->buffer().filePath();
2882 // If directory is writeable, use this as default.
2883 if (FileName(trypath).isDirWritable())
2887 FileDialog dlg(qt_("Select LyX document to insert"));
2888 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2889 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2891 FileDialog::Result result = dlg.open(toqstr(initpath),
2892 QStringList(qt_("LyX Documents (*.lyx)")));
2894 if (result.first == FileDialog::Later)
2898 filename.set(fromqstr(result.second));
2900 // check selected filename
2901 if (filename.empty()) {
2902 // emit message signal.
2903 message(_("Canceled."));
2908 bv->insertLyXFile(filename, ignorelang);
2909 bv->buffer().errors("Parse");
2914 string const GuiView::getTemplatesPath(Buffer & b)
2916 // We start off with the user's templates path
2917 string result = addPath(package().user_support().absFileName(), "templates");
2918 // Check for the document language
2919 string const langcode = b.params().language->code();
2920 string const shortcode = langcode.substr(0, 2);
2921 if (!langcode.empty() && shortcode != "en") {
2922 string subpath = addPath(result, shortcode);
2923 string subpath_long = addPath(result, langcode);
2924 // If we have a subdirectory for the language already,
2926 FileName sp = FileName(subpath);
2927 if (sp.isDirectory())
2929 else if (FileName(subpath_long).isDirectory())
2930 result = subpath_long;
2932 // Ask whether we should create such a subdirectory
2933 docstring const text =
2934 bformat(_("It is suggested to save the template in a subdirectory\n"
2935 "appropriate to the document language (%1$s).\n"
2936 "This subdirectory does not exists yet.\n"
2937 "Do you want to create it?"),
2938 _(b.params().language->display()));
2939 if (Alert::prompt(_("Create Language Directory?"),
2940 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2941 // If the user agreed, we try to create it and report if this failed.
2942 if (!sp.createDirectory(0777))
2943 Alert::error(_("Subdirectory creation failed!"),
2944 _("Could not create subdirectory.\n"
2945 "The template will be saved in the parent directory."));
2951 // Do we have a layout category?
2952 string const cat = b.params().baseClass() ?
2953 b.params().baseClass()->category()
2956 string subpath = addPath(result, cat);
2957 // If we have a subdirectory for the category already,
2959 FileName sp = FileName(subpath);
2960 if (sp.isDirectory())
2963 // Ask whether we should create such a subdirectory
2964 docstring const text =
2965 bformat(_("It is suggested to save the template in a subdirectory\n"
2966 "appropriate to the layout category (%1$s).\n"
2967 "This subdirectory does not exists yet.\n"
2968 "Do you want to create it?"),
2970 if (Alert::prompt(_("Create Category Directory?"),
2971 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2972 // If the user agreed, we try to create it and report if this failed.
2973 if (!sp.createDirectory(0777))
2974 Alert::error(_("Subdirectory creation failed!"),
2975 _("Could not create subdirectory.\n"
2976 "The template will be saved in the parent directory."));
2986 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2988 FileName fname = b.fileName();
2989 FileName const oldname = fname;
2990 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2992 if (!newname.empty()) {
2995 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2997 fname = support::makeAbsPath(to_utf8(newname),
2998 oldname.onlyPath().absFileName());
3000 // Switch to this Buffer.
3003 // No argument? Ask user through dialog.
3005 QString const title = as_template ? qt_("Choose a filename to save template as")
3006 : qt_("Choose a filename to save document as");
3007 FileDialog dlg(title);
3008 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3009 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3011 if (!isLyXFileName(fname.absFileName()))
3012 fname.changeExtension(".lyx");
3014 string const path = as_template ?
3016 : fname.onlyPath().absFileName();
3017 FileDialog::Result result =
3018 dlg.save(toqstr(path),
3019 QStringList(qt_("LyX Documents (*.lyx)")),
3020 toqstr(fname.onlyFileName()));
3022 if (result.first == FileDialog::Later)
3025 fname.set(fromqstr(result.second));
3030 if (!isLyXFileName(fname.absFileName()))
3031 fname.changeExtension(".lyx");
3034 // fname is now the new Buffer location.
3036 // if there is already a Buffer open with this name, we do not want
3037 // to have another one. (the second test makes sure we're not just
3038 // trying to overwrite ourselves, which is fine.)
3039 if (theBufferList().exists(fname) && fname != oldname
3040 && theBufferList().getBuffer(fname) != &b) {
3041 docstring const text =
3042 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3043 "Please close it before attempting to overwrite it.\n"
3044 "Do you want to choose a new filename?"),
3045 from_utf8(fname.absFileName()));
3046 int const ret = Alert::prompt(_("Chosen File Already Open"),
3047 text, 0, 1, _("&Rename"), _("&Cancel"));
3049 case 0: return renameBuffer(b, docstring(), kind);
3050 case 1: return false;
3055 bool const existsLocal = fname.exists();
3056 bool const existsInVC = LyXVC::fileInVC(fname);
3057 if (existsLocal || existsInVC) {
3058 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3059 if (kind != LV_WRITE_AS && existsInVC) {
3060 // renaming to a name that is already in VC
3062 docstring text = bformat(_("The document %1$s "
3063 "is already registered.\n\n"
3064 "Do you want to choose a new name?"),
3066 docstring const title = (kind == LV_VC_RENAME) ?
3067 _("Rename document?") : _("Copy document?");
3068 docstring const button = (kind == LV_VC_RENAME) ?
3069 _("&Rename") : _("&Copy");
3070 int const ret = Alert::prompt(title, text, 0, 1,
3071 button, _("&Cancel"));
3073 case 0: return renameBuffer(b, docstring(), kind);
3074 case 1: return false;
3079 docstring text = bformat(_("The document %1$s "
3080 "already exists.\n\n"
3081 "Do you want to overwrite that document?"),
3083 int const ret = Alert::prompt(_("Overwrite document?"),
3084 text, 0, 2, _("&Overwrite"),
3085 _("&Rename"), _("&Cancel"));
3088 case 1: return renameBuffer(b, docstring(), kind);
3089 case 2: return false;
3095 case LV_VC_RENAME: {
3096 string msg = b.lyxvc().rename(fname);
3099 message(from_utf8(msg));
3103 string msg = b.lyxvc().copy(fname);
3106 message(from_utf8(msg));
3110 case LV_WRITE_AS_TEMPLATE:
3113 // LyXVC created the file already in case of LV_VC_RENAME or
3114 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3115 // relative paths of included stuff right if we moved e.g. from
3116 // /a/b.lyx to /a/c/b.lyx.
3118 bool const saved = saveBuffer(b, fname);
3125 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3127 FileName fname = b.fileName();
3129 FileDialog dlg(qt_("Choose a filename to export the document as"));
3130 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3133 QString const anyformat = qt_("Guess from extension (*.*)");
3136 vector<Format const *> export_formats;
3137 for (Format const & f : theFormats())
3138 if (f.documentFormat())
3139 export_formats.push_back(&f);
3140 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3141 map<QString, string> fmap;
3144 for (Format const * f : export_formats) {
3145 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3146 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3148 from_ascii(f->extension())));
3149 types << loc_filter;
3150 fmap[loc_filter] = f->name();
3151 if (from_ascii(f->name()) == iformat) {
3152 filter = loc_filter;
3153 ext = f->extension();
3156 string ofname = fname.onlyFileName();
3158 ofname = support::changeExtension(ofname, ext);
3159 FileDialog::Result result =
3160 dlg.save(toqstr(fname.onlyPath().absFileName()),
3164 if (result.first != FileDialog::Chosen)
3168 fname.set(fromqstr(result.second));
3169 if (filter == anyformat)
3170 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3172 fmt_name = fmap[filter];
3173 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3174 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3176 if (fmt_name.empty() || fname.empty())
3179 // fname is now the new Buffer location.
3180 if (FileName(fname).exists()) {
3181 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3182 docstring text = bformat(_("The document %1$s already "
3183 "exists.\n\nDo you want to "
3184 "overwrite that document?"),
3186 int const ret = Alert::prompt(_("Overwrite document?"),
3187 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3190 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3191 case 2: return false;
3195 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3198 return dr.dispatched();
3202 bool GuiView::saveBuffer(Buffer & b)
3204 return saveBuffer(b, FileName());
3208 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3210 if (workArea(b) && workArea(b)->inDialogMode())
3213 if (fn.empty() && b.isUnnamed())
3214 return renameBuffer(b, docstring());
3216 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3218 theSession().lastFiles().add(b.fileName());
3219 theSession().writeFile();
3223 // Switch to this Buffer.
3226 // FIXME: we don't tell the user *WHY* the save failed !!
3227 docstring const file = makeDisplayPath(b.absFileName(), 30);
3228 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3229 "Do you want to rename the document and "
3230 "try again?"), file);
3231 int const ret = Alert::prompt(_("Rename and save?"),
3232 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3235 if (!renameBuffer(b, docstring()))
3244 return saveBuffer(b, fn);
3248 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3250 return closeWorkArea(wa, false);
3254 // We only want to close the buffer if it is not visible in other workareas
3255 // of the same view, nor in other views, and if this is not a child
3256 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3258 Buffer & buf = wa->bufferView().buffer();
3260 bool last_wa = d.countWorkAreasOf(buf) == 1
3261 && !inOtherView(buf) && !buf.parent();
3263 bool close_buffer = last_wa;
3266 if (lyxrc.close_buffer_with_last_view == "yes")
3268 else if (lyxrc.close_buffer_with_last_view == "no")
3269 close_buffer = false;
3272 if (buf.isUnnamed())
3273 file = from_utf8(buf.fileName().onlyFileName());
3275 file = buf.fileName().displayName(30);
3276 docstring const text = bformat(
3277 _("Last view on document %1$s is being closed.\n"
3278 "Would you like to close or hide the document?\n"
3280 "Hidden documents can be displayed back through\n"
3281 "the menu: View->Hidden->...\n"
3283 "To remove this question, set your preference in:\n"
3284 " Tools->Preferences->Look&Feel->UserInterface\n"
3286 int ret = Alert::prompt(_("Close or hide document?"),
3287 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3290 close_buffer = (ret == 0);
3294 return closeWorkArea(wa, close_buffer);
3298 bool GuiView::closeBuffer()
3300 GuiWorkArea * wa = currentMainWorkArea();
3301 // coverity complained about this
3302 // it seems unnecessary, but perhaps is worth the check
3303 LASSERT(wa, return false);
3305 setCurrentWorkArea(wa);
3306 Buffer & buf = wa->bufferView().buffer();
3307 return closeWorkArea(wa, !buf.parent());
3311 void GuiView::writeSession() const {
3312 GuiWorkArea const * active_wa = currentMainWorkArea();
3313 for (int i = 0; i < d.splitter_->count(); ++i) {
3314 TabWorkArea * twa = d.tabWorkArea(i);
3315 for (int j = 0; j < twa->count(); ++j) {
3316 GuiWorkArea * wa = twa->workArea(j);
3317 Buffer & buf = wa->bufferView().buffer();
3318 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3324 bool GuiView::closeBufferAll()
3327 for (auto & buf : theBufferList()) {
3328 if (!saveBufferIfNeeded(*buf, false)) {
3329 // Closing has been cancelled, so abort.
3334 // Close the workareas in all other views
3335 QList<int> const ids = guiApp->viewIds();
3336 for (int i = 0; i != ids.size(); ++i) {
3337 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3341 // Close our own workareas
3342 if (!closeWorkAreaAll())
3349 bool GuiView::closeWorkAreaAll()
3351 setCurrentWorkArea(currentMainWorkArea());
3353 // We might be in a situation that there is still a tabWorkArea, but
3354 // there are no tabs anymore. This can happen when we get here after a
3355 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3356 // many TabWorkArea's have no documents anymore.
3359 // We have to call count() each time, because it can happen that
3360 // more than one splitter will disappear in one iteration (bug 5998).
3361 while (d.splitter_->count() > empty_twa) {
3362 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3364 if (twa->count() == 0)
3367 setCurrentWorkArea(twa->currentWorkArea());
3368 if (!closeTabWorkArea(twa))
3376 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3381 Buffer & buf = wa->bufferView().buffer();
3383 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3384 Alert::warning(_("Close document"),
3385 _("Document could not be closed because it is being processed by LyX."));
3390 return closeBuffer(buf);
3392 if (!inMultiTabs(wa))
3393 if (!saveBufferIfNeeded(buf, true))
3401 bool GuiView::closeBuffer(Buffer & buf)
3403 bool success = true;
3404 for (Buffer * child_buf : buf.getChildren()) {
3405 if (theBufferList().isOthersChild(&buf, child_buf)) {
3406 child_buf->setParent(nullptr);
3410 // FIXME: should we look in other tabworkareas?
3411 // ANSWER: I don't think so. I've tested, and if the child is
3412 // open in some other window, it closes without a problem.
3413 GuiWorkArea * child_wa = workArea(*child_buf);
3416 // If we are in a close_event all children will be closed in some time,
3417 // so no need to do it here. This will ensure that the children end up
3418 // in the session file in the correct order. If we close the master
3419 // buffer, we can close or release the child buffers here too.
3421 success = closeWorkArea(child_wa, true);
3425 // In this case the child buffer is open but hidden.
3426 // Even in this case, children can be dirty (e.g.,
3427 // after a label change in the master, see #11405).
3428 // Therefore, check this
3429 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3430 // If we are in a close_event all children will be closed in some time,
3431 // so no need to do it here. This will ensure that the children end up
3432 // in the session file in the correct order. If we close the master
3433 // buffer, we can close or release the child buffers here too.
3436 // Save dirty buffers also if closing_!
3437 if (saveBufferIfNeeded(*child_buf, false)) {
3438 child_buf->removeAutosaveFile();
3439 theBufferList().release(child_buf);
3441 // Saving of dirty children has been cancelled.
3442 // Cancel the whole process.
3449 // goto bookmark to update bookmark pit.
3450 // FIXME: we should update only the bookmarks related to this buffer!
3451 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3452 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3453 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3454 guiApp->gotoBookmark(i, false, false);
3456 if (saveBufferIfNeeded(buf, false)) {
3457 buf.removeAutosaveFile();
3458 theBufferList().release(&buf);
3462 // open all children again to avoid a crash because of dangling
3463 // pointers (bug 6603)
3469 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3471 while (twa == d.currentTabWorkArea()) {
3472 twa->setCurrentIndex(twa->count() - 1);
3474 GuiWorkArea * wa = twa->currentWorkArea();
3475 Buffer & b = wa->bufferView().buffer();
3477 // We only want to close the buffer if the same buffer is not visible
3478 // in another view, and if this is not a child and if we are closing
3479 // a view (not a tabgroup).
3480 bool const close_buffer =
3481 !inOtherView(b) && !b.parent() && closing_;
3483 if (!closeWorkArea(wa, close_buffer))
3490 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3492 if (buf.isClean() || buf.paragraphs().empty())
3495 // Switch to this Buffer.
3501 if (buf.isUnnamed()) {
3502 file = from_utf8(buf.fileName().onlyFileName());
3505 FileName filename = buf.fileName();
3507 file = filename.displayName(30);
3508 exists = filename.exists();
3511 // Bring this window to top before asking questions.
3516 if (hiding && buf.isUnnamed()) {
3517 docstring const text = bformat(_("The document %1$s has not been "
3518 "saved yet.\n\nDo you want to save "
3519 "the document?"), file);
3520 ret = Alert::prompt(_("Save new document?"),
3521 text, 0, 1, _("&Save"), _("&Cancel"));
3525 docstring const text = exists ?
3526 bformat(_("The document %1$s has unsaved changes."
3527 "\n\nDo you want to save the document or "
3528 "discard the changes?"), file) :
3529 bformat(_("The document %1$s has not been saved yet."
3530 "\n\nDo you want to save the document or "
3531 "discard it entirely?"), file);
3532 docstring const title = exists ?
3533 _("Save changed document?") : _("Save document?");
3534 ret = Alert::prompt(title, text, 0, 2,
3535 _("&Save"), _("&Discard"), _("&Cancel"));
3540 if (!saveBuffer(buf))
3544 // If we crash after this we could have no autosave file
3545 // but I guess this is really improbable (Jug).
3546 // Sometimes improbable things happen:
3547 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3548 // buf.removeAutosaveFile();
3550 // revert all changes
3561 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3563 Buffer & buf = wa->bufferView().buffer();
3565 for (int i = 0; i != d.splitter_->count(); ++i) {
3566 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3567 if (wa_ && wa_ != wa)
3570 return inOtherView(buf);
3574 bool GuiView::inOtherView(Buffer & buf)
3576 QList<int> const ids = guiApp->viewIds();
3578 for (int i = 0; i != ids.size(); ++i) {
3582 if (guiApp->view(ids[i]).workArea(buf))
3589 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3591 if (!documentBufferView())
3594 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3595 Buffer * const curbuf = &documentBufferView()->buffer();
3596 int nwa = twa->count();
3597 for (int i = 0; i < nwa; ++i) {
3598 if (&workArea(i)->bufferView().buffer() == curbuf) {
3600 if (np == NEXTBUFFER)
3601 next_index = (i == nwa - 1 ? 0 : i + 1);
3603 next_index = (i == 0 ? nwa - 1 : i - 1);
3605 twa->moveTab(i, next_index);
3607 setBuffer(&workArea(next_index)->bufferView().buffer());
3615 /// make sure the document is saved
3616 static bool ensureBufferClean(Buffer * buffer)
3618 LASSERT(buffer, return false);
3619 if (buffer->isClean() && !buffer->isUnnamed())
3622 docstring const file = buffer->fileName().displayName(30);
3625 if (!buffer->isUnnamed()) {
3626 text = bformat(_("The document %1$s has unsaved "
3627 "changes.\n\nDo you want to save "
3628 "the document?"), file);
3629 title = _("Save changed document?");
3632 text = bformat(_("The document %1$s has not been "
3633 "saved yet.\n\nDo you want to save "
3634 "the document?"), file);
3635 title = _("Save new document?");
3637 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3640 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3642 return buffer->isClean() && !buffer->isUnnamed();
3646 bool GuiView::reloadBuffer(Buffer & buf)
3648 currentBufferView()->cursor().reset();
3649 Buffer::ReadStatus status = buf.reload();
3650 return status == Buffer::ReadSuccess;
3654 void GuiView::checkExternallyModifiedBuffers()
3656 for (Buffer * buf : theBufferList()) {
3657 if (buf->fileName().exists() && buf->isChecksumModified()) {
3658 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3659 " Reload now? Any local changes will be lost."),
3660 from_utf8(buf->absFileName()));
3661 int const ret = Alert::prompt(_("Reload externally changed document?"),
3662 text, 0, 1, _("&Reload"), _("&Cancel"));
3670 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3672 Buffer * buffer = documentBufferView()
3673 ? &(documentBufferView()->buffer()) : nullptr;
3675 switch (cmd.action()) {
3676 case LFUN_VC_REGISTER:
3677 if (!buffer || !ensureBufferClean(buffer))
3679 if (!buffer->lyxvc().inUse()) {
3680 if (buffer->lyxvc().registrer()) {
3681 reloadBuffer(*buffer);
3682 dr.clearMessageUpdate();
3687 case LFUN_VC_RENAME:
3688 case LFUN_VC_COPY: {
3689 if (!buffer || !ensureBufferClean(buffer))
3691 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3692 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3693 // Some changes are not yet committed.
3694 // We test here and not in getStatus(), since
3695 // this test is expensive.
3697 LyXVC::CommandResult ret =
3698 buffer->lyxvc().checkIn(log);
3700 if (ret == LyXVC::ErrorCommand ||
3701 ret == LyXVC::VCSuccess)
3702 reloadBuffer(*buffer);
3703 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3704 frontend::Alert::error(
3705 _("Revision control error."),
3706 _("Document could not be checked in."));
3710 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3711 LV_VC_RENAME : LV_VC_COPY;
3712 renameBuffer(*buffer, cmd.argument(), kind);
3717 case LFUN_VC_CHECK_IN:
3718 if (!buffer || !ensureBufferClean(buffer))
3720 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3722 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3724 // Only skip reloading if the checkin was cancelled or
3725 // an error occurred before the real checkin VCS command
3726 // was executed, since the VCS might have changed the
3727 // file even if it could not checkin successfully.
3728 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3729 reloadBuffer(*buffer);
3733 case LFUN_VC_CHECK_OUT:
3734 if (!buffer || !ensureBufferClean(buffer))
3736 if (buffer->lyxvc().inUse()) {
3737 dr.setMessage(buffer->lyxvc().checkOut());
3738 reloadBuffer(*buffer);
3742 case LFUN_VC_LOCKING_TOGGLE:
3743 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3745 if (buffer->lyxvc().inUse()) {
3746 string res = buffer->lyxvc().lockingToggle();
3748 frontend::Alert::error(_("Revision control error."),
3749 _("Error when setting the locking property."));
3752 reloadBuffer(*buffer);
3757 case LFUN_VC_REVERT:
3760 if (buffer->lyxvc().revert()) {
3761 reloadBuffer(*buffer);
3762 dr.clearMessageUpdate();
3766 case LFUN_VC_UNDO_LAST:
3769 buffer->lyxvc().undoLast();
3770 reloadBuffer(*buffer);
3771 dr.clearMessageUpdate();
3774 case LFUN_VC_REPO_UPDATE:
3777 if (ensureBufferClean(buffer)) {
3778 dr.setMessage(buffer->lyxvc().repoUpdate());
3779 checkExternallyModifiedBuffers();
3783 case LFUN_VC_COMMAND: {
3784 string flag = cmd.getArg(0);
3785 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3788 if (contains(flag, 'M')) {
3789 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3792 string path = cmd.getArg(1);
3793 if (contains(path, "$$p") && buffer)
3794 path = subst(path, "$$p", buffer->filePath());
3795 LYXERR(Debug::LYXVC, "Directory: " << path);
3797 if (!pp.isReadableDirectory()) {
3798 lyxerr << _("Directory is not accessible.") << endl;
3801 support::PathChanger p(pp);
3803 string command = cmd.getArg(2);
3804 if (command.empty())
3807 command = subst(command, "$$i", buffer->absFileName());
3808 command = subst(command, "$$p", buffer->filePath());
3810 command = subst(command, "$$m", to_utf8(message));
3811 LYXERR(Debug::LYXVC, "Command: " << command);
3813 one.startscript(Systemcall::Wait, command);
3817 if (contains(flag, 'I'))
3818 buffer->markDirty();
3819 if (contains(flag, 'R'))
3820 reloadBuffer(*buffer);
3825 case LFUN_VC_COMPARE: {
3826 if (cmd.argument().empty()) {
3827 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3833 string rev1 = cmd.getArg(0);
3837 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3840 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3841 f2 = buffer->absFileName();
3843 string rev2 = cmd.getArg(1);
3847 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3851 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3852 f1 << "\n" << f2 << "\n" );
3853 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3854 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3864 void GuiView::openChildDocument(string const & fname)
3866 LASSERT(documentBufferView(), return);
3867 Buffer & buffer = documentBufferView()->buffer();
3868 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3869 documentBufferView()->saveBookmark(false);
3870 Buffer * child = nullptr;
3871 if (theBufferList().exists(filename)) {
3872 child = theBufferList().getBuffer(filename);
3875 message(bformat(_("Opening child document %1$s..."),
3876 makeDisplayPath(filename.absFileName())));
3877 child = loadDocument(filename, false);
3879 // Set the parent name of the child document.
3880 // This makes insertion of citations and references in the child work,
3881 // when the target is in the parent or another child document.
3883 child->setParent(&buffer);
3887 bool GuiView::goToFileRow(string const & argument)
3891 size_t i = argument.find_last_of(' ');
3892 if (i != string::npos) {
3893 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3894 istringstream is(argument.substr(i + 1));
3899 if (i == string::npos) {
3900 LYXERR0("Wrong argument: " << argument);
3903 Buffer * buf = nullptr;
3904 string const realtmp = package().temp_dir().realPath();
3905 // We have to use os::path_prefix_is() here, instead of
3906 // simply prefixIs(), because the file name comes from
3907 // an external application and may need case adjustment.
3908 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3909 buf = theBufferList().getBufferFromTmp(file_name, true);
3910 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3911 << (buf ? " success" : " failed"));
3913 // Must replace extension of the file to be .lyx
3914 // and get full path
3915 FileName const s = fileSearch(string(),
3916 support::changeExtension(file_name, ".lyx"), "lyx");
3917 // Either change buffer or load the file
3918 if (theBufferList().exists(s))
3919 buf = theBufferList().getBuffer(s);
3920 else if (s.exists()) {
3921 buf = loadDocument(s);
3926 _("File does not exist: %1$s"),
3927 makeDisplayPath(file_name)));
3933 _("No buffer for file: %1$s."),
3934 makeDisplayPath(file_name))
3939 bool success = documentBufferView()->setCursorFromRow(row);
3941 LYXERR(Debug::LATEX,
3942 "setCursorFromRow: invalid position for row " << row);
3943 frontend::Alert::error(_("Inverse Search Failed"),
3944 _("Invalid position requested by inverse search.\n"
3945 "You may need to update the viewed document."));
3951 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3953 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3954 menu->exec(QCursor::pos());
3959 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3960 Buffer const * orig, Buffer * clone, string const & format)
3962 Buffer::ExportStatus const status = func(format);
3964 // the cloning operation will have produced a clone of the entire set of
3965 // documents, starting from the master. so we must delete those.
3966 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3968 busyBuffers.remove(orig);
3973 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3974 Buffer const * orig, Buffer * clone, string const & format)
3976 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3978 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3982 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3983 Buffer const * orig, Buffer * clone, string const & format)
3985 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3987 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3991 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3992 Buffer const * orig, Buffer * clone, string const & format)
3994 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3996 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4000 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4001 Buffer const * used_buffer,
4002 docstring const & msg,
4003 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4004 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4005 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4006 bool allow_async, bool use_tmpdir)
4011 string format = argument;
4013 format = used_buffer->params().getDefaultOutputFormat();
4014 processing_format = format;
4016 progress_->clearMessages();
4019 #if EXPORT_in_THREAD
4021 GuiViewPrivate::busyBuffers.insert(used_buffer);
4022 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4023 if (!cloned_buffer) {
4024 Alert::error(_("Export Error"),
4025 _("Error cloning the Buffer."));
4028 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4033 setPreviewFuture(f);
4034 last_export_format = used_buffer->params().bufferFormat();
4037 // We are asynchronous, so we don't know here anything about the success
4040 Buffer::ExportStatus status;
4042 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4043 } else if (previewFunc) {
4044 status = (used_buffer->*previewFunc)(format);
4047 handleExportStatus(gv_, status, format);
4049 return (status == Buffer::ExportSuccess
4050 || status == Buffer::PreviewSuccess);
4054 Buffer::ExportStatus status;
4056 status = (used_buffer->*syncFunc)(format, true);
4057 } else if (previewFunc) {
4058 status = (used_buffer->*previewFunc)(format);
4061 handleExportStatus(gv_, status, format);
4063 return (status == Buffer::ExportSuccess
4064 || status == Buffer::PreviewSuccess);
4068 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4070 BufferView * bv = currentBufferView();
4071 LASSERT(bv, return);
4073 // Let the current BufferView dispatch its own actions.
4074 bv->dispatch(cmd, dr);
4075 if (dr.dispatched()) {
4076 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4077 updateDialog("document", "");
4081 // Try with the document BufferView dispatch if any.
4082 BufferView * doc_bv = documentBufferView();
4083 if (doc_bv && doc_bv != bv) {
4084 doc_bv->dispatch(cmd, dr);
4085 if (dr.dispatched()) {
4086 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4087 updateDialog("document", "");
4092 // Then let the current Cursor dispatch its own actions.
4093 bv->cursor().dispatch(cmd);
4095 // update completion. We do it here and not in
4096 // processKeySym to avoid another redraw just for a
4097 // changed inline completion
4098 if (cmd.origin() == FuncRequest::KEYBOARD) {
4099 if (cmd.action() == LFUN_SELF_INSERT
4100 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4101 updateCompletion(bv->cursor(), true, true);
4102 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4103 updateCompletion(bv->cursor(), false, true);
4105 updateCompletion(bv->cursor(), false, false);
4108 dr = bv->cursor().result();
4112 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4114 BufferView * bv = currentBufferView();
4115 // By default we won't need any update.
4116 dr.screenUpdate(Update::None);
4117 // assume cmd will be dispatched
4118 dr.dispatched(true);
4120 Buffer * doc_buffer = documentBufferView()
4121 ? &(documentBufferView()->buffer()) : nullptr;
4123 if (cmd.origin() == FuncRequest::TOC) {
4124 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4125 toc->doDispatch(bv->cursor(), cmd, dr);
4129 string const argument = to_utf8(cmd.argument());
4131 switch(cmd.action()) {
4132 case LFUN_BUFFER_CHILD_OPEN:
4133 openChildDocument(to_utf8(cmd.argument()));
4136 case LFUN_BUFFER_IMPORT:
4137 importDocument(to_utf8(cmd.argument()));
4140 case LFUN_MASTER_BUFFER_EXPORT:
4142 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4144 case LFUN_BUFFER_EXPORT: {
4147 // GCC only sees strfwd.h when building merged
4148 if (::lyx::operator==(cmd.argument(), "custom")) {
4149 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4150 // so the following test should not be needed.
4151 // In principle, we could try to switch to such a view...
4152 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4153 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4157 string const dest = cmd.getArg(1);
4158 FileName target_dir;
4159 if (!dest.empty() && FileName::isAbsolute(dest))
4160 target_dir = FileName(support::onlyPath(dest));
4162 target_dir = doc_buffer->fileName().onlyPath();
4164 string const format = (argument.empty() || argument == "default") ?
4165 doc_buffer->params().getDefaultOutputFormat() : argument;
4167 if ((dest.empty() && doc_buffer->isUnnamed())
4168 || !target_dir.isDirWritable()) {
4169 exportBufferAs(*doc_buffer, from_utf8(format));
4172 /* TODO/Review: Is it a problem to also export the children?
4173 See the update_unincluded flag */
4174 d.asyncBufferProcessing(format,
4177 &GuiViewPrivate::exportAndDestroy,
4179 nullptr, cmd.allowAsync());
4180 // TODO Inform user about success
4184 case LFUN_BUFFER_EXPORT_AS: {
4185 LASSERT(doc_buffer, break);
4186 docstring f = cmd.argument();
4188 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4189 exportBufferAs(*doc_buffer, f);
4193 case LFUN_BUFFER_UPDATE: {
4194 d.asyncBufferProcessing(argument,
4197 &GuiViewPrivate::compileAndDestroy,
4199 nullptr, cmd.allowAsync(), true);
4202 case LFUN_BUFFER_VIEW: {
4203 d.asyncBufferProcessing(argument,
4205 _("Previewing ..."),
4206 &GuiViewPrivate::previewAndDestroy,
4208 &Buffer::preview, cmd.allowAsync());
4211 case LFUN_MASTER_BUFFER_UPDATE: {
4212 d.asyncBufferProcessing(argument,
4213 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4215 &GuiViewPrivate::compileAndDestroy,
4217 nullptr, cmd.allowAsync(), true);
4220 case LFUN_MASTER_BUFFER_VIEW: {
4221 d.asyncBufferProcessing(argument,
4222 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4224 &GuiViewPrivate::previewAndDestroy,
4225 nullptr, &Buffer::preview, cmd.allowAsync());
4228 case LFUN_EXPORT_CANCEL: {
4229 Systemcall::killscript();
4232 case LFUN_BUFFER_SWITCH: {
4233 string const file_name = to_utf8(cmd.argument());
4234 if (!FileName::isAbsolute(file_name)) {
4236 dr.setMessage(_("Absolute filename expected."));
4240 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4243 dr.setMessage(_("Document not loaded"));
4247 // Do we open or switch to the buffer in this view ?
4248 if (workArea(*buffer)
4249 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4254 // Look for the buffer in other views
4255 QList<int> const ids = guiApp->viewIds();
4257 for (; i != ids.size(); ++i) {
4258 GuiView & gv = guiApp->view(ids[i]);
4259 if (gv.workArea(*buffer)) {
4261 gv.activateWindow();
4263 gv.setBuffer(buffer);
4268 // If necessary, open a new window as a last resort
4269 if (i == ids.size()) {
4270 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4276 case LFUN_BUFFER_NEXT:
4277 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4280 case LFUN_BUFFER_MOVE_NEXT:
4281 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4284 case LFUN_BUFFER_PREVIOUS:
4285 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4288 case LFUN_BUFFER_MOVE_PREVIOUS:
4289 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4292 case LFUN_BUFFER_CHKTEX:
4293 LASSERT(doc_buffer, break);
4294 doc_buffer->runChktex();
4297 case LFUN_COMMAND_EXECUTE: {
4298 command_execute_ = true;
4299 minibuffer_focus_ = true;
4302 case LFUN_DROP_LAYOUTS_CHOICE:
4303 d.layout_->showPopup();
4306 case LFUN_MENU_OPEN:
4307 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4308 menu->exec(QCursor::pos());
4311 case LFUN_FILE_INSERT: {
4312 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4313 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4314 dr.forceBufferUpdate();
4315 dr.screenUpdate(Update::Force);
4320 case LFUN_FILE_INSERT_PLAINTEXT:
4321 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4322 string const fname = to_utf8(cmd.argument());
4323 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4324 dr.setMessage(_("Absolute filename expected."));
4328 FileName filename(fname);
4329 if (fname.empty()) {
4330 FileDialog dlg(qt_("Select file to insert"));
4332 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4333 QStringList(qt_("All Files (*)")));
4335 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4336 dr.setMessage(_("Canceled."));
4340 filename.set(fromqstr(result.second));
4344 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4345 bv->dispatch(new_cmd, dr);
4350 case LFUN_BUFFER_RELOAD: {
4351 LASSERT(doc_buffer, break);
4354 bool drop = (cmd.argument() == "dump");
4357 if (!drop && !doc_buffer->isClean()) {
4358 docstring const file =
4359 makeDisplayPath(doc_buffer->absFileName(), 20);
4360 if (doc_buffer->notifiesExternalModification()) {
4361 docstring text = _("The current version will be lost. "
4362 "Are you sure you want to load the version on disk "
4363 "of the document %1$s?");
4364 ret = Alert::prompt(_("Reload saved document?"),
4365 bformat(text, file), 1, 1,
4366 _("&Reload"), _("&Cancel"));
4368 docstring text = _("Any changes will be lost. "
4369 "Are you sure you want to revert to the saved version "
4370 "of the document %1$s?");
4371 ret = Alert::prompt(_("Revert to saved document?"),
4372 bformat(text, file), 1, 1,
4373 _("&Revert"), _("&Cancel"));
4378 doc_buffer->markClean();
4379 reloadBuffer(*doc_buffer);
4380 dr.forceBufferUpdate();
4385 case LFUN_BUFFER_RESET_EXPORT:
4386 LASSERT(doc_buffer, break);
4387 doc_buffer->requireFreshStart(true);
4388 dr.setMessage(_("Buffer export reset."));
4391 case LFUN_BUFFER_WRITE:
4392 LASSERT(doc_buffer, break);
4393 saveBuffer(*doc_buffer);
4396 case LFUN_BUFFER_WRITE_AS:
4397 LASSERT(doc_buffer, break);
4398 renameBuffer(*doc_buffer, cmd.argument());
4401 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4402 LASSERT(doc_buffer, break);
4403 renameBuffer(*doc_buffer, cmd.argument(),
4404 LV_WRITE_AS_TEMPLATE);
4407 case LFUN_BUFFER_WRITE_ALL: {
4408 Buffer * first = theBufferList().first();
4411 message(_("Saving all documents..."));
4412 // We cannot use a for loop as the buffer list cycles.
4415 if (!b->isClean()) {
4417 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4419 b = theBufferList().next(b);
4420 } while (b != first);
4421 dr.setMessage(_("All documents saved."));
4425 case LFUN_MASTER_BUFFER_FORALL: {
4429 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4430 funcToRun.allowAsync(false);
4432 for (Buffer const * buf : doc_buffer->allRelatives()) {
4433 // Switch to other buffer view and resend cmd
4434 lyx::dispatch(FuncRequest(
4435 LFUN_BUFFER_SWITCH, buf->absFileName()));
4436 lyx::dispatch(funcToRun);
4439 lyx::dispatch(FuncRequest(
4440 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4444 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4445 LASSERT(doc_buffer, break);
4446 doc_buffer->clearExternalModification();
4449 case LFUN_BUFFER_CLOSE:
4453 case LFUN_BUFFER_CLOSE_ALL:
4457 case LFUN_DEVEL_MODE_TOGGLE:
4458 devel_mode_ = !devel_mode_;
4460 dr.setMessage(_("Developer mode is now enabled."));
4462 dr.setMessage(_("Developer mode is now disabled."));
4465 case LFUN_TOOLBAR_SET: {
4466 string const name = cmd.getArg(0);
4467 string const state = cmd.getArg(1);
4468 if (GuiToolbar * t = toolbar(name))
4473 case LFUN_TOOLBAR_TOGGLE: {
4474 string const name = cmd.getArg(0);
4475 if (GuiToolbar * t = toolbar(name))
4480 case LFUN_TOOLBAR_MOVABLE: {
4481 string const name = cmd.getArg(0);
4483 // toggle (all) toolbars movablility
4484 toolbarsMovable_ = !toolbarsMovable_;
4485 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4486 GuiToolbar * tb = toolbar(ti.name);
4487 if (tb && tb->isMovable() != toolbarsMovable_)
4488 // toggle toolbar movablity if it does not fit lock
4489 // (all) toolbars positions state silent = true, since
4490 // status bar notifications are slow
4493 if (toolbarsMovable_)
4494 dr.setMessage(_("Toolbars unlocked."));
4496 dr.setMessage(_("Toolbars locked."));
4497 } else if (GuiToolbar * t = toolbar(name)) {
4498 // toggle current toolbar movablity
4500 // update lock (all) toolbars positions
4501 updateLockToolbars();
4506 case LFUN_ICON_SIZE: {
4507 QSize size = d.iconSize(cmd.argument());
4509 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4510 size.width(), size.height()));
4514 case LFUN_DIALOG_UPDATE: {
4515 string const name = to_utf8(cmd.argument());
4516 if (name == "prefs" || name == "document")
4517 updateDialog(name, string());
4518 else if (name == "paragraph")
4519 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4520 else if (currentBufferView()) {
4521 Inset * inset = currentBufferView()->editedInset(name);
4522 // Can only update a dialog connected to an existing inset
4524 // FIXME: get rid of this indirection; GuiView ask the inset
4525 // if he is kind enough to update itself...
4526 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4527 //FIXME: pass DispatchResult here?
4528 inset->dispatch(currentBufferView()->cursor(), fr);
4534 case LFUN_DIALOG_TOGGLE: {
4535 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4536 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4537 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4541 case LFUN_DIALOG_DISCONNECT_INSET:
4542 disconnectDialog(to_utf8(cmd.argument()));
4545 case LFUN_DIALOG_HIDE: {
4546 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4550 case LFUN_DIALOG_SHOW: {
4551 string const name = cmd.getArg(0);
4552 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4554 if (name == "latexlog") {
4555 // getStatus checks that
4556 LASSERT(doc_buffer, break);
4557 Buffer::LogType type;
4558 string const logfile = doc_buffer->logName(&type);
4560 case Buffer::latexlog:
4563 case Buffer::buildlog:
4564 sdata = "literate ";
4567 sdata += Lexer::quoteString(logfile);
4568 showDialog("log", sdata);
4569 } else if (name == "vclog") {
4570 // getStatus checks that
4571 LASSERT(doc_buffer, break);
4572 string const sdata2 = "vc " +
4573 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4574 showDialog("log", sdata2);
4575 } else if (name == "symbols") {
4576 sdata = bv->cursor().getEncoding()->name();
4578 showDialog("symbols", sdata);
4579 } else if (name == "findreplace") {
4580 sdata = to_utf8(bv->cursor().selectionAsString(false));
4581 showDialog(name, sdata);
4583 } else if (name == "prefs" && isFullScreen()) {
4584 lfunUiToggle("fullscreen");
4585 showDialog("prefs", sdata);
4587 showDialog(name, sdata);
4592 dr.setMessage(cmd.argument());
4595 case LFUN_UI_TOGGLE: {
4596 string arg = cmd.getArg(0);
4597 if (!lfunUiToggle(arg)) {
4598 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4599 dr.setMessage(bformat(msg, from_utf8(arg)));
4601 // Make sure the keyboard focus stays in the work area.
4606 case LFUN_VIEW_SPLIT: {
4607 LASSERT(doc_buffer, break);
4608 string const orientation = cmd.getArg(0);
4609 d.splitter_->setOrientation(orientation == "vertical"
4610 ? Qt::Vertical : Qt::Horizontal);
4611 TabWorkArea * twa = addTabWorkArea();
4612 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4613 setCurrentWorkArea(wa);
4616 case LFUN_TAB_GROUP_CLOSE:
4617 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4618 closeTabWorkArea(twa);
4619 d.current_work_area_ = nullptr;
4620 twa = d.currentTabWorkArea();
4621 // Switch to the next GuiWorkArea in the found TabWorkArea.
4623 // Make sure the work area is up to date.
4624 setCurrentWorkArea(twa->currentWorkArea());
4626 setCurrentWorkArea(nullptr);
4631 case LFUN_VIEW_CLOSE:
4632 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4633 closeWorkArea(twa->currentWorkArea());
4634 d.current_work_area_ = nullptr;
4635 twa = d.currentTabWorkArea();
4636 // Switch to the next GuiWorkArea in the found TabWorkArea.
4638 // Make sure the work area is up to date.
4639 setCurrentWorkArea(twa->currentWorkArea());
4641 setCurrentWorkArea(nullptr);
4646 case LFUN_COMPLETION_INLINE:
4647 if (d.current_work_area_)
4648 d.current_work_area_->completer().showInline();
4651 case LFUN_COMPLETION_POPUP:
4652 if (d.current_work_area_)
4653 d.current_work_area_->completer().showPopup();
4658 if (d.current_work_area_)
4659 d.current_work_area_->completer().tab();
4662 case LFUN_COMPLETION_CANCEL:
4663 if (d.current_work_area_) {
4664 if (d.current_work_area_->completer().popupVisible())
4665 d.current_work_area_->completer().hidePopup();
4667 d.current_work_area_->completer().hideInline();
4671 case LFUN_COMPLETION_ACCEPT:
4672 if (d.current_work_area_)
4673 d.current_work_area_->completer().activate();
4676 case LFUN_BUFFER_ZOOM_IN:
4677 case LFUN_BUFFER_ZOOM_OUT:
4678 case LFUN_BUFFER_ZOOM: {
4679 if (cmd.argument().empty()) {
4680 if (cmd.action() == LFUN_BUFFER_ZOOM)
4682 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4687 if (cmd.action() == LFUN_BUFFER_ZOOM)
4688 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4689 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4690 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4692 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4695 // Actual zoom value: default zoom + fractional extra value
4696 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4697 if (zoom < static_cast<int>(zoom_min_))
4700 setCurrentZoom(zoom);
4702 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4703 lyxrc.currentZoom, lyxrc.defaultZoom));
4705 guiApp->fontLoader().update();
4706 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4710 case LFUN_VC_REGISTER:
4711 case LFUN_VC_RENAME:
4713 case LFUN_VC_CHECK_IN:
4714 case LFUN_VC_CHECK_OUT:
4715 case LFUN_VC_REPO_UPDATE:
4716 case LFUN_VC_LOCKING_TOGGLE:
4717 case LFUN_VC_REVERT:
4718 case LFUN_VC_UNDO_LAST:
4719 case LFUN_VC_COMMAND:
4720 case LFUN_VC_COMPARE:
4721 dispatchVC(cmd, dr);
4724 case LFUN_SERVER_GOTO_FILE_ROW:
4725 if(goToFileRow(to_utf8(cmd.argument())))
4726 dr.screenUpdate(Update::Force | Update::FitCursor);
4729 case LFUN_LYX_ACTIVATE:
4733 case LFUN_WINDOW_RAISE:
4739 case LFUN_FORWARD_SEARCH: {
4740 // it seems safe to assume we have a document buffer, since
4741 // getStatus wants one.
4742 LASSERT(doc_buffer, break);
4743 Buffer const * doc_master = doc_buffer->masterBuffer();
4744 FileName const path(doc_master->temppath());
4745 string const texname = doc_master->isChild(doc_buffer)
4746 ? DocFileName(changeExtension(
4747 doc_buffer->absFileName(),
4748 "tex")).mangledFileName()
4749 : doc_buffer->latexName();
4750 string const fulltexname =
4751 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4752 string const mastername =
4753 removeExtension(doc_master->latexName());
4754 FileName const dviname(addName(path.absFileName(),
4755 addExtension(mastername, "dvi")));
4756 FileName const pdfname(addName(path.absFileName(),
4757 addExtension(mastername, "pdf")));
4758 bool const have_dvi = dviname.exists();
4759 bool const have_pdf = pdfname.exists();
4760 if (!have_dvi && !have_pdf) {
4761 dr.setMessage(_("Please, preview the document first."));
4764 string outname = dviname.onlyFileName();
4765 string command = lyxrc.forward_search_dvi;
4766 if (!have_dvi || (have_pdf &&
4767 pdfname.lastModified() > dviname.lastModified())) {
4768 outname = pdfname.onlyFileName();
4769 command = lyxrc.forward_search_pdf;
4772 DocIterator cur = bv->cursor();
4773 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4774 LYXERR(Debug::ACTION, "Forward search: row:" << row
4776 if (row == -1 || command.empty()) {
4777 dr.setMessage(_("Couldn't proceed."));
4780 string texrow = convert<string>(row);
4782 command = subst(command, "$$n", texrow);
4783 command = subst(command, "$$f", fulltexname);
4784 command = subst(command, "$$t", texname);
4785 command = subst(command, "$$o", outname);
4787 volatile PathChanger p(path);
4789 one.startscript(Systemcall::DontWait, command);
4793 case LFUN_SPELLING_CONTINUOUSLY:
4794 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4795 dr.screenUpdate(Update::Force);
4798 case LFUN_CITATION_OPEN: {
4800 if (theFormats().getFormat("pdf"))
4801 pdfv = theFormats().getFormat("pdf")->viewer();
4802 if (theFormats().getFormat("ps"))
4803 psv = theFormats().getFormat("ps")->viewer();
4804 frontend::showTarget(argument, pdfv, psv);
4809 // The LFUN must be for one of BufferView, Buffer or Cursor;
4811 dispatchToBufferView(cmd, dr);
4815 // Need to update bv because many LFUNs here might have destroyed it
4816 bv = currentBufferView();
4818 // Clear non-empty selections
4819 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4821 Cursor & cur = bv->cursor();
4822 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4823 cur.clearSelection();
4829 bool GuiView::lfunUiToggle(string const & ui_component)
4831 if (ui_component == "scrollbar") {
4832 // hide() is of no help
4833 if (d.current_work_area_->verticalScrollBarPolicy() ==
4834 Qt::ScrollBarAlwaysOff)
4836 d.current_work_area_->setVerticalScrollBarPolicy(
4837 Qt::ScrollBarAsNeeded);
4839 d.current_work_area_->setVerticalScrollBarPolicy(
4840 Qt::ScrollBarAlwaysOff);
4841 } else if (ui_component == "statusbar") {
4842 statusBar()->setVisible(!statusBar()->isVisible());
4843 } else if (ui_component == "menubar") {
4844 menuBar()->setVisible(!menuBar()->isVisible());
4846 if (ui_component == "frame") {
4847 int const l = contentsMargins().left();
4849 //are the frames in default state?
4850 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4852 #if QT_VERSION > 0x050903
4853 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4855 setContentsMargins(-2, -2, -2, -2);
4857 #if QT_VERSION > 0x050903
4858 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4860 setContentsMargins(0, 0, 0, 0);
4863 if (ui_component == "fullscreen") {
4871 void GuiView::toggleFullScreen()
4873 setWindowState(windowState() ^ Qt::WindowFullScreen);
4877 Buffer const * GuiView::updateInset(Inset const * inset)
4882 Buffer const * inset_buffer = &(inset->buffer());
4884 for (int i = 0; i != d.splitter_->count(); ++i) {
4885 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4888 Buffer const * buffer = &(wa->bufferView().buffer());
4889 if (inset_buffer == buffer)
4890 wa->scheduleRedraw(true);
4892 return inset_buffer;
4896 void GuiView::restartCaret()
4898 /* When we move around, or type, it's nice to be able to see
4899 * the caret immediately after the keypress.
4901 if (d.current_work_area_)
4902 d.current_work_area_->startBlinkingCaret();
4904 // Take this occasion to update the other GUI elements.
4910 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4912 if (d.current_work_area_)
4913 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4918 // This list should be kept in sync with the list of insets in
4919 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4920 // dialog should have the same name as the inset.
4921 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4922 // docs in LyXAction.cpp.
4924 char const * const dialognames[] = {
4926 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4927 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4928 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4929 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4930 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4931 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4932 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4933 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4935 char const * const * const end_dialognames =
4936 dialognames + (sizeof(dialognames) / sizeof(char *));
4940 cmpCStr(char const * name) : name_(name) {}
4941 bool operator()(char const * other) {
4942 return strcmp(other, name_) == 0;
4949 bool isValidName(string const & name)
4951 return find_if(dialognames, end_dialognames,
4952 cmpCStr(name.c_str())) != end_dialognames;
4958 void GuiView::resetDialogs()
4960 // Make sure that no LFUN uses any GuiView.
4961 guiApp->setCurrentView(nullptr);
4965 constructToolbars();
4966 guiApp->menus().fillMenuBar(menuBar(), this, false);
4967 d.layout_->updateContents(true);
4968 // Now update controls with current buffer.
4969 guiApp->setCurrentView(this);
4975 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4977 for (QObject * child: widget->children()) {
4978 if (child->inherits("QGroupBox")) {
4979 QGroupBox * box = (QGroupBox*) child;
4982 flatGroupBoxes(child, flag);
4988 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4990 if (!isValidName(name))
4993 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4995 if (it != d.dialogs_.end()) {
4997 it->second->hideView();
4998 return it->second.get();
5001 Dialog * dialog = build(name);
5002 d.dialogs_[name].reset(dialog);
5003 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5004 // Force a uniform style for group boxes
5005 // On Mac non-flat works better, on Linux flat is standard
5006 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5008 if (lyxrc.allow_geometry_session)
5009 dialog->restoreSession();
5016 void GuiView::showDialog(string const & name, string const & sdata,
5019 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5023 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5029 const string name = fromqstr(qname);
5030 const string sdata = fromqstr(qdata);
5034 Dialog * dialog = findOrBuild(name, false);
5036 bool const visible = dialog->isVisibleView();
5037 dialog->showData(sdata);
5038 if (currentBufferView())
5039 currentBufferView()->editInset(name, inset);
5040 // We only set the focus to the new dialog if it was not yet
5041 // visible in order not to change the existing previous behaviour
5043 // activateWindow is needed for floating dockviews
5044 dialog->asQWidget()->raise();
5045 dialog->asQWidget()->activateWindow();
5046 if (dialog->wantInitialFocus())
5047 dialog->asQWidget()->setFocus();
5051 catch (ExceptionMessage const &) {
5059 bool GuiView::isDialogVisible(string const & name) const
5061 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5062 if (it == d.dialogs_.end())
5064 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5068 void GuiView::hideDialog(string const & name, Inset * inset)
5070 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5071 if (it == d.dialogs_.end())
5075 if (!currentBufferView())
5077 if (inset != currentBufferView()->editedInset(name))
5081 Dialog * const dialog = it->second.get();
5082 if (dialog->isVisibleView())
5084 if (currentBufferView())
5085 currentBufferView()->editInset(name, nullptr);
5089 void GuiView::disconnectDialog(string const & name)
5091 if (!isValidName(name))
5093 if (currentBufferView())
5094 currentBufferView()->editInset(name, nullptr);
5098 void GuiView::hideAll() const
5100 for(auto const & dlg_p : d.dialogs_)
5101 dlg_p.second->hideView();
5105 void GuiView::updateDialogs()
5107 for(auto const & dlg_p : d.dialogs_) {
5108 Dialog * dialog = dlg_p.second.get();
5110 if (dialog->needBufferOpen() && !documentBufferView())
5111 hideDialog(fromqstr(dialog->name()), nullptr);
5112 else if (dialog->isVisibleView())
5113 dialog->checkStatus();
5121 Dialog * GuiView::build(string const & name)
5123 return createDialog(*this, name);
5127 SEMenu::SEMenu(QWidget * parent)
5129 QAction * action = addAction(qt_("Disable Shell Escape"));
5130 connect(action, SIGNAL(triggered()),
5131 parent, SLOT(disableShellEscape()));
5134 } // namespace frontend
5137 #include "moc_GuiView.cpp"