3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
103 #include <QShowEvent>
106 #include <QStackedWidget>
107 #include <QStatusBar>
108 #include <QSvgRenderer>
109 #include <QtConcurrentRun>
112 #include <QWindowStateChangeEvent>
115 // sync with GuiAlert.cpp
116 #define EXPORT_in_THREAD 1
119 #include "support/bind.h"
123 #ifdef HAVE_SYS_TIME_H
124 # include <sys/time.h>
132 using namespace lyx::support;
136 using support::addExtension;
137 using support::changeExtension;
138 using support::removeExtension;
144 class BackgroundWidget : public QWidget
147 BackgroundWidget(int width, int height)
148 : width_(width), height_(height)
150 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
151 if (!lyxrc.show_banner)
153 /// The text to be written on top of the pixmap
154 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
155 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
156 /// The text to be written on top of the pixmap
157 QString const text = lyx_version ?
158 qt_("version ") + lyx_version : qt_("unknown version");
159 #if QT_VERSION >= 0x050000
160 QString imagedir = "images/";
161 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
162 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
163 if (svgRenderer.isValid()) {
164 splash_ = QPixmap(splashSize());
165 QPainter painter(&splash_);
166 svgRenderer.render(&painter);
167 splash_.setDevicePixelRatio(pixelRatio());
169 splash_ = getPixmap("images/", "banner", "png");
172 splash_ = getPixmap("images/", "banner", "svgz,png");
175 QPainter pain(&splash_);
176 pain.setPen(QColor(0, 0, 0));
177 qreal const fsize = fontSize();
180 qreal locscale = htextsize.toFloat(&ok);
183 QPointF const position = textPosition(false);
184 QPointF const hposition = textPosition(true);
185 QRectF const hrect(hposition, splashSize());
187 "widget pixel ratio: " << pixelRatio() <<
188 " splash pixel ratio: " << splashPixelRatio() <<
189 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
191 // The font used to display the version info
192 font.setStyleHint(QFont::SansSerif);
193 font.setWeight(QFont::Bold);
194 font.setPointSizeF(fsize);
196 pain.drawText(position, text);
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Normal);
200 font.setPointSizeF(hfsize);
201 // Check how long the logo gets with the current font
202 // and adapt if the font is running wider than what
204 GuiFontMetrics fm(font);
205 // Split the title into lines to measure the longest line
206 // in the current l7n.
207 QStringList titlesegs = htext.split('\n');
209 int hline = fm.maxHeight();
210 QStringList::const_iterator sit;
211 for (QString const & seg : titlesegs) {
212 if (fm.width(seg) > wline)
213 wline = fm.width(seg);
215 // The longest line in the reference font (for English)
216 // is 180. Calculate scale factor from that.
217 double const wscale = wline > 0 ? (180.0 / wline) : 1;
218 // Now do the same for the height (necessary for condensed fonts)
219 double const hscale = (34.0 / hline);
220 // take the lower of the two scale factors.
221 double const scale = min(wscale, hscale);
222 // Now rescale. Also consider l7n's offset factor.
223 font.setPointSizeF(hfsize * scale * locscale);
226 pain.drawText(hrect, Qt::AlignLeft, htext);
227 setFocusPolicy(Qt::StrongFocus);
230 void paintEvent(QPaintEvent *) override
232 int const w = width_;
233 int const h = height_;
234 int const x = (width() - w) / 2;
235 int const y = (height() - h) / 2;
237 "widget pixel ratio: " << pixelRatio() <<
238 " splash pixel ratio: " << splashPixelRatio() <<
239 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
241 pain.drawPixmap(x, y, w, h, splash_);
244 void keyPressEvent(QKeyEvent * ev) override
247 setKeySymbol(&sym, ev);
249 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
261 /// Current ratio between physical pixels and device-independent pixels
262 double pixelRatio() const {
263 #if QT_VERSION >= 0x050000
264 return qt_scale_factor * devicePixelRatio();
270 qreal fontSize() const {
271 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
274 QPointF textPosition(bool const heading) const {
275 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
276 : QPointF(width_/2 - 18, height_/2 + 45);
279 QSize splashSize() const {
281 static_cast<unsigned int>(width_ * pixelRatio()),
282 static_cast<unsigned int>(height_ * pixelRatio()));
285 /// Ratio between physical pixels and device-independent pixels of splash image
286 double splashPixelRatio() const {
287 #if QT_VERSION >= 0x050000
288 return splash_.devicePixelRatio();
296 /// Toolbar store providing access to individual toolbars by name.
297 typedef map<string, GuiToolbar *> ToolbarMap;
299 typedef shared_ptr<Dialog> DialogPtr;
304 class GuiView::GuiViewPrivate
307 GuiViewPrivate(GuiViewPrivate const &);
308 void operator=(GuiViewPrivate const &);
310 GuiViewPrivate(GuiView * gv)
311 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
312 layout_(nullptr), autosave_timeout_(5000),
315 // hardcode here the platform specific icon size
316 smallIconSize = 16; // scaling problems
317 normalIconSize = 20; // ok, default if iconsize.png is missing
318 bigIconSize = 26; // better for some math icons
319 hugeIconSize = 32; // better for hires displays
322 // if it exists, use width of iconsize.png as normal size
323 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
324 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
326 QImage image(toqstr(fn.absFileName()));
327 if (image.width() < int(smallIconSize))
328 normalIconSize = smallIconSize;
329 else if (image.width() > int(giantIconSize))
330 normalIconSize = giantIconSize;
332 normalIconSize = image.width();
335 splitter_ = new QSplitter;
336 bg_widget_ = new BackgroundWidget(400, 250);
337 stack_widget_ = new QStackedWidget;
338 stack_widget_->addWidget(bg_widget_);
339 stack_widget_->addWidget(splitter_);
342 // TODO cleanup, remove the singleton, handle multiple Windows?
343 progress_ = ProgressInterface::instance();
344 if (!dynamic_cast<GuiProgress*>(progress_)) {
345 progress_ = new GuiProgress; // TODO who deletes it
346 ProgressInterface::setInstance(progress_);
349 dynamic_cast<GuiProgress*>(progress_),
350 SIGNAL(updateStatusBarMessage(QString const&)),
351 gv, SLOT(updateStatusBarMessage(QString const&)));
353 dynamic_cast<GuiProgress*>(progress_),
354 SIGNAL(clearMessageText()),
355 gv, SLOT(clearMessageText()));
362 delete stack_widget_;
367 stack_widget_->setCurrentWidget(bg_widget_);
368 bg_widget_->setUpdatesEnabled(true);
369 bg_widget_->setFocus();
372 int tabWorkAreaCount()
374 return splitter_->count();
377 TabWorkArea * tabWorkArea(int i)
379 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
382 TabWorkArea * currentTabWorkArea()
384 int areas = tabWorkAreaCount();
386 // The first TabWorkArea is always the first one, if any.
387 return tabWorkArea(0);
389 for (int i = 0; i != areas; ++i) {
390 TabWorkArea * twa = tabWorkArea(i);
391 if (current_main_work_area_ == twa->currentWorkArea())
395 // None has the focus so we just take the first one.
396 return tabWorkArea(0);
399 int countWorkAreasOf(Buffer & buf)
401 int areas = tabWorkAreaCount();
403 for (int i = 0; i != areas; ++i) {
404 TabWorkArea * twa = tabWorkArea(i);
405 if (twa->workArea(buf))
411 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
413 if (processing_thread_watcher_.isRunning()) {
414 // we prefer to cancel this preview in order to keep a snappy
418 processing_thread_watcher_.setFuture(f);
421 QSize iconSize(docstring const & icon_size)
424 if (icon_size == "small")
425 size = smallIconSize;
426 else if (icon_size == "normal")
427 size = normalIconSize;
428 else if (icon_size == "big")
430 else if (icon_size == "huge")
432 else if (icon_size == "giant")
433 size = giantIconSize;
435 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
437 if (size < smallIconSize)
438 size = smallIconSize;
440 return QSize(size, size);
443 QSize iconSize(QString const & icon_size)
445 return iconSize(qstring_to_ucs4(icon_size));
448 string & iconSize(QSize const & qsize)
450 LATTEST(qsize.width() == qsize.height());
452 static string icon_size;
454 unsigned int size = qsize.width();
456 if (size < smallIconSize)
457 size = smallIconSize;
459 if (size == smallIconSize)
461 else if (size == normalIconSize)
462 icon_size = "normal";
463 else if (size == bigIconSize)
465 else if (size == hugeIconSize)
467 else if (size == giantIconSize)
470 icon_size = convert<string>(size);
475 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
476 Buffer * buffer, string const & format);
477 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
484 static Buffer::ExportStatus runAndDestroy(const T& func,
485 Buffer const * orig, Buffer * buffer, string const & format);
487 // TODO syncFunc/previewFunc: use bind
488 bool asyncBufferProcessing(string const & argument,
489 Buffer const * used_buffer,
490 docstring const & msg,
491 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
492 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
493 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
494 bool allow_async, bool use_tmpdir = false);
496 QVector<GuiWorkArea*> guiWorkAreas();
500 GuiWorkArea * current_work_area_;
501 GuiWorkArea * current_main_work_area_;
502 QSplitter * splitter_;
503 QStackedWidget * stack_widget_;
504 BackgroundWidget * bg_widget_;
506 ToolbarMap toolbars_;
507 ProgressInterface* progress_;
508 /// The main layout box.
510 * \warning Don't Delete! The layout box is actually owned by
511 * whichever toolbar contains it. All the GuiView class needs is a
512 * means of accessing it.
514 * FIXME: replace that with a proper model so that we are not limited
515 * to only one dialog.
520 map<string, DialogPtr> dialogs_;
523 QTimer statusbar_timer_;
524 /// auto-saving of buffers
525 Timeout autosave_timeout_;
528 TocModels toc_models_;
531 QFutureWatcher<docstring> autosave_watcher_;
532 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
534 string last_export_format;
535 string processing_format;
537 static QSet<Buffer const *> busyBuffers;
539 unsigned int smallIconSize;
540 unsigned int normalIconSize;
541 unsigned int bigIconSize;
542 unsigned int hugeIconSize;
543 unsigned int giantIconSize;
545 /// flag against a race condition due to multiclicks, see bug #1119
549 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
552 GuiView::GuiView(int id)
553 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
554 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
557 connect(this, SIGNAL(bufferViewChanged()),
558 this, SLOT(onBufferViewChanged()));
560 // GuiToolbars *must* be initialised before the menu bar.
561 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
564 // set ourself as the current view. This is needed for the menu bar
565 // filling, at least for the static special menu item on Mac. Otherwise
566 // they are greyed out.
567 guiApp->setCurrentView(this);
569 // Fill up the menu bar.
570 guiApp->menus().fillMenuBar(menuBar(), this, true);
572 setCentralWidget(d.stack_widget_);
574 // Start autosave timer
575 if (lyxrc.autosave) {
576 // The connection is closed when this is destroyed.
577 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
578 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
579 d.autosave_timeout_.start();
581 connect(&d.statusbar_timer_, SIGNAL(timeout()),
582 this, SLOT(clearMessage()));
584 // We don't want to keep the window in memory if it is closed.
585 setAttribute(Qt::WA_DeleteOnClose, true);
587 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
588 // QIcon::fromTheme was introduced in Qt 4.6
589 #if (QT_VERSION >= 0x040600)
590 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
591 // since the icon is provided in the application bundle. We use a themed
592 // version when available and use the bundled one as fallback.
593 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
595 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
601 // use tabbed dock area for multiple docks
602 // (such as "source" and "messages")
603 setDockOptions(QMainWindow::ForceTabbedDocks);
606 // use document mode tabs on docks
607 setDocumentMode(true);
611 setAcceptDrops(true);
613 // add busy indicator to statusbar
614 search_mode mode = theGuiApp()->imageSearchMode();
615 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
616 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
617 statusBar()->addPermanentWidget(busySVG);
618 // make busy indicator square with 5px margins
619 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
622 connect(&d.processing_thread_watcher_, SIGNAL(started()),
623 busySVG, SLOT(show()));
624 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
625 busySVG, SLOT(hide()));
626 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
628 QFontMetrics const fm(statusBar()->fontMetrics());
630 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
631 // Small size slider for macOS to prevent the status bar from enlarging
632 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
633 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
634 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
636 zoom_slider_->setFixedWidth(fm.width('x') * 15);
638 // Make the defaultZoom center
639 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
640 // Initialize proper zoom value
642 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
643 // Actual zoom value: default zoom + fractional offset
644 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
645 if (zoom < static_cast<int>(zoom_min_))
647 zoom_slider_->setValue(zoom);
648 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
650 // Buttons to change zoom stepwise
651 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
652 QSize s(fm.horizontalAdvance('+'), fm.height());
654 QSize s(fm.width('+'), fm.height());
656 zoom_in_ = new GuiClickableLabel(statusBar());
657 zoom_in_->setText("+");
658 zoom_in_->setFixedSize(s);
659 zoom_in_->setAlignment(Qt::AlignCenter);
660 zoom_out_ = new GuiClickableLabel(statusBar());
661 zoom_out_->setText(QString(QChar(0x2212)));
662 zoom_out_->setFixedSize(s);
663 zoom_out_->setAlignment(Qt::AlignCenter);
665 statusBar()->addPermanentWidget(zoom_out_);
666 zoom_out_->setEnabled(currentBufferView());
667 statusBar()->addPermanentWidget(zoom_slider_);
668 zoom_slider_->setEnabled(currentBufferView());
669 zoom_in_->setEnabled(currentBufferView());
670 statusBar()->addPermanentWidget(zoom_in_);
672 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
673 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
674 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
675 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
676 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
678 // QPalette palette = statusBar()->palette();
680 zoom_value_ = new QLabel(statusBar());
681 // zoom_value_->setPalette(palette);
682 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
683 zoom_value_->setFixedHeight(fm.height());
684 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
685 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
687 zoom_value_->setMinimumWidth(fm.width("444\%"));
689 zoom_value_->setAlignment(Qt::AlignCenter);
690 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
691 statusBar()->addPermanentWidget(zoom_value_);
692 zoom_value_->setEnabled(currentBufferView());
694 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
695 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
696 this, SLOT(showZoomContextMenu()));
698 int const iconheight = max(int(d.normalIconSize), fm.height());
699 QSize const iconsize(iconheight, iconheight);
701 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
702 shell_escape_ = new QLabel(statusBar());
703 shell_escape_->setPixmap(shellescape);
704 shell_escape_->setAlignment(Qt::AlignCenter);
705 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
706 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
707 "external commands for this document. "
708 "Right click to change."));
709 SEMenu * menu = new SEMenu(this);
710 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
711 menu, SLOT(showMenu(QPoint)));
712 shell_escape_->hide();
713 statusBar()->addPermanentWidget(shell_escape_);
715 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
716 read_only_ = new QLabel(statusBar());
717 read_only_->setPixmap(readonly);
718 read_only_->setAlignment(Qt::AlignCenter);
720 statusBar()->addPermanentWidget(read_only_);
722 version_control_ = new QLabel(statusBar());
723 version_control_->setAlignment(Qt::AlignCenter);
724 version_control_->setFrameStyle(QFrame::StyledPanel);
725 version_control_->hide();
726 statusBar()->addPermanentWidget(version_control_);
728 statusBar()->setSizeGripEnabled(true);
731 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
732 SLOT(autoSaveThreadFinished()));
734 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
735 SLOT(processingThreadStarted()));
736 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
737 SLOT(processingThreadFinished()));
739 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
740 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
742 // set custom application bars context menu, e.g. tool bar and menu bar
743 setContextMenuPolicy(Qt::CustomContextMenu);
744 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
745 SLOT(toolBarPopup(const QPoint &)));
747 // Forbid too small unresizable window because it can happen
748 // with some window manager under X11.
749 setMinimumSize(300, 200);
751 if (lyxrc.allow_geometry_session) {
752 // Now take care of session management.
757 // no session handling, default to a sane size.
758 setGeometry(50, 50, 690, 510);
761 // clear session data if any.
762 settings.remove("views");
772 void GuiView::disableShellEscape()
774 BufferView * bv = documentBufferView();
777 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
778 bv->buffer().params().shell_escape = false;
779 bv->processUpdateFlags(Update::Force);
783 void GuiView::checkCancelBackground()
785 docstring const ttl = _("Cancel Export?");
786 docstring const msg = _("Do you want to cancel the background export process?");
788 Alert::prompt(ttl, msg, 1, 1,
789 _("&Cancel export"), _("Co&ntinue"));
791 Systemcall::killscript();
795 void GuiView::zoomSliderMoved(int value)
798 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
799 scheduleRedrawWorkAreas();
800 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
804 void GuiView::zoomValueChanged(int value)
806 if (value != lyxrc.currentZoom)
807 zoomSliderMoved(value);
811 void GuiView::zoomInPressed()
814 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
815 scheduleRedrawWorkAreas();
819 void GuiView::zoomOutPressed()
822 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
823 scheduleRedrawWorkAreas();
827 void GuiView::showZoomContextMenu()
829 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
832 menu->exec(QCursor::pos());
836 void GuiView::scheduleRedrawWorkAreas()
838 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
839 TabWorkArea* ta = d.tabWorkArea(i);
840 for (int u = 0; u < ta->count(); u++) {
841 ta->workArea(u)->scheduleRedraw(true);
847 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
849 QVector<GuiWorkArea*> areas;
850 for (int i = 0; i < tabWorkAreaCount(); i++) {
851 TabWorkArea* ta = tabWorkArea(i);
852 for (int u = 0; u < ta->count(); u++) {
853 areas << ta->workArea(u);
859 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
860 string const & format)
862 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
865 case Buffer::ExportSuccess:
866 msg = bformat(_("Successful export to format: %1$s"), fmt);
868 case Buffer::ExportCancel:
869 msg = _("Document export cancelled.");
871 case Buffer::ExportError:
872 case Buffer::ExportNoPathToFormat:
873 case Buffer::ExportTexPathHasSpaces:
874 case Buffer::ExportConverterError:
875 msg = bformat(_("Error while exporting format: %1$s"), fmt);
877 case Buffer::PreviewSuccess:
878 msg = bformat(_("Successful preview of format: %1$s"), fmt);
880 case Buffer::PreviewError:
881 msg = bformat(_("Error while previewing format: %1$s"), fmt);
883 case Buffer::ExportKilled:
884 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
891 void GuiView::processingThreadStarted()
896 void GuiView::processingThreadFinished()
898 QFutureWatcher<Buffer::ExportStatus> const * watcher =
899 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
901 Buffer::ExportStatus const status = watcher->result();
902 handleExportStatus(this, status, d.processing_format);
905 BufferView const * const bv = currentBufferView();
906 if (bv && !bv->buffer().errorList("Export").empty()) {
911 bool const error = (status != Buffer::ExportSuccess &&
912 status != Buffer::PreviewSuccess &&
913 status != Buffer::ExportCancel);
915 ErrorList & el = bv->buffer().errorList(d.last_export_format);
916 // at this point, we do not know if buffer-view or
917 // master-buffer-view was called. If there was an export error,
918 // and the current buffer's error log is empty, we guess that
919 // it must be master-buffer-view that was called so we set
921 errors(d.last_export_format, el.empty());
926 void GuiView::autoSaveThreadFinished()
928 QFutureWatcher<docstring> const * watcher =
929 static_cast<QFutureWatcher<docstring> const *>(sender());
930 message(watcher->result());
935 void GuiView::saveLayout() const
938 settings.setValue("zoom_ratio", zoom_ratio_);
939 settings.setValue("devel_mode", devel_mode_);
940 settings.beginGroup("views");
941 settings.beginGroup(QString::number(id_));
942 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
943 settings.setValue("pos", pos());
944 settings.setValue("size", size());
946 settings.setValue("geometry", saveGeometry());
947 settings.setValue("layout", saveState(0));
948 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
949 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
950 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
954 void GuiView::saveUISettings() const
958 // Save the toolbar private states
959 for (auto const & tb_p : d.toolbars_)
960 tb_p.second->saveSession(settings);
961 // Now take care of all other dialogs
962 for (auto const & dlg_p : d.dialogs_)
963 dlg_p.second->saveSession(settings);
967 void GuiView::setCurrentZoom(const int v)
969 lyxrc.currentZoom = v;
970 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
971 Q_EMIT currentZoomChanged(v);
975 bool GuiView::restoreLayout()
978 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
979 // Actual zoom value: default zoom + fractional offset
980 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
981 if (zoom < static_cast<int>(zoom_min_))
983 setCurrentZoom(zoom);
984 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
985 settings.beginGroup("views");
986 settings.beginGroup(QString::number(id_));
987 QString const icon_key = "icon_size";
988 if (!settings.contains(icon_key))
991 //code below is skipped when when ~/.config/LyX is (re)created
992 setIconSize(d.iconSize(settings.value(icon_key).toString()));
994 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
996 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
997 zoom_slider_->setVisible(show_zoom_slider);
998 zoom_in_->setVisible(show_zoom_slider);
999 zoom_out_->setVisible(show_zoom_slider);
1001 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1002 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1003 QSize size = settings.value("size", QSize(690, 510)).toSize();
1007 // Work-around for bug #6034: the window ends up in an undetermined
1008 // state when trying to restore a maximized window when it is
1009 // already maximized.
1010 if (!(windowState() & Qt::WindowMaximized))
1011 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1012 setGeometry(50, 50, 690, 510);
1015 // Make sure layout is correctly oriented.
1016 setLayoutDirection(qApp->layoutDirection());
1018 // Allow the toc and view-source dock widget to be restored if needed.
1020 if ((dialog = findOrBuild("toc", true)))
1021 // see bug 5082. At least setup title and enabled state.
1022 // Visibility will be adjusted by restoreState below.
1023 dialog->prepareView();
1024 if ((dialog = findOrBuild("view-source", true)))
1025 dialog->prepareView();
1026 if ((dialog = findOrBuild("progress", true)))
1027 dialog->prepareView();
1029 if (!restoreState(settings.value("layout").toByteArray(), 0))
1032 // init the toolbars that have not been restored
1033 for (auto const & tb_p : guiApp->toolbars()) {
1034 GuiToolbar * tb = toolbar(tb_p.name);
1035 if (tb && !tb->isRestored())
1036 initToolbar(tb_p.name);
1039 // update lock (all) toolbars positions
1040 updateLockToolbars();
1047 GuiToolbar * GuiView::toolbar(string const & name)
1049 ToolbarMap::iterator it = d.toolbars_.find(name);
1050 if (it != d.toolbars_.end())
1053 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1058 void GuiView::updateLockToolbars()
1060 toolbarsMovable_ = false;
1061 for (ToolbarInfo const & info : guiApp->toolbars()) {
1062 GuiToolbar * tb = toolbar(info.name);
1063 if (tb && tb->isMovable())
1064 toolbarsMovable_ = true;
1066 #if QT_VERSION >= 0x050200
1067 // set unified mac toolbars only when not movable as recommended:
1068 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1069 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1074 void GuiView::constructToolbars()
1076 for (auto const & tb_p : d.toolbars_)
1078 d.toolbars_.clear();
1080 // I don't like doing this here, but the standard toolbar
1081 // destroys this object when it's destroyed itself (vfr)
1082 d.layout_ = new LayoutBox(*this);
1083 d.stack_widget_->addWidget(d.layout_);
1084 d.layout_->move(0,0);
1086 // extracts the toolbars from the backend
1087 for (ToolbarInfo const & inf : guiApp->toolbars())
1088 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1090 DynamicMenuButton::resetIconCache();
1094 void GuiView::initToolbars()
1096 // extracts the toolbars from the backend
1097 for (ToolbarInfo const & inf : guiApp->toolbars())
1098 initToolbar(inf.name);
1102 void GuiView::initToolbar(string const & name)
1104 GuiToolbar * tb = toolbar(name);
1107 int const visibility = guiApp->toolbars().defaultVisibility(name);
1108 bool newline = !(visibility & Toolbars::SAMEROW);
1109 tb->setVisible(false);
1110 tb->setVisibility(visibility);
1112 if (visibility & Toolbars::TOP) {
1114 addToolBarBreak(Qt::TopToolBarArea);
1115 addToolBar(Qt::TopToolBarArea, tb);
1118 if (visibility & Toolbars::BOTTOM) {
1120 addToolBarBreak(Qt::BottomToolBarArea);
1121 addToolBar(Qt::BottomToolBarArea, tb);
1124 if (visibility & Toolbars::LEFT) {
1126 addToolBarBreak(Qt::LeftToolBarArea);
1127 addToolBar(Qt::LeftToolBarArea, tb);
1130 if (visibility & Toolbars::RIGHT) {
1132 addToolBarBreak(Qt::RightToolBarArea);
1133 addToolBar(Qt::RightToolBarArea, tb);
1136 if (visibility & Toolbars::ON)
1137 tb->setVisible(true);
1139 tb->setMovable(true);
1143 TocModels & GuiView::tocModels()
1145 return d.toc_models_;
1149 void GuiView::setFocus()
1151 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1152 QMainWindow::setFocus();
1156 bool GuiView::hasFocus() const
1158 if (currentWorkArea())
1159 return currentWorkArea()->hasFocus();
1160 if (currentMainWorkArea())
1161 return currentMainWorkArea()->hasFocus();
1162 return d.bg_widget_->hasFocus();
1166 void GuiView::focusInEvent(QFocusEvent * e)
1168 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1169 QMainWindow::focusInEvent(e);
1170 // Make sure guiApp points to the correct view.
1171 guiApp->setCurrentView(this);
1172 if (currentWorkArea())
1173 currentWorkArea()->setFocus();
1174 else if (currentMainWorkArea())
1175 currentMainWorkArea()->setFocus();
1177 d.bg_widget_->setFocus();
1181 void GuiView::showEvent(QShowEvent * e)
1183 LYXERR(Debug::GUI, "Passed Geometry "
1184 << size().height() << "x" << size().width()
1185 << "+" << pos().x() << "+" << pos().y());
1187 if (d.splitter_->count() == 0)
1188 // No work area, switch to the background widget.
1192 QMainWindow::showEvent(e);
1196 bool GuiView::closeScheduled()
1203 bool GuiView::prepareAllBuffersForLogout()
1205 Buffer * first = theBufferList().first();
1209 // First, iterate over all buffers and ask the users if unsaved
1210 // changes should be saved.
1211 // We cannot use a for loop as the buffer list cycles.
1214 if (!saveBufferIfNeeded(*b, false))
1216 b = theBufferList().next(b);
1217 } while (b != first);
1219 // Next, save session state
1220 // When a view/window was closed before without quitting LyX, there
1221 // are already entries in the lastOpened list.
1222 theSession().lastOpened().clear();
1229 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1230 ** is responsibility of the container (e.g., dialog)
1232 void GuiView::closeEvent(QCloseEvent * close_event)
1234 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1236 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1237 Alert::warning(_("Exit LyX"),
1238 _("LyX could not be closed because documents are being processed by LyX."));
1239 close_event->setAccepted(false);
1243 // If the user pressed the x (so we didn't call closeView
1244 // programmatically), we want to clear all existing entries.
1246 theSession().lastOpened().clear();
1251 // it can happen that this event arrives without selecting the view,
1252 // e.g. when clicking the close button on a background window.
1254 if (!closeWorkAreaAll()) {
1256 close_event->ignore();
1260 // Make sure that nothing will use this to be closed View.
1261 guiApp->unregisterView(this);
1263 if (isFullScreen()) {
1264 // Switch off fullscreen before closing.
1269 // Make sure the timer time out will not trigger a statusbar update.
1270 d.statusbar_timer_.stop();
1272 // Saving fullscreen requires additional tweaks in the toolbar code.
1273 // It wouldn't also work under linux natively.
1274 if (lyxrc.allow_geometry_session) {
1279 close_event->accept();
1283 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1285 if (event->mimeData()->hasUrls())
1287 /// \todo Ask lyx-devel is this is enough:
1288 /// if (event->mimeData()->hasFormat("text/plain"))
1289 /// event->acceptProposedAction();
1293 void GuiView::dropEvent(QDropEvent * event)
1295 QList<QUrl> files = event->mimeData()->urls();
1296 if (files.isEmpty())
1299 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1300 for (int i = 0; i != files.size(); ++i) {
1301 string const file = os::internal_path(fromqstr(
1302 files.at(i).toLocalFile()));
1306 string const ext = support::getExtension(file);
1307 vector<const Format *> found_formats;
1309 // Find all formats that have the correct extension.
1310 for (const Format * fmt : theConverters().importableFormats())
1311 if (fmt->hasExtension(ext))
1312 found_formats.push_back(fmt);
1315 if (!found_formats.empty()) {
1316 if (found_formats.size() > 1) {
1317 //FIXME: show a dialog to choose the correct importable format
1318 LYXERR(Debug::FILES,
1319 "Multiple importable formats found, selecting first");
1321 string const arg = found_formats[0]->name() + " " + file;
1322 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1325 //FIXME: do we have to explicitly check whether it's a lyx file?
1326 LYXERR(Debug::FILES,
1327 "No formats found, trying to open it as a lyx file");
1328 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1330 // add the functions to the queue
1331 guiApp->addToFuncRequestQueue(cmd);
1334 // now process the collected functions. We perform the events
1335 // asynchronously. This prevents potential problems in case the
1336 // BufferView is closed within an event.
1337 guiApp->processFuncRequestQueueAsync();
1341 void GuiView::message(docstring const & str)
1343 if (ForkedProcess::iAmAChild())
1346 // call is moved to GUI-thread by GuiProgress
1347 d.progress_->appendMessage(toqstr(str));
1351 void GuiView::clearMessageText()
1353 message(docstring());
1357 void GuiView::updateStatusBarMessage(QString const & str)
1359 statusBar()->showMessage(str);
1360 d.statusbar_timer_.stop();
1361 d.statusbar_timer_.start(3000);
1365 void GuiView::clearMessage()
1367 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1368 // the hasFocus function mostly returns false, even if the focus is on
1369 // a workarea in this view.
1373 d.statusbar_timer_.stop();
1377 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1379 if (wa != d.current_work_area_
1380 || wa->bufferView().buffer().isInternal())
1382 Buffer const & buf = wa->bufferView().buffer();
1383 // Set the windows title
1384 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1385 if (buf.notifiesExternalModification()) {
1386 title = bformat(_("%1$s (modified externally)"), title);
1387 // If the external modification status has changed, then maybe the status of
1388 // buffer-save has changed too.
1392 title += from_ascii(" - LyX");
1394 setWindowTitle(toqstr(title));
1395 // Sets the path for the window: this is used by OSX to
1396 // allow a context click on the title bar showing a menu
1397 // with the path up to the file
1398 setWindowFilePath(toqstr(buf.absFileName()));
1399 // Tell Qt whether the current document is changed
1400 setWindowModified(!buf.isClean());
1402 if (buf.params().shell_escape)
1403 shell_escape_->show();
1405 shell_escape_->hide();
1407 if (buf.hasReadonlyFlag())
1412 if (buf.lyxvc().inUse()) {
1413 version_control_->show();
1414 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1416 version_control_->hide();
1420 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1422 if (d.current_work_area_)
1423 // disconnect the current work area from all slots
1424 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1426 disconnectBufferView();
1427 connectBufferView(wa->bufferView());
1428 connectBuffer(wa->bufferView().buffer());
1429 d.current_work_area_ = wa;
1430 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1431 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1432 QObject::connect(wa, SIGNAL(busy(bool)),
1433 this, SLOT(setBusy(bool)));
1434 // connection of a signal to a signal
1435 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1436 this, SIGNAL(bufferViewChanged()));
1437 Q_EMIT updateWindowTitle(wa);
1438 Q_EMIT bufferViewChanged();
1442 void GuiView::onBufferViewChanged()
1445 // Buffer-dependent dialogs must be updated. This is done here because
1446 // some dialogs require buffer()->text.
1448 zoom_slider_->setEnabled(currentBufferView());
1449 zoom_value_->setEnabled(currentBufferView());
1450 zoom_in_->setEnabled(currentBufferView());
1451 zoom_out_->setEnabled(currentBufferView());
1455 void GuiView::on_lastWorkAreaRemoved()
1458 // We already are in a close event. Nothing more to do.
1461 if (d.splitter_->count() > 1)
1462 // We have a splitter so don't close anything.
1465 // Reset and updates the dialogs.
1466 Q_EMIT bufferViewChanged();
1471 if (lyxrc.open_buffers_in_tabs)
1472 // Nothing more to do, the window should stay open.
1475 if (guiApp->viewIds().size() > 1) {
1481 // On Mac we also close the last window because the application stay
1482 // resident in memory. On other platforms we don't close the last
1483 // window because this would quit the application.
1489 void GuiView::updateStatusBar()
1491 // let the user see the explicit message
1492 if (d.statusbar_timer_.isActive())
1499 void GuiView::showMessage()
1503 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1504 if (msg.isEmpty()) {
1505 BufferView const * bv = currentBufferView();
1507 msg = toqstr(bv->cursor().currentState(devel_mode_));
1509 msg = qt_("Welcome to LyX!");
1511 statusBar()->showMessage(msg);
1515 bool GuiView::event(QEvent * e)
1519 // Useful debug code:
1520 //case QEvent::ActivationChange:
1521 //case QEvent::WindowDeactivate:
1522 //case QEvent::Paint:
1523 //case QEvent::Enter:
1524 //case QEvent::Leave:
1525 //case QEvent::HoverEnter:
1526 //case QEvent::HoverLeave:
1527 //case QEvent::HoverMove:
1528 //case QEvent::StatusTip:
1529 //case QEvent::DragEnter:
1530 //case QEvent::DragLeave:
1531 //case QEvent::Drop:
1534 case QEvent::WindowStateChange: {
1535 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1536 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1537 bool result = QMainWindow::event(e);
1538 bool nfstate = (windowState() & Qt::WindowFullScreen);
1539 if (!ofstate && nfstate) {
1540 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1541 // switch to full-screen state
1542 if (lyxrc.full_screen_statusbar)
1543 statusBar()->hide();
1544 if (lyxrc.full_screen_menubar)
1546 if (lyxrc.full_screen_toolbars) {
1547 for (auto const & tb_p : d.toolbars_)
1548 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1549 tb_p.second->hide();
1551 for (int i = 0; i != d.splitter_->count(); ++i)
1552 d.tabWorkArea(i)->setFullScreen(true);
1553 #if QT_VERSION > 0x050903
1554 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1555 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1557 setContentsMargins(-2, -2, -2, -2);
1559 hideDialogs("prefs", nullptr);
1560 } else if (ofstate && !nfstate) {
1561 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1562 // switch back from full-screen state
1563 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1564 statusBar()->show();
1565 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1567 if (lyxrc.full_screen_toolbars) {
1568 for (auto const & tb_p : d.toolbars_)
1569 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1570 tb_p.second->show();
1573 for (int i = 0; i != d.splitter_->count(); ++i)
1574 d.tabWorkArea(i)->setFullScreen(false);
1575 #if QT_VERSION > 0x050903
1576 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1578 setContentsMargins(0, 0, 0, 0);
1582 case QEvent::WindowActivate: {
1583 GuiView * old_view = guiApp->currentView();
1584 if (this == old_view) {
1586 return QMainWindow::event(e);
1588 if (old_view && old_view->currentBufferView()) {
1589 // save current selection to the selection buffer to allow
1590 // middle-button paste in this window.
1591 cap::saveSelection(old_view->currentBufferView()->cursor());
1593 guiApp->setCurrentView(this);
1594 if (d.current_work_area_)
1595 on_currentWorkAreaChanged(d.current_work_area_);
1599 return QMainWindow::event(e);
1602 case QEvent::ShortcutOverride: {
1604 if (isFullScreen() && menuBar()->isHidden()) {
1605 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1606 // FIXME: we should also try to detect special LyX shortcut such as
1607 // Alt-P and Alt-M. Right now there is a hack in
1608 // GuiWorkArea::processKeySym() that hides again the menubar for
1610 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1612 return QMainWindow::event(e);
1615 return QMainWindow::event(e);
1618 case QEvent::ApplicationPaletteChange: {
1619 // runtime switch from/to dark mode
1621 return QMainWindow::event(e);
1625 return QMainWindow::event(e);
1629 void GuiView::resetWindowTitle()
1631 setWindowTitle(qt_("LyX"));
1634 bool GuiView::focusNextPrevChild(bool /*next*/)
1641 bool GuiView::busy() const
1647 void GuiView::setBusy(bool busy)
1649 bool const busy_before = busy_ > 0;
1650 busy ? ++busy_ : --busy_;
1651 if ((busy_ > 0) == busy_before)
1652 // busy state didn't change
1656 QApplication::setOverrideCursor(Qt::WaitCursor);
1659 QApplication::restoreOverrideCursor();
1664 void GuiView::resetCommandExecute()
1666 command_execute_ = false;
1671 double GuiView::pixelRatio() const
1673 #if QT_VERSION >= 0x050000
1674 return qt_scale_factor * devicePixelRatio();
1681 GuiWorkArea * GuiView::workArea(int index)
1683 if (TabWorkArea * twa = d.currentTabWorkArea())
1684 if (index < twa->count())
1685 return twa->workArea(index);
1690 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1692 if (currentWorkArea()
1693 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1694 return currentWorkArea();
1695 if (TabWorkArea * twa = d.currentTabWorkArea())
1696 return twa->workArea(buffer);
1701 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1703 // Automatically create a TabWorkArea if there are none yet.
1704 TabWorkArea * tab_widget = d.splitter_->count()
1705 ? d.currentTabWorkArea() : addTabWorkArea();
1706 return tab_widget->addWorkArea(buffer, *this);
1710 TabWorkArea * GuiView::addTabWorkArea()
1712 TabWorkArea * twa = new TabWorkArea;
1713 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1714 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1715 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1716 this, SLOT(on_lastWorkAreaRemoved()));
1718 d.splitter_->addWidget(twa);
1719 d.stack_widget_->setCurrentWidget(d.splitter_);
1724 GuiWorkArea const * GuiView::currentWorkArea() const
1726 return d.current_work_area_;
1730 GuiWorkArea * GuiView::currentWorkArea()
1732 return d.current_work_area_;
1736 GuiWorkArea const * GuiView::currentMainWorkArea() const
1738 if (!d.currentTabWorkArea())
1740 return d.currentTabWorkArea()->currentWorkArea();
1744 GuiWorkArea * GuiView::currentMainWorkArea()
1746 if (!d.currentTabWorkArea())
1748 return d.currentTabWorkArea()->currentWorkArea();
1752 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1754 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1756 d.current_work_area_ = nullptr;
1758 Q_EMIT bufferViewChanged();
1762 // FIXME: I've no clue why this is here and why it accesses
1763 // theGuiApp()->currentView, which might be 0 (bug 6464).
1764 // See also 27525 (vfr).
1765 if (theGuiApp()->currentView() == this
1766 && theGuiApp()->currentView()->currentWorkArea() == wa)
1769 if (currentBufferView())
1770 cap::saveSelection(currentBufferView()->cursor());
1772 theGuiApp()->setCurrentView(this);
1773 d.current_work_area_ = wa;
1775 // We need to reset this now, because it will need to be
1776 // right if the tabWorkArea gets reset in the for loop. We
1777 // will change it back if we aren't in that case.
1778 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1779 d.current_main_work_area_ = wa;
1781 for (int i = 0; i != d.splitter_->count(); ++i) {
1782 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1783 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1784 << ", Current main wa: " << currentMainWorkArea());
1789 d.current_main_work_area_ = old_cmwa;
1791 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1792 on_currentWorkAreaChanged(wa);
1793 BufferView & bv = wa->bufferView();
1794 bv.cursor().fixIfBroken();
1796 wa->setUpdatesEnabled(true);
1797 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1801 void GuiView::removeWorkArea(GuiWorkArea * wa)
1803 LASSERT(wa, return);
1804 if (wa == d.current_work_area_) {
1806 disconnectBufferView();
1807 d.current_work_area_ = nullptr;
1808 d.current_main_work_area_ = nullptr;
1811 bool found_twa = false;
1812 for (int i = 0; i != d.splitter_->count(); ++i) {
1813 TabWorkArea * twa = d.tabWorkArea(i);
1814 if (twa->removeWorkArea(wa)) {
1815 // Found in this tab group, and deleted the GuiWorkArea.
1817 if (twa->count() != 0) {
1818 if (d.current_work_area_ == nullptr)
1819 // This means that we are closing the current GuiWorkArea, so
1820 // switch to the next GuiWorkArea in the found TabWorkArea.
1821 setCurrentWorkArea(twa->currentWorkArea());
1823 // No more WorkAreas in this tab group, so delete it.
1830 // It is not a tabbed work area (i.e., the search work area), so it
1831 // should be deleted by other means.
1832 LASSERT(found_twa, return);
1834 if (d.current_work_area_ == nullptr) {
1835 if (d.splitter_->count() != 0) {
1836 TabWorkArea * twa = d.currentTabWorkArea();
1837 setCurrentWorkArea(twa->currentWorkArea());
1839 // No more work areas, switch to the background widget.
1840 setCurrentWorkArea(nullptr);
1846 LayoutBox * GuiView::getLayoutDialog() const
1852 void GuiView::updateLayoutList()
1855 d.layout_->updateContents(false);
1859 void GuiView::updateToolbars()
1861 if (d.current_work_area_) {
1863 if (d.current_work_area_->bufferView().cursor().inMathed()
1864 && !d.current_work_area_->bufferView().cursor().inRegexped())
1865 context |= Toolbars::MATH;
1866 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1867 context |= Toolbars::TABLE;
1868 if (currentBufferView()->buffer().areChangesPresent()
1869 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1870 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1871 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1872 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1873 context |= Toolbars::REVIEW;
1874 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1875 context |= Toolbars::MATHMACROTEMPLATE;
1876 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1877 context |= Toolbars::IPA;
1878 if (command_execute_)
1879 context |= Toolbars::MINIBUFFER;
1880 if (minibuffer_focus_) {
1881 context |= Toolbars::MINIBUFFER_FOCUS;
1882 minibuffer_focus_ = false;
1885 for (auto const & tb_p : d.toolbars_)
1886 tb_p.second->update(context);
1888 for (auto const & tb_p : d.toolbars_)
1889 tb_p.second->update();
1893 void GuiView::refillToolbars()
1895 DynamicMenuButton::resetIconCache();
1896 for (auto const & tb_p : d.toolbars_)
1897 tb_p.second->refill();
1901 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1903 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1904 LASSERT(newBuffer, return);
1906 GuiWorkArea * wa = workArea(*newBuffer);
1907 if (wa == nullptr) {
1909 newBuffer->masterBuffer()->updateBuffer();
1911 wa = addWorkArea(*newBuffer);
1912 // scroll to the position when the BufferView was last closed
1913 if (lyxrc.use_lastfilepos) {
1914 LastFilePosSection::FilePos filepos =
1915 theSession().lastFilePos().load(newBuffer->fileName());
1916 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1919 //Disconnect the old buffer...there's no new one.
1922 connectBuffer(*newBuffer);
1923 connectBufferView(wa->bufferView());
1925 setCurrentWorkArea(wa);
1929 void GuiView::connectBuffer(Buffer & buf)
1931 buf.setGuiDelegate(this);
1935 void GuiView::disconnectBuffer()
1937 if (d.current_work_area_)
1938 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1942 void GuiView::connectBufferView(BufferView & bv)
1944 bv.setGuiDelegate(this);
1948 void GuiView::disconnectBufferView()
1950 if (d.current_work_area_)
1951 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1955 void GuiView::errors(string const & error_type, bool from_master)
1957 BufferView const * const bv = currentBufferView();
1961 ErrorList const & el = from_master ?
1962 bv->buffer().masterBuffer()->errorList(error_type) :
1963 bv->buffer().errorList(error_type);
1968 string err = error_type;
1970 err = "from_master|" + error_type;
1971 showDialog("errorlist", err);
1975 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1977 d.toc_models_.updateItem(toqstr(type), dit);
1981 void GuiView::structureChanged()
1983 // This is called from the Buffer, which has no way to ensure that cursors
1984 // in BufferView remain valid.
1985 if (documentBufferView())
1986 documentBufferView()->cursor().sanitize();
1987 // FIXME: This is slightly expensive, though less than the tocBackend update
1988 // (#9880). This also resets the view in the Toc Widget (#6675).
1989 d.toc_models_.reset(documentBufferView());
1990 // Navigator needs more than a simple update in this case. It needs to be
1992 updateDialog("toc", "");
1996 void GuiView::updateDialog(string const & name, string const & sdata)
1998 if (!isDialogVisible(name))
2001 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2002 if (it == d.dialogs_.end())
2005 Dialog * const dialog = it->second.get();
2006 if (dialog->isVisibleView())
2007 dialog->initialiseParams(sdata);
2011 BufferView * GuiView::documentBufferView()
2013 return currentMainWorkArea()
2014 ? ¤tMainWorkArea()->bufferView()
2019 BufferView const * GuiView::documentBufferView() const
2021 return currentMainWorkArea()
2022 ? ¤tMainWorkArea()->bufferView()
2027 BufferView * GuiView::currentBufferView()
2029 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2033 BufferView const * GuiView::currentBufferView() const
2035 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2039 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2040 Buffer const * orig, Buffer * clone)
2042 bool const success = clone->autoSave();
2044 busyBuffers.remove(orig);
2046 ? _("Automatic save done.")
2047 : _("Automatic save failed!");
2051 void GuiView::autoSave()
2053 LYXERR(Debug::INFO, "Running autoSave()");
2055 Buffer * buffer = documentBufferView()
2056 ? &documentBufferView()->buffer() : nullptr;
2058 resetAutosaveTimers();
2062 GuiViewPrivate::busyBuffers.insert(buffer);
2063 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2064 buffer, buffer->cloneBufferOnly());
2065 d.autosave_watcher_.setFuture(f);
2066 resetAutosaveTimers();
2070 void GuiView::resetAutosaveTimers()
2073 d.autosave_timeout_.restart();
2077 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2080 Buffer * buf = currentBufferView()
2081 ? ¤tBufferView()->buffer() : nullptr;
2082 Buffer * doc_buffer = documentBufferView()
2083 ? &(documentBufferView()->buffer()) : nullptr;
2086 /* In LyX/Mac, when a dialog is open, the menus of the
2087 application can still be accessed without giving focus to
2088 the main window. In this case, we want to disable the menu
2089 entries that are buffer-related.
2090 This code must not be used on Linux and Windows, since it
2091 would disable buffer-related entries when hovering over the
2092 menu (see bug #9574).
2094 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2100 // Check whether we need a buffer
2101 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2102 // no, exit directly
2103 flag.message(from_utf8(N_("Command not allowed with"
2104 "out any document open")));
2105 flag.setEnabled(false);
2109 if (cmd.origin() == FuncRequest::TOC) {
2110 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2111 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2112 flag.setEnabled(false);
2116 switch(cmd.action()) {
2117 case LFUN_BUFFER_IMPORT:
2120 case LFUN_MASTER_BUFFER_EXPORT:
2122 && (doc_buffer->parent() != nullptr
2123 || doc_buffer->hasChildren())
2124 && !d.processing_thread_watcher_.isRunning()
2125 // this launches a dialog, which would be in the wrong Buffer
2126 && !(::lyx::operator==(cmd.argument(), "custom"));
2129 case LFUN_MASTER_BUFFER_UPDATE:
2130 case LFUN_MASTER_BUFFER_VIEW:
2132 && (doc_buffer->parent() != nullptr
2133 || doc_buffer->hasChildren())
2134 && !d.processing_thread_watcher_.isRunning();
2137 case LFUN_BUFFER_UPDATE:
2138 case LFUN_BUFFER_VIEW: {
2139 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2143 string format = to_utf8(cmd.argument());
2144 if (cmd.argument().empty())
2145 format = doc_buffer->params().getDefaultOutputFormat();
2146 enable = doc_buffer->params().isExportable(format, true);
2150 case LFUN_BUFFER_RELOAD:
2151 enable = doc_buffer && !doc_buffer->isUnnamed()
2152 && doc_buffer->fileName().exists()
2153 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2156 case LFUN_BUFFER_RESET_EXPORT:
2157 enable = doc_buffer != nullptr;
2160 case LFUN_BUFFER_CHILD_OPEN:
2161 enable = doc_buffer != nullptr;
2164 case LFUN_MASTER_BUFFER_FORALL: {
2165 if (doc_buffer == nullptr) {
2166 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2170 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2171 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2172 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2177 for (Buffer * buf : doc_buffer->allRelatives()) {
2178 GuiWorkArea * wa = workArea(*buf);
2181 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2182 enable = flag.enabled();
2189 case LFUN_BUFFER_WRITE:
2190 enable = doc_buffer && (doc_buffer->isUnnamed()
2191 || (!doc_buffer->isClean()
2192 || cmd.argument() == "force"));
2195 //FIXME: This LFUN should be moved to GuiApplication.
2196 case LFUN_BUFFER_WRITE_ALL: {
2197 // We enable the command only if there are some modified buffers
2198 Buffer * first = theBufferList().first();
2203 // We cannot use a for loop as the buffer list is a cycle.
2205 if (!b->isClean()) {
2209 b = theBufferList().next(b);
2210 } while (b != first);
2214 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2215 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2218 case LFUN_BUFFER_EXPORT: {
2219 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2223 return doc_buffer->getStatus(cmd, flag);
2226 case LFUN_BUFFER_EXPORT_AS:
2227 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2232 case LFUN_BUFFER_WRITE_AS:
2233 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2234 enable = doc_buffer != nullptr;
2237 case LFUN_EXPORT_CANCEL:
2238 enable = d.processing_thread_watcher_.isRunning();
2241 case LFUN_BUFFER_CLOSE:
2242 case LFUN_VIEW_CLOSE:
2243 enable = doc_buffer != nullptr;
2246 case LFUN_BUFFER_CLOSE_ALL:
2247 enable = theBufferList().last() != theBufferList().first();
2250 case LFUN_BUFFER_CHKTEX: {
2251 // hide if we have no checktex command
2252 if (lyxrc.chktex_command.empty()) {
2253 flag.setUnknown(true);
2257 if (!doc_buffer || !doc_buffer->params().isLatex()
2258 || d.processing_thread_watcher_.isRunning()) {
2259 // grey out, don't hide
2267 case LFUN_VIEW_SPLIT:
2268 if (cmd.getArg(0) == "vertical")
2269 enable = doc_buffer && (d.splitter_->count() == 1 ||
2270 d.splitter_->orientation() == Qt::Vertical);
2272 enable = doc_buffer && (d.splitter_->count() == 1 ||
2273 d.splitter_->orientation() == Qt::Horizontal);
2276 case LFUN_TAB_GROUP_CLOSE:
2277 enable = d.tabWorkAreaCount() > 1;
2280 case LFUN_DEVEL_MODE_TOGGLE:
2281 flag.setOnOff(devel_mode_);
2284 case LFUN_TOOLBAR_SET: {
2285 string const name = cmd.getArg(0);
2286 string const state = cmd.getArg(1);
2287 if (name.empty() || state.empty()) {
2289 docstring const msg =
2290 _("Function toolbar-set requires two arguments!");
2294 if (state != "on" && state != "off" && state != "auto") {
2296 docstring const msg =
2297 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2302 if (GuiToolbar * t = toolbar(name)) {
2303 bool const autovis = t->visibility() & Toolbars::AUTO;
2305 flag.setOnOff(t->isVisible() && !autovis);
2306 else if (state == "off")
2307 flag.setOnOff(!t->isVisible() && !autovis);
2308 else if (state == "auto")
2309 flag.setOnOff(autovis);
2312 docstring const msg =
2313 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2319 case LFUN_TOOLBAR_TOGGLE: {
2320 string const name = cmd.getArg(0);
2321 if (GuiToolbar * t = toolbar(name))
2322 flag.setOnOff(t->isVisible());
2325 docstring const msg =
2326 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2332 case LFUN_TOOLBAR_MOVABLE: {
2333 string const name = cmd.getArg(0);
2334 // use negation since locked == !movable
2336 // toolbar name * locks all toolbars
2337 flag.setOnOff(!toolbarsMovable_);
2338 else if (GuiToolbar * t = toolbar(name))
2339 flag.setOnOff(!(t->isMovable()));
2342 docstring const msg =
2343 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2349 case LFUN_ICON_SIZE:
2350 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2353 case LFUN_DROP_LAYOUTS_CHOICE:
2354 enable = buf != nullptr;
2357 case LFUN_UI_TOGGLE:
2358 if (cmd.argument() == "zoom") {
2359 enable = doc_buffer;
2360 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2361 } else if (cmd.argument() == "zoomslider") {
2362 enable = doc_buffer;
2363 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2365 flag.setOnOff(isFullScreen());
2368 case LFUN_DIALOG_DISCONNECT_INSET:
2371 case LFUN_DIALOG_HIDE:
2372 // FIXME: should we check if the dialog is shown?
2375 case LFUN_DIALOG_TOGGLE:
2376 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2379 case LFUN_DIALOG_SHOW: {
2380 string const name = cmd.getArg(0);
2382 enable = name == "aboutlyx"
2383 || name == "file" //FIXME: should be removed.
2384 || name == "lyxfiles"
2386 || name == "texinfo"
2387 || name == "progress"
2388 || name == "compare";
2389 else if (name == "character" || name == "symbols"
2390 || name == "mathdelimiter" || name == "mathmatrix") {
2391 if (!buf || buf->isReadonly())
2394 Cursor const & cur = currentBufferView()->cursor();
2395 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2398 else if (name == "latexlog")
2399 enable = FileName(doc_buffer->logName()).isReadableFile();
2400 else if (name == "spellchecker")
2401 enable = theSpellChecker()
2402 && !doc_buffer->text().empty();
2403 else if (name == "vclog")
2404 enable = doc_buffer->lyxvc().inUse();
2408 case LFUN_DIALOG_UPDATE: {
2409 string const name = cmd.getArg(0);
2411 enable = name == "prefs";
2415 case LFUN_COMMAND_EXECUTE:
2417 case LFUN_MENU_OPEN:
2418 // Nothing to check.
2421 case LFUN_COMPLETION_INLINE:
2422 if (!d.current_work_area_
2423 || !d.current_work_area_->completer().inlinePossible(
2424 currentBufferView()->cursor()))
2428 case LFUN_COMPLETION_POPUP:
2429 if (!d.current_work_area_
2430 || !d.current_work_area_->completer().popupPossible(
2431 currentBufferView()->cursor()))
2436 if (!d.current_work_area_
2437 || !d.current_work_area_->completer().inlinePossible(
2438 currentBufferView()->cursor()))
2442 case LFUN_COMPLETION_ACCEPT:
2443 if (!d.current_work_area_
2444 || (!d.current_work_area_->completer().popupVisible()
2445 && !d.current_work_area_->completer().inlineVisible()
2446 && !d.current_work_area_->completer().completionAvailable()))
2450 case LFUN_COMPLETION_CANCEL:
2451 if (!d.current_work_area_
2452 || (!d.current_work_area_->completer().popupVisible()
2453 && !d.current_work_area_->completer().inlineVisible()))
2457 case LFUN_BUFFER_ZOOM_OUT:
2458 case LFUN_BUFFER_ZOOM_IN: {
2459 // only diff between these two is that the default for ZOOM_OUT
2461 bool const neg_zoom =
2462 convert<int>(cmd.argument()) < 0 ||
2463 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2464 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2465 docstring const msg =
2466 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2470 enable = doc_buffer;
2474 case LFUN_BUFFER_ZOOM: {
2475 bool const less_than_min_zoom =
2476 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2477 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2478 docstring const msg =
2479 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2482 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2485 enable = doc_buffer;
2489 case LFUN_BUFFER_MOVE_NEXT:
2490 case LFUN_BUFFER_MOVE_PREVIOUS:
2491 // we do not cycle when moving
2492 case LFUN_BUFFER_NEXT:
2493 case LFUN_BUFFER_PREVIOUS:
2494 // because we cycle, it doesn't matter whether on first or last
2495 enable = (d.currentTabWorkArea()->count() > 1);
2497 case LFUN_BUFFER_SWITCH:
2498 // toggle on the current buffer, but do not toggle off
2499 // the other ones (is that a good idea?)
2501 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2502 flag.setOnOff(true);
2505 case LFUN_VC_REGISTER:
2506 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2508 case LFUN_VC_RENAME:
2509 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2512 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2514 case LFUN_VC_CHECK_IN:
2515 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2517 case LFUN_VC_CHECK_OUT:
2518 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2520 case LFUN_VC_LOCKING_TOGGLE:
2521 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2522 && doc_buffer->lyxvc().lockingToggleEnabled();
2523 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2525 case LFUN_VC_REVERT:
2526 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2527 && !doc_buffer->hasReadonlyFlag();
2529 case LFUN_VC_UNDO_LAST:
2530 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2532 case LFUN_VC_REPO_UPDATE:
2533 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2535 case LFUN_VC_COMMAND: {
2536 if (cmd.argument().empty())
2538 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2542 case LFUN_VC_COMPARE:
2543 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2546 case LFUN_SERVER_GOTO_FILE_ROW:
2547 case LFUN_LYX_ACTIVATE:
2548 case LFUN_WINDOW_RAISE:
2550 case LFUN_FORWARD_SEARCH:
2551 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2554 case LFUN_FILE_INSERT_PLAINTEXT:
2555 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2556 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2559 case LFUN_SPELLING_CONTINUOUSLY:
2560 flag.setOnOff(lyxrc.spellcheck_continuously);
2563 case LFUN_CITATION_OPEN:
2572 flag.setEnabled(false);
2578 static FileName selectTemplateFile()
2580 FileDialog dlg(qt_("Select template file"));
2581 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2582 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2584 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2585 QStringList(qt_("LyX Documents (*.lyx)")));
2587 if (result.first == FileDialog::Later)
2589 if (result.second.isEmpty())
2591 return FileName(fromqstr(result.second));
2595 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2599 Buffer * newBuffer = nullptr;
2601 newBuffer = checkAndLoadLyXFile(filename);
2602 } catch (ExceptionMessage const &) {
2609 message(_("Document not loaded."));
2613 setBuffer(newBuffer);
2614 newBuffer->errors("Parse");
2617 theSession().lastFiles().add(filename);
2618 theSession().writeFile();
2625 void GuiView::openDocument(string const & fname)
2627 string initpath = lyxrc.document_path;
2629 if (documentBufferView()) {
2630 string const trypath = documentBufferView()->buffer().filePath();
2631 // If directory is writeable, use this as default.
2632 if (FileName(trypath).isDirWritable())
2638 if (fname.empty()) {
2639 FileDialog dlg(qt_("Select document to open"));
2640 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2641 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2643 QStringList const filter({
2644 qt_("LyX Documents (*.lyx)"),
2645 qt_("LyX Document Backups (*.lyx~)"),
2646 qt_("All Files (*.*)")
2648 FileDialog::Result result =
2649 dlg.open(toqstr(initpath), filter);
2651 if (result.first == FileDialog::Later)
2654 filename = fromqstr(result.second);
2656 // check selected filename
2657 if (filename.empty()) {
2658 message(_("Canceled."));
2664 // get absolute path of file and add ".lyx" to the filename if
2666 FileName const fullname =
2667 fileSearch(string(), filename, "lyx", support::may_not_exist);
2668 if (!fullname.empty())
2669 filename = fullname.absFileName();
2671 if (!fullname.onlyPath().isDirectory()) {
2672 Alert::warning(_("Invalid filename"),
2673 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2674 from_utf8(fullname.absFileName())));
2678 // if the file doesn't exist and isn't already open (bug 6645),
2679 // let the user create one
2680 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2681 !LyXVC::file_not_found_hook(fullname)) {
2682 // the user specifically chose this name. Believe him.
2683 Buffer * const b = newFile(filename, string(), true);
2689 docstring const disp_fn = makeDisplayPath(filename);
2690 message(bformat(_("Opening document %1$s..."), disp_fn));
2693 Buffer * buf = loadDocument(fullname);
2695 str2 = bformat(_("Document %1$s opened."), disp_fn);
2696 if (buf->lyxvc().inUse())
2697 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2698 " " + _("Version control detected.");
2700 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2705 // FIXME: clean that
2706 static bool import(GuiView * lv, FileName const & filename,
2707 string const & format, ErrorList & errorList)
2709 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2711 string loader_format;
2712 vector<string> loaders = theConverters().loaders();
2713 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2714 for (string const & loader : loaders) {
2715 if (!theConverters().isReachable(format, loader))
2718 string const tofile =
2719 support::changeExtension(filename.absFileName(),
2720 theFormats().extension(loader));
2721 if (theConverters().convert(nullptr, filename, FileName(tofile),
2722 filename, format, loader, errorList) != Converters::SUCCESS)
2724 loader_format = loader;
2727 if (loader_format.empty()) {
2728 frontend::Alert::error(_("Couldn't import file"),
2729 bformat(_("No information for importing the format %1$s."),
2730 translateIfPossible(theFormats().prettyName(format))));
2734 loader_format = format;
2736 if (loader_format == "lyx") {
2737 Buffer * buf = lv->loadDocument(lyxfile);
2741 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2745 bool as_paragraphs = loader_format == "textparagraph";
2746 string filename2 = (loader_format == format) ? filename.absFileName()
2747 : support::changeExtension(filename.absFileName(),
2748 theFormats().extension(loader_format));
2749 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2751 guiApp->setCurrentView(lv);
2752 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2759 void GuiView::importDocument(string const & argument)
2762 string filename = split(argument, format, ' ');
2764 LYXERR(Debug::INFO, format << " file: " << filename);
2766 // need user interaction
2767 if (filename.empty()) {
2768 string initpath = lyxrc.document_path;
2769 if (documentBufferView()) {
2770 string const trypath = documentBufferView()->buffer().filePath();
2771 // If directory is writeable, use this as default.
2772 if (FileName(trypath).isDirWritable())
2776 docstring const text = bformat(_("Select %1$s file to import"),
2777 translateIfPossible(theFormats().prettyName(format)));
2779 FileDialog dlg(toqstr(text));
2780 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2781 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2783 docstring filter = translateIfPossible(theFormats().prettyName(format));
2786 filter += from_utf8(theFormats().extensions(format));
2789 FileDialog::Result result =
2790 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2792 if (result.first == FileDialog::Later)
2795 filename = fromqstr(result.second);
2797 // check selected filename
2798 if (filename.empty())
2799 message(_("Canceled."));
2802 if (filename.empty())
2805 // get absolute path of file
2806 FileName const fullname(support::makeAbsPath(filename));
2808 // Can happen if the user entered a path into the dialog
2810 if (fullname.onlyFileName().empty()) {
2811 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2812 "Aborting import."),
2813 from_utf8(fullname.absFileName()));
2814 frontend::Alert::error(_("File name error"), msg);
2815 message(_("Canceled."));
2820 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2822 // Check if the document already is open
2823 Buffer * buf = theBufferList().getBuffer(lyxfile);
2826 if (!closeBuffer()) {
2827 message(_("Canceled."));
2832 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2834 // if the file exists already, and we didn't do
2835 // -i lyx thefile.lyx, warn
2836 if (lyxfile.exists() && fullname != lyxfile) {
2838 docstring text = bformat(_("The document %1$s already exists.\n\n"
2839 "Do you want to overwrite that document?"), displaypath);
2840 int const ret = Alert::prompt(_("Overwrite document?"),
2841 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2844 message(_("Canceled."));
2849 message(bformat(_("Importing %1$s..."), displaypath));
2850 ErrorList errorList;
2851 if (import(this, fullname, format, errorList))
2852 message(_("imported."));
2854 message(_("file not imported!"));
2856 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2860 void GuiView::newDocument(string const & filename, string templatefile,
2863 FileName initpath(lyxrc.document_path);
2864 if (documentBufferView()) {
2865 FileName const trypath(documentBufferView()->buffer().filePath());
2866 // If directory is writeable, use this as default.
2867 if (trypath.isDirWritable())
2871 if (from_template) {
2872 if (templatefile.empty())
2873 templatefile = selectTemplateFile().absFileName();
2874 if (templatefile.empty())
2879 if (filename.empty())
2880 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2882 b = newFile(filename, templatefile, true);
2887 // If no new document could be created, it is unsure
2888 // whether there is a valid BufferView.
2889 if (currentBufferView())
2890 // Ensure the cursor is correctly positioned on screen.
2891 currentBufferView()->showCursor();
2895 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2897 BufferView * bv = documentBufferView();
2902 FileName filename(to_utf8(fname));
2903 if (filename.empty()) {
2904 // Launch a file browser
2906 string initpath = lyxrc.document_path;
2907 string const trypath = bv->buffer().filePath();
2908 // If directory is writeable, use this as default.
2909 if (FileName(trypath).isDirWritable())
2913 FileDialog dlg(qt_("Select LyX document to insert"));
2914 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2915 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2917 FileDialog::Result result = dlg.open(toqstr(initpath),
2918 QStringList(qt_("LyX Documents (*.lyx)")));
2920 if (result.first == FileDialog::Later)
2924 filename.set(fromqstr(result.second));
2926 // check selected filename
2927 if (filename.empty()) {
2928 // emit message signal.
2929 message(_("Canceled."));
2934 bv->insertLyXFile(filename, ignorelang);
2935 bv->buffer().errors("Parse");
2940 string const GuiView::getTemplatesPath(Buffer & b)
2942 // We start off with the user's templates path
2943 string result = addPath(package().user_support().absFileName(), "templates");
2944 // Check for the document language
2945 string const langcode = b.params().language->code();
2946 string const shortcode = langcode.substr(0, 2);
2947 if (!langcode.empty() && shortcode != "en") {
2948 string subpath = addPath(result, shortcode);
2949 string subpath_long = addPath(result, langcode);
2950 // If we have a subdirectory for the language already,
2952 FileName sp = FileName(subpath);
2953 if (sp.isDirectory())
2955 else if (FileName(subpath_long).isDirectory())
2956 result = subpath_long;
2958 // Ask whether we should create such a subdirectory
2959 docstring const text =
2960 bformat(_("It is suggested to save the template in a subdirectory\n"
2961 "appropriate to the document language (%1$s).\n"
2962 "This subdirectory does not exists yet.\n"
2963 "Do you want to create it?"),
2964 _(b.params().language->display()));
2965 if (Alert::prompt(_("Create Language Directory?"),
2966 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2967 // If the user agreed, we try to create it and report if this failed.
2968 if (!sp.createDirectory(0777))
2969 Alert::error(_("Subdirectory creation failed!"),
2970 _("Could not create subdirectory.\n"
2971 "The template will be saved in the parent directory."));
2977 // Do we have a layout category?
2978 string const cat = b.params().baseClass() ?
2979 b.params().baseClass()->category()
2982 string subpath = addPath(result, cat);
2983 // If we have a subdirectory for the category already,
2985 FileName sp = FileName(subpath);
2986 if (sp.isDirectory())
2989 // Ask whether we should create such a subdirectory
2990 docstring const text =
2991 bformat(_("It is suggested to save the template in a subdirectory\n"
2992 "appropriate to the layout category (%1$s).\n"
2993 "This subdirectory does not exists yet.\n"
2994 "Do you want to create it?"),
2996 if (Alert::prompt(_("Create Category Directory?"),
2997 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2998 // If the user agreed, we try to create it and report if this failed.
2999 if (!sp.createDirectory(0777))
3000 Alert::error(_("Subdirectory creation failed!"),
3001 _("Could not create subdirectory.\n"
3002 "The template will be saved in the parent directory."));
3012 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3014 FileName fname = b.fileName();
3015 FileName const oldname = fname;
3016 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3018 if (!newname.empty()) {
3021 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3023 fname = support::makeAbsPath(to_utf8(newname),
3024 oldname.onlyPath().absFileName());
3026 // Switch to this Buffer.
3029 // No argument? Ask user through dialog.
3031 QString const title = as_template ? qt_("Choose a filename to save template as")
3032 : qt_("Choose a filename to save document as");
3033 FileDialog dlg(title);
3034 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3035 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3037 if (!isLyXFileName(fname.absFileName()))
3038 fname.changeExtension(".lyx");
3040 string const path = as_template ?
3042 : fname.onlyPath().absFileName();
3043 FileDialog::Result result =
3044 dlg.save(toqstr(path),
3045 QStringList(qt_("LyX Documents (*.lyx)")),
3046 toqstr(fname.onlyFileName()));
3048 if (result.first == FileDialog::Later)
3051 fname.set(fromqstr(result.second));
3056 if (!isLyXFileName(fname.absFileName()))
3057 fname.changeExtension(".lyx");
3060 // fname is now the new Buffer location.
3062 // if there is already a Buffer open with this name, we do not want
3063 // to have another one. (the second test makes sure we're not just
3064 // trying to overwrite ourselves, which is fine.)
3065 if (theBufferList().exists(fname) && fname != oldname
3066 && theBufferList().getBuffer(fname) != &b) {
3067 docstring const text =
3068 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3069 "Please close it before attempting to overwrite it.\n"
3070 "Do you want to choose a new filename?"),
3071 from_utf8(fname.absFileName()));
3072 int const ret = Alert::prompt(_("Chosen File Already Open"),
3073 text, 0, 1, _("&Rename"), _("&Cancel"));
3075 case 0: return renameBuffer(b, docstring(), kind);
3076 case 1: return false;
3081 bool const existsLocal = fname.exists();
3082 bool const existsInVC = LyXVC::fileInVC(fname);
3083 if (existsLocal || existsInVC) {
3084 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3085 if (kind != LV_WRITE_AS && existsInVC) {
3086 // renaming to a name that is already in VC
3088 docstring text = bformat(_("The document %1$s "
3089 "is already registered.\n\n"
3090 "Do you want to choose a new name?"),
3092 docstring const title = (kind == LV_VC_RENAME) ?
3093 _("Rename document?") : _("Copy document?");
3094 docstring const button = (kind == LV_VC_RENAME) ?
3095 _("&Rename") : _("&Copy");
3096 int const ret = Alert::prompt(title, text, 0, 1,
3097 button, _("&Cancel"));
3099 case 0: return renameBuffer(b, docstring(), kind);
3100 case 1: return false;
3105 docstring text = bformat(_("The document %1$s "
3106 "already exists.\n\n"
3107 "Do you want to overwrite that document?"),
3109 int const ret = Alert::prompt(_("Overwrite document?"),
3110 text, 0, 2, _("&Overwrite"),
3111 _("&Rename"), _("&Cancel"));
3114 case 1: return renameBuffer(b, docstring(), kind);
3115 case 2: return false;
3121 case LV_VC_RENAME: {
3122 string msg = b.lyxvc().rename(fname);
3125 message(from_utf8(msg));
3129 string msg = b.lyxvc().copy(fname);
3132 message(from_utf8(msg));
3136 case LV_WRITE_AS_TEMPLATE:
3139 // LyXVC created the file already in case of LV_VC_RENAME or
3140 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3141 // relative paths of included stuff right if we moved e.g. from
3142 // /a/b.lyx to /a/c/b.lyx.
3144 bool const saved = saveBuffer(b, fname);
3151 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3153 FileName fname = b.fileName();
3155 FileDialog dlg(qt_("Choose a filename to export the document as"));
3156 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3159 QString const anyformat = qt_("Guess from extension (*.*)");
3162 vector<Format const *> export_formats;
3163 for (Format const & f : theFormats())
3164 if (f.documentFormat())
3165 export_formats.push_back(&f);
3166 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3167 map<QString, string> fmap;
3170 for (Format const * f : export_formats) {
3171 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3172 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3174 from_ascii(f->extension())));
3175 types << loc_filter;
3176 fmap[loc_filter] = f->name();
3177 if (from_ascii(f->name()) == iformat) {
3178 filter = loc_filter;
3179 ext = f->extension();
3182 string ofname = fname.onlyFileName();
3184 ofname = support::changeExtension(ofname, ext);
3185 FileDialog::Result result =
3186 dlg.save(toqstr(fname.onlyPath().absFileName()),
3190 if (result.first != FileDialog::Chosen)
3194 fname.set(fromqstr(result.second));
3195 if (filter == anyformat)
3196 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3198 fmt_name = fmap[filter];
3199 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3200 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3202 if (fmt_name.empty() || fname.empty())
3205 // fname is now the new Buffer location.
3206 if (fname.exists()) {
3207 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3208 docstring text = bformat(_("The document %1$s already "
3209 "exists.\n\nDo you want to "
3210 "overwrite that document?"),
3212 int const ret = Alert::prompt(_("Overwrite document?"),
3213 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3216 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3217 case 2: return false;
3221 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3224 return dr.dispatched();
3228 bool GuiView::saveBuffer(Buffer & b)
3230 return saveBuffer(b, FileName());
3234 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3236 if (workArea(b) && workArea(b)->inDialogMode())
3239 if (fn.empty() && b.isUnnamed())
3240 return renameBuffer(b, docstring());
3242 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3244 theSession().lastFiles().add(b.fileName());
3245 theSession().writeFile();
3249 // Switch to this Buffer.
3252 // FIXME: we don't tell the user *WHY* the save failed !!
3253 docstring const file = makeDisplayPath(b.absFileName(), 30);
3254 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3255 "Do you want to rename the document and "
3256 "try again?"), file);
3257 int const ret = Alert::prompt(_("Rename and save?"),
3258 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3261 if (!renameBuffer(b, docstring()))
3270 return saveBuffer(b, fn);
3274 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3276 return closeWorkArea(wa, false);
3280 // We only want to close the buffer if it is not visible in other workareas
3281 // of the same view, nor in other views, and if this is not a child
3282 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3284 Buffer & buf = wa->bufferView().buffer();
3286 bool last_wa = d.countWorkAreasOf(buf) == 1
3287 && !inOtherView(buf) && !buf.parent();
3289 bool close_buffer = last_wa;
3292 if (lyxrc.close_buffer_with_last_view == "yes")
3294 else if (lyxrc.close_buffer_with_last_view == "no")
3295 close_buffer = false;
3298 if (buf.isUnnamed())
3299 file = from_utf8(buf.fileName().onlyFileName());
3301 file = buf.fileName().displayName(30);
3302 docstring const text = bformat(
3303 _("Last view on document %1$s is being closed.\n"
3304 "Would you like to close or hide the document?\n"
3306 "Hidden documents can be displayed back through\n"
3307 "the menu: View->Hidden->...\n"
3309 "To remove this question, set your preference in:\n"
3310 " Tools->Preferences->Look&Feel->UserInterface\n"
3312 int ret = Alert::prompt(_("Close or hide document?"),
3313 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3316 close_buffer = (ret == 0);
3320 return closeWorkArea(wa, close_buffer);
3324 bool GuiView::closeBuffer()
3326 GuiWorkArea * wa = currentMainWorkArea();
3327 // coverity complained about this
3328 // it seems unnecessary, but perhaps is worth the check
3329 LASSERT(wa, return false);
3331 setCurrentWorkArea(wa);
3332 Buffer & buf = wa->bufferView().buffer();
3333 return closeWorkArea(wa, !buf.parent());
3337 void GuiView::writeSession() const {
3338 GuiWorkArea const * active_wa = currentMainWorkArea();
3339 for (int i = 0; i < d.splitter_->count(); ++i) {
3340 TabWorkArea * twa = d.tabWorkArea(i);
3341 for (int j = 0; j < twa->count(); ++j) {
3342 GuiWorkArea * wa = twa->workArea(j);
3343 Buffer & buf = wa->bufferView().buffer();
3344 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3350 bool GuiView::closeBufferAll()
3353 for (auto & buf : theBufferList()) {
3354 if (!saveBufferIfNeeded(*buf, false)) {
3355 // Closing has been cancelled, so abort.
3360 // Close the workareas in all other views
3361 QList<int> const ids = guiApp->viewIds();
3362 for (int i = 0; i != ids.size(); ++i) {
3363 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3367 // Close our own workareas
3368 if (!closeWorkAreaAll())
3375 bool GuiView::closeWorkAreaAll()
3377 setCurrentWorkArea(currentMainWorkArea());
3379 // We might be in a situation that there is still a tabWorkArea, but
3380 // there are no tabs anymore. This can happen when we get here after a
3381 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3382 // many TabWorkArea's have no documents anymore.
3385 // We have to call count() each time, because it can happen that
3386 // more than one splitter will disappear in one iteration (bug 5998).
3387 while (d.splitter_->count() > empty_twa) {
3388 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3390 if (twa->count() == 0)
3393 setCurrentWorkArea(twa->currentWorkArea());
3394 if (!closeTabWorkArea(twa))
3402 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3407 Buffer & buf = wa->bufferView().buffer();
3409 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3410 Alert::warning(_("Close document"),
3411 _("Document could not be closed because it is being processed by LyX."));
3416 return closeBuffer(buf);
3418 if (!inMultiTabs(wa))
3419 if (!saveBufferIfNeeded(buf, true))
3427 bool GuiView::closeBuffer(Buffer & buf)
3429 bool success = true;
3430 for (Buffer * child_buf : buf.getChildren()) {
3431 if (theBufferList().isOthersChild(&buf, child_buf)) {
3432 child_buf->setParent(nullptr);
3436 // FIXME: should we look in other tabworkareas?
3437 // ANSWER: I don't think so. I've tested, and if the child is
3438 // open in some other window, it closes without a problem.
3439 GuiWorkArea * child_wa = workArea(*child_buf);
3442 // If we are in a close_event all children will be closed in some time,
3443 // so no need to do it here. This will ensure that the children end up
3444 // in the session file in the correct order. If we close the master
3445 // buffer, we can close or release the child buffers here too.
3447 success = closeWorkArea(child_wa, true);
3451 // In this case the child buffer is open but hidden.
3452 // Even in this case, children can be dirty (e.g.,
3453 // after a label change in the master, see #11405).
3454 // Therefore, check this
3455 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3456 // If we are in a close_event all children will be closed in some time,
3457 // so no need to do it here. This will ensure that the children end up
3458 // in the session file in the correct order. If we close the master
3459 // buffer, we can close or release the child buffers here too.
3462 // Save dirty buffers also if closing_!
3463 if (saveBufferIfNeeded(*child_buf, false)) {
3464 child_buf->removeAutosaveFile();
3465 theBufferList().release(child_buf);
3467 // Saving of dirty children has been cancelled.
3468 // Cancel the whole process.
3475 // goto bookmark to update bookmark pit.
3476 // FIXME: we should update only the bookmarks related to this buffer!
3477 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3478 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3479 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3480 guiApp->gotoBookmark(i, false, false);
3482 if (saveBufferIfNeeded(buf, false)) {
3483 buf.removeAutosaveFile();
3484 theBufferList().release(&buf);
3488 // open all children again to avoid a crash because of dangling
3489 // pointers (bug 6603)
3495 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3497 while (twa == d.currentTabWorkArea()) {
3498 twa->setCurrentIndex(twa->count() - 1);
3500 GuiWorkArea * wa = twa->currentWorkArea();
3501 Buffer & b = wa->bufferView().buffer();
3503 // We only want to close the buffer if the same buffer is not visible
3504 // in another view, and if this is not a child and if we are closing
3505 // a view (not a tabgroup).
3506 bool const close_buffer =
3507 !inOtherView(b) && !b.parent() && closing_;
3509 if (!closeWorkArea(wa, close_buffer))
3516 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3518 if (buf.isClean() || buf.paragraphs().empty())
3521 // Switch to this Buffer.
3527 if (buf.isUnnamed()) {
3528 file = from_utf8(buf.fileName().onlyFileName());
3531 FileName filename = buf.fileName();
3533 file = filename.displayName(30);
3534 exists = filename.exists();
3537 // Bring this window to top before asking questions.
3542 if (hiding && buf.isUnnamed()) {
3543 docstring const text = bformat(_("The document %1$s has not been "
3544 "saved yet.\n\nDo you want to save "
3545 "the document?"), file);
3546 ret = Alert::prompt(_("Save new document?"),
3547 text, 0, 1, _("&Save"), _("&Cancel"));
3551 docstring const text = exists ?
3552 bformat(_("The document %1$s has unsaved changes."
3553 "\n\nDo you want to save the document or "
3554 "discard the changes?"), file) :
3555 bformat(_("The document %1$s has not been saved yet."
3556 "\n\nDo you want to save the document or "
3557 "discard it entirely?"), file);
3558 docstring const title = exists ?
3559 _("Save changed document?") : _("Save document?");
3560 ret = Alert::prompt(title, text, 0, 2,
3561 _("&Save"), _("&Discard"), _("&Cancel"));
3566 if (!saveBuffer(buf))
3570 // If we crash after this we could have no autosave file
3571 // but I guess this is really improbable (Jug).
3572 // Sometimes improbable things happen:
3573 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3574 // buf.removeAutosaveFile();
3576 // revert all changes
3587 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3589 Buffer & buf = wa->bufferView().buffer();
3591 for (int i = 0; i != d.splitter_->count(); ++i) {
3592 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3593 if (wa_ && wa_ != wa)
3596 return inOtherView(buf);
3600 bool GuiView::inOtherView(Buffer & buf)
3602 QList<int> const ids = guiApp->viewIds();
3604 for (int i = 0; i != ids.size(); ++i) {
3608 if (guiApp->view(ids[i]).workArea(buf))
3615 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3617 if (!documentBufferView())
3620 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3621 Buffer * const curbuf = &documentBufferView()->buffer();
3622 int nwa = twa->count();
3623 for (int i = 0; i < nwa; ++i) {
3624 if (&workArea(i)->bufferView().buffer() == curbuf) {
3626 if (np == NEXTBUFFER)
3627 next_index = (i == nwa - 1 ? 0 : i + 1);
3629 next_index = (i == 0 ? nwa - 1 : i - 1);
3631 twa->moveTab(i, next_index);
3633 setBuffer(&workArea(next_index)->bufferView().buffer());
3641 /// make sure the document is saved
3642 static bool ensureBufferClean(Buffer * buffer)
3644 LASSERT(buffer, return false);
3645 if (buffer->isClean() && !buffer->isUnnamed())
3648 docstring const file = buffer->fileName().displayName(30);
3651 if (!buffer->isUnnamed()) {
3652 text = bformat(_("The document %1$s has unsaved "
3653 "changes.\n\nDo you want to save "
3654 "the document?"), file);
3655 title = _("Save changed document?");
3658 text = bformat(_("The document %1$s has not been "
3659 "saved yet.\n\nDo you want to save "
3660 "the document?"), file);
3661 title = _("Save new document?");
3663 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3666 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3668 return buffer->isClean() && !buffer->isUnnamed();
3672 bool GuiView::reloadBuffer(Buffer & buf)
3674 currentBufferView()->cursor().reset();
3675 Buffer::ReadStatus status = buf.reload();
3676 return status == Buffer::ReadSuccess;
3680 void GuiView::checkExternallyModifiedBuffers()
3682 for (Buffer * buf : theBufferList()) {
3683 if (buf->fileName().exists() && buf->isChecksumModified()) {
3684 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3685 " Reload now? Any local changes will be lost."),
3686 from_utf8(buf->absFileName()));
3687 int const ret = Alert::prompt(_("Reload externally changed document?"),
3688 text, 0, 1, _("&Reload"), _("&Cancel"));
3696 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3698 Buffer * buffer = documentBufferView()
3699 ? &(documentBufferView()->buffer()) : nullptr;
3701 switch (cmd.action()) {
3702 case LFUN_VC_REGISTER:
3703 if (!buffer || !ensureBufferClean(buffer))
3705 if (!buffer->lyxvc().inUse()) {
3706 if (buffer->lyxvc().registrer()) {
3707 reloadBuffer(*buffer);
3708 dr.clearMessageUpdate();
3713 case LFUN_VC_RENAME:
3714 case LFUN_VC_COPY: {
3715 if (!buffer || !ensureBufferClean(buffer))
3717 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3718 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3719 // Some changes are not yet committed.
3720 // We test here and not in getStatus(), since
3721 // this test is expensive.
3723 LyXVC::CommandResult ret =
3724 buffer->lyxvc().checkIn(log);
3726 if (ret == LyXVC::ErrorCommand ||
3727 ret == LyXVC::VCSuccess)
3728 reloadBuffer(*buffer);
3729 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3730 frontend::Alert::error(
3731 _("Revision control error."),
3732 _("Document could not be checked in."));
3736 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3737 LV_VC_RENAME : LV_VC_COPY;
3738 renameBuffer(*buffer, cmd.argument(), kind);
3743 case LFUN_VC_CHECK_IN:
3744 if (!buffer || !ensureBufferClean(buffer))
3746 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3748 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3750 // Only skip reloading if the checkin was cancelled or
3751 // an error occurred before the real checkin VCS command
3752 // was executed, since the VCS might have changed the
3753 // file even if it could not checkin successfully.
3754 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3755 reloadBuffer(*buffer);
3759 case LFUN_VC_CHECK_OUT:
3760 if (!buffer || !ensureBufferClean(buffer))
3762 if (buffer->lyxvc().inUse()) {
3763 dr.setMessage(buffer->lyxvc().checkOut());
3764 reloadBuffer(*buffer);
3768 case LFUN_VC_LOCKING_TOGGLE:
3769 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3771 if (buffer->lyxvc().inUse()) {
3772 string res = buffer->lyxvc().lockingToggle();
3774 frontend::Alert::error(_("Revision control error."),
3775 _("Error when setting the locking property."));
3778 reloadBuffer(*buffer);
3783 case LFUN_VC_REVERT:
3786 if (buffer->lyxvc().revert()) {
3787 reloadBuffer(*buffer);
3788 dr.clearMessageUpdate();
3792 case LFUN_VC_UNDO_LAST:
3795 buffer->lyxvc().undoLast();
3796 reloadBuffer(*buffer);
3797 dr.clearMessageUpdate();
3800 case LFUN_VC_REPO_UPDATE:
3803 if (ensureBufferClean(buffer)) {
3804 dr.setMessage(buffer->lyxvc().repoUpdate());
3805 checkExternallyModifiedBuffers();
3809 case LFUN_VC_COMMAND: {
3810 string flag = cmd.getArg(0);
3811 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3814 if (contains(flag, 'M')) {
3815 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3818 string path = cmd.getArg(1);
3819 if (contains(path, "$$p") && buffer)
3820 path = subst(path, "$$p", buffer->filePath());
3821 LYXERR(Debug::LYXVC, "Directory: " << path);
3823 if (!pp.isReadableDirectory()) {
3824 lyxerr << _("Directory is not accessible.") << endl;
3827 support::PathChanger p(pp);
3829 string command = cmd.getArg(2);
3830 if (command.empty())
3833 command = subst(command, "$$i", buffer->absFileName());
3834 command = subst(command, "$$p", buffer->filePath());
3836 command = subst(command, "$$m", to_utf8(message));
3837 LYXERR(Debug::LYXVC, "Command: " << command);
3839 one.startscript(Systemcall::Wait, command);
3843 if (contains(flag, 'I'))
3844 buffer->markDirty();
3845 if (contains(flag, 'R'))
3846 reloadBuffer(*buffer);
3851 case LFUN_VC_COMPARE: {
3852 if (cmd.argument().empty()) {
3853 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3859 string rev1 = cmd.getArg(0);
3863 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3866 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3867 f2 = buffer->absFileName();
3869 string rev2 = cmd.getArg(1);
3873 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3877 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3878 f1 << "\n" << f2 << "\n" );
3879 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3880 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3890 void GuiView::openChildDocument(string const & fname)
3892 LASSERT(documentBufferView(), return);
3893 Buffer & buffer = documentBufferView()->buffer();
3894 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3895 documentBufferView()->saveBookmark(false);
3896 Buffer * child = nullptr;
3897 if (theBufferList().exists(filename)) {
3898 child = theBufferList().getBuffer(filename);
3901 message(bformat(_("Opening child document %1$s..."),
3902 makeDisplayPath(filename.absFileName())));
3903 child = loadDocument(filename, false);
3905 // Set the parent name of the child document.
3906 // This makes insertion of citations and references in the child work,
3907 // when the target is in the parent or another child document.
3909 child->setParent(&buffer);
3913 bool GuiView::goToFileRow(string const & argument)
3917 size_t i = argument.find_last_of(' ');
3918 if (i != string::npos) {
3919 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3920 istringstream is(argument.substr(i + 1));
3925 if (i == string::npos) {
3926 LYXERR0("Wrong argument: " << argument);
3929 Buffer * buf = nullptr;
3930 string const realtmp = package().temp_dir().realPath();
3931 // We have to use os::path_prefix_is() here, instead of
3932 // simply prefixIs(), because the file name comes from
3933 // an external application and may need case adjustment.
3934 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3935 buf = theBufferList().getBufferFromTmp(file_name, true);
3936 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3937 << (buf ? " success" : " failed"));
3939 // Must replace extension of the file to be .lyx
3940 // and get full path
3941 FileName const s = fileSearch(string(),
3942 support::changeExtension(file_name, ".lyx"), "lyx");
3943 // Either change buffer or load the file
3944 if (theBufferList().exists(s))
3945 buf = theBufferList().getBuffer(s);
3946 else if (s.exists()) {
3947 buf = loadDocument(s);
3952 _("File does not exist: %1$s"),
3953 makeDisplayPath(file_name)));
3959 _("No buffer for file: %1$s."),
3960 makeDisplayPath(file_name))
3965 bool success = documentBufferView()->setCursorFromRow(row);
3967 LYXERR(Debug::LATEX,
3968 "setCursorFromRow: invalid position for row " << row);
3969 frontend::Alert::error(_("Inverse Search Failed"),
3970 _("Invalid position requested by inverse search.\n"
3971 "You may need to update the viewed document."));
3977 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3979 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3980 menu->exec(QCursor::pos());
3985 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3986 Buffer const * orig, Buffer * clone, string const & format)
3988 Buffer::ExportStatus const status = func(format);
3990 // the cloning operation will have produced a clone of the entire set of
3991 // documents, starting from the master. so we must delete those.
3992 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3994 busyBuffers.remove(orig);
3999 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4000 Buffer const * orig, Buffer * clone, string const & format)
4002 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4004 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4008 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4009 Buffer const * orig, Buffer * clone, string const & format)
4011 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4013 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4017 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4018 Buffer const * orig, Buffer * clone, string const & format)
4020 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4022 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4026 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4027 Buffer const * used_buffer,
4028 docstring const & msg,
4029 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4030 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4031 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4032 bool allow_async, bool use_tmpdir)
4037 string format = argument;
4039 format = used_buffer->params().getDefaultOutputFormat();
4040 processing_format = format;
4042 progress_->clearMessages();
4045 #if EXPORT_in_THREAD
4047 GuiViewPrivate::busyBuffers.insert(used_buffer);
4048 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4049 if (!cloned_buffer) {
4050 Alert::error(_("Export Error"),
4051 _("Error cloning the Buffer."));
4054 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4059 setPreviewFuture(f);
4060 last_export_format = used_buffer->params().bufferFormat();
4063 // We are asynchronous, so we don't know here anything about the success
4066 Buffer::ExportStatus status;
4068 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4069 } else if (previewFunc) {
4070 status = (used_buffer->*previewFunc)(format);
4073 handleExportStatus(gv_, status, format);
4075 return (status == Buffer::ExportSuccess
4076 || status == Buffer::PreviewSuccess);
4080 Buffer::ExportStatus status;
4082 status = (used_buffer->*syncFunc)(format, true);
4083 } else if (previewFunc) {
4084 status = (used_buffer->*previewFunc)(format);
4087 handleExportStatus(gv_, status, format);
4089 return (status == Buffer::ExportSuccess
4090 || status == Buffer::PreviewSuccess);
4094 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4096 BufferView * bv = currentBufferView();
4097 LASSERT(bv, return);
4099 // Let the current BufferView dispatch its own actions.
4100 bv->dispatch(cmd, dr);
4101 if (dr.dispatched()) {
4102 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4103 updateDialog("document", "");
4107 // Try with the document BufferView dispatch if any.
4108 BufferView * doc_bv = documentBufferView();
4109 if (doc_bv && doc_bv != bv) {
4110 doc_bv->dispatch(cmd, dr);
4111 if (dr.dispatched()) {
4112 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4113 updateDialog("document", "");
4118 // Then let the current Cursor dispatch its own actions.
4119 bv->cursor().dispatch(cmd);
4121 // update completion. We do it here and not in
4122 // processKeySym to avoid another redraw just for a
4123 // changed inline completion
4124 if (cmd.origin() == FuncRequest::KEYBOARD) {
4125 if (cmd.action() == LFUN_SELF_INSERT
4126 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4127 updateCompletion(bv->cursor(), true, true);
4128 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4129 updateCompletion(bv->cursor(), false, true);
4131 updateCompletion(bv->cursor(), false, false);
4134 dr = bv->cursor().result();
4138 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4140 BufferView * bv = currentBufferView();
4141 // By default we won't need any update.
4142 dr.screenUpdate(Update::None);
4143 // assume cmd will be dispatched
4144 dr.dispatched(true);
4146 Buffer * doc_buffer = documentBufferView()
4147 ? &(documentBufferView()->buffer()) : nullptr;
4149 if (cmd.origin() == FuncRequest::TOC) {
4150 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4151 toc->doDispatch(bv->cursor(), cmd, dr);
4155 string const argument = to_utf8(cmd.argument());
4157 switch(cmd.action()) {
4158 case LFUN_BUFFER_CHILD_OPEN:
4159 openChildDocument(to_utf8(cmd.argument()));
4162 case LFUN_BUFFER_IMPORT:
4163 importDocument(to_utf8(cmd.argument()));
4166 case LFUN_MASTER_BUFFER_EXPORT:
4168 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4170 case LFUN_BUFFER_EXPORT: {
4173 // GCC only sees strfwd.h when building merged
4174 if (::lyx::operator==(cmd.argument(), "custom")) {
4175 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4176 // so the following test should not be needed.
4177 // In principle, we could try to switch to such a view...
4178 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4179 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4183 string const dest = cmd.getArg(1);
4184 FileName target_dir;
4185 if (!dest.empty() && FileName::isAbsolute(dest))
4186 target_dir = FileName(support::onlyPath(dest));
4188 target_dir = doc_buffer->fileName().onlyPath();
4190 string const format = (argument.empty() || argument == "default") ?
4191 doc_buffer->params().getDefaultOutputFormat() : argument;
4193 if ((dest.empty() && doc_buffer->isUnnamed())
4194 || !target_dir.isDirWritable()) {
4195 exportBufferAs(*doc_buffer, from_utf8(format));
4198 /* TODO/Review: Is it a problem to also export the children?
4199 See the update_unincluded flag */
4200 d.asyncBufferProcessing(format,
4203 &GuiViewPrivate::exportAndDestroy,
4205 nullptr, cmd.allowAsync());
4206 // TODO Inform user about success
4210 case LFUN_BUFFER_EXPORT_AS: {
4211 LASSERT(doc_buffer, break);
4212 docstring f = cmd.argument();
4214 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4215 exportBufferAs(*doc_buffer, f);
4219 case LFUN_BUFFER_UPDATE: {
4220 d.asyncBufferProcessing(argument,
4223 &GuiViewPrivate::compileAndDestroy,
4225 nullptr, cmd.allowAsync(), true);
4228 case LFUN_BUFFER_VIEW: {
4229 d.asyncBufferProcessing(argument,
4231 _("Previewing ..."),
4232 &GuiViewPrivate::previewAndDestroy,
4234 &Buffer::preview, cmd.allowAsync());
4237 case LFUN_MASTER_BUFFER_UPDATE: {
4238 d.asyncBufferProcessing(argument,
4239 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4241 &GuiViewPrivate::compileAndDestroy,
4243 nullptr, cmd.allowAsync(), true);
4246 case LFUN_MASTER_BUFFER_VIEW: {
4247 d.asyncBufferProcessing(argument,
4248 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4250 &GuiViewPrivate::previewAndDestroy,
4251 nullptr, &Buffer::preview, cmd.allowAsync());
4254 case LFUN_EXPORT_CANCEL: {
4255 Systemcall::killscript();
4258 case LFUN_BUFFER_SWITCH: {
4259 string const file_name = to_utf8(cmd.argument());
4260 if (!FileName::isAbsolute(file_name)) {
4262 dr.setMessage(_("Absolute filename expected."));
4266 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4269 dr.setMessage(_("Document not loaded"));
4273 // Do we open or switch to the buffer in this view ?
4274 if (workArea(*buffer)
4275 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4280 // Look for the buffer in other views
4281 QList<int> const ids = guiApp->viewIds();
4283 for (; i != ids.size(); ++i) {
4284 GuiView & gv = guiApp->view(ids[i]);
4285 if (gv.workArea(*buffer)) {
4287 gv.activateWindow();
4289 gv.setBuffer(buffer);
4294 // If necessary, open a new window as a last resort
4295 if (i == ids.size()) {
4296 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4302 case LFUN_BUFFER_NEXT:
4303 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4306 case LFUN_BUFFER_MOVE_NEXT:
4307 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4310 case LFUN_BUFFER_PREVIOUS:
4311 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4314 case LFUN_BUFFER_MOVE_PREVIOUS:
4315 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4318 case LFUN_BUFFER_CHKTEX:
4319 LASSERT(doc_buffer, break);
4320 doc_buffer->runChktex();
4323 case LFUN_COMMAND_EXECUTE: {
4324 command_execute_ = true;
4325 minibuffer_focus_ = true;
4328 case LFUN_DROP_LAYOUTS_CHOICE:
4329 d.layout_->showPopup();
4332 case LFUN_MENU_OPEN:
4333 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4334 menu->exec(QCursor::pos());
4337 case LFUN_FILE_INSERT: {
4338 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4339 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4340 dr.forceBufferUpdate();
4341 dr.screenUpdate(Update::Force);
4346 case LFUN_FILE_INSERT_PLAINTEXT:
4347 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4348 string const fname = to_utf8(cmd.argument());
4349 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4350 dr.setMessage(_("Absolute filename expected."));
4354 FileName filename(fname);
4355 if (fname.empty()) {
4356 FileDialog dlg(qt_("Select file to insert"));
4358 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4359 QStringList(qt_("All Files (*)")));
4361 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4362 dr.setMessage(_("Canceled."));
4366 filename.set(fromqstr(result.second));
4370 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4371 bv->dispatch(new_cmd, dr);
4376 case LFUN_BUFFER_RELOAD: {
4377 LASSERT(doc_buffer, break);
4380 bool drop = (cmd.argument() == "dump");
4383 if (!drop && !doc_buffer->isClean()) {
4384 docstring const file =
4385 makeDisplayPath(doc_buffer->absFileName(), 20);
4386 if (doc_buffer->notifiesExternalModification()) {
4387 docstring text = _("The current version will be lost. "
4388 "Are you sure you want to load the version on disk "
4389 "of the document %1$s?");
4390 ret = Alert::prompt(_("Reload saved document?"),
4391 bformat(text, file), 1, 1,
4392 _("&Reload"), _("&Cancel"));
4394 docstring text = _("Any changes will be lost. "
4395 "Are you sure you want to revert to the saved version "
4396 "of the document %1$s?");
4397 ret = Alert::prompt(_("Revert to saved document?"),
4398 bformat(text, file), 1, 1,
4399 _("&Revert"), _("&Cancel"));
4404 doc_buffer->markClean();
4405 reloadBuffer(*doc_buffer);
4406 dr.forceBufferUpdate();
4411 case LFUN_BUFFER_RESET_EXPORT:
4412 LASSERT(doc_buffer, break);
4413 doc_buffer->requireFreshStart(true);
4414 dr.setMessage(_("Buffer export reset."));
4417 case LFUN_BUFFER_WRITE:
4418 LASSERT(doc_buffer, break);
4419 saveBuffer(*doc_buffer);
4422 case LFUN_BUFFER_WRITE_AS:
4423 LASSERT(doc_buffer, break);
4424 renameBuffer(*doc_buffer, cmd.argument());
4427 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4428 LASSERT(doc_buffer, break);
4429 renameBuffer(*doc_buffer, cmd.argument(),
4430 LV_WRITE_AS_TEMPLATE);
4433 case LFUN_BUFFER_WRITE_ALL: {
4434 Buffer * first = theBufferList().first();
4437 message(_("Saving all documents..."));
4438 // We cannot use a for loop as the buffer list cycles.
4441 if (!b->isClean()) {
4443 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4445 b = theBufferList().next(b);
4446 } while (b != first);
4447 dr.setMessage(_("All documents saved."));
4451 case LFUN_MASTER_BUFFER_FORALL: {
4455 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4456 funcToRun.allowAsync(false);
4458 for (Buffer const * buf : doc_buffer->allRelatives()) {
4459 // Switch to other buffer view and resend cmd
4460 lyx::dispatch(FuncRequest(
4461 LFUN_BUFFER_SWITCH, buf->absFileName()));
4462 lyx::dispatch(funcToRun);
4465 lyx::dispatch(FuncRequest(
4466 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4470 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4471 LASSERT(doc_buffer, break);
4472 doc_buffer->clearExternalModification();
4475 case LFUN_BUFFER_CLOSE:
4479 case LFUN_BUFFER_CLOSE_ALL:
4483 case LFUN_DEVEL_MODE_TOGGLE:
4484 devel_mode_ = !devel_mode_;
4486 dr.setMessage(_("Developer mode is now enabled."));
4488 dr.setMessage(_("Developer mode is now disabled."));
4491 case LFUN_TOOLBAR_SET: {
4492 string const name = cmd.getArg(0);
4493 string const state = cmd.getArg(1);
4494 if (GuiToolbar * t = toolbar(name))
4499 case LFUN_TOOLBAR_TOGGLE: {
4500 string const name = cmd.getArg(0);
4501 if (GuiToolbar * t = toolbar(name))
4506 case LFUN_TOOLBAR_MOVABLE: {
4507 string const name = cmd.getArg(0);
4509 // toggle (all) toolbars movablility
4510 toolbarsMovable_ = !toolbarsMovable_;
4511 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4512 GuiToolbar * tb = toolbar(ti.name);
4513 if (tb && tb->isMovable() != toolbarsMovable_)
4514 // toggle toolbar movablity if it does not fit lock
4515 // (all) toolbars positions state silent = true, since
4516 // status bar notifications are slow
4519 if (toolbarsMovable_)
4520 dr.setMessage(_("Toolbars unlocked."));
4522 dr.setMessage(_("Toolbars locked."));
4523 } else if (GuiToolbar * tb = toolbar(name))
4524 // toggle current toolbar movablity
4526 // update lock (all) toolbars positions
4527 updateLockToolbars();
4531 case LFUN_ICON_SIZE: {
4532 QSize size = d.iconSize(cmd.argument());
4534 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4535 size.width(), size.height()));
4539 case LFUN_DIALOG_UPDATE: {
4540 string const name = to_utf8(cmd.argument());
4541 if (name == "prefs" || name == "document")
4542 updateDialog(name, string());
4543 else if (name == "paragraph")
4544 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4545 else if (currentBufferView()) {
4546 Inset * inset = currentBufferView()->editedInset(name);
4547 // Can only update a dialog connected to an existing inset
4549 // FIXME: get rid of this indirection; GuiView ask the inset
4550 // if he is kind enough to update itself...
4551 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4552 //FIXME: pass DispatchResult here?
4553 inset->dispatch(currentBufferView()->cursor(), fr);
4559 case LFUN_DIALOG_TOGGLE: {
4560 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4561 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4562 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4566 case LFUN_DIALOG_DISCONNECT_INSET:
4567 disconnectDialog(to_utf8(cmd.argument()));
4570 case LFUN_DIALOG_HIDE: {
4571 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4575 case LFUN_DIALOG_SHOW: {
4576 string const name = cmd.getArg(0);
4577 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4579 if (name == "latexlog") {
4580 // getStatus checks that
4581 LASSERT(doc_buffer, break);
4582 Buffer::LogType type;
4583 string const logfile = doc_buffer->logName(&type);
4585 case Buffer::latexlog:
4588 case Buffer::buildlog:
4589 sdata = "literate ";
4592 sdata += Lexer::quoteString(logfile);
4593 showDialog("log", sdata);
4594 } else if (name == "vclog") {
4595 // getStatus checks that
4596 LASSERT(doc_buffer, break);
4597 string const sdata2 = "vc " +
4598 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4599 showDialog("log", sdata2);
4600 } else if (name == "symbols") {
4601 sdata = bv->cursor().getEncoding()->name();
4603 showDialog("symbols", sdata);
4604 } else if (name == "findreplace") {
4605 sdata = to_utf8(bv->cursor().selectionAsString(false));
4606 showDialog(name, sdata);
4608 } else if (name == "prefs" && isFullScreen()) {
4609 lfunUiToggle("fullscreen");
4610 showDialog("prefs", sdata);
4612 showDialog(name, sdata);
4617 dr.setMessage(cmd.argument());
4620 case LFUN_UI_TOGGLE: {
4621 string arg = cmd.getArg(0);
4622 if (!lfunUiToggle(arg)) {
4623 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4624 dr.setMessage(bformat(msg, from_utf8(arg)));
4626 // Make sure the keyboard focus stays in the work area.
4631 case LFUN_VIEW_SPLIT: {
4632 LASSERT(doc_buffer, break);
4633 string const orientation = cmd.getArg(0);
4634 d.splitter_->setOrientation(orientation == "vertical"
4635 ? Qt::Vertical : Qt::Horizontal);
4636 TabWorkArea * twa = addTabWorkArea();
4637 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4638 setCurrentWorkArea(wa);
4641 case LFUN_TAB_GROUP_CLOSE:
4642 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4643 closeTabWorkArea(twa);
4644 d.current_work_area_ = nullptr;
4645 twa = d.currentTabWorkArea();
4646 // Switch to the next GuiWorkArea in the found TabWorkArea.
4648 // Make sure the work area is up to date.
4649 setCurrentWorkArea(twa->currentWorkArea());
4651 setCurrentWorkArea(nullptr);
4656 case LFUN_VIEW_CLOSE:
4657 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4658 closeWorkArea(twa->currentWorkArea());
4659 d.current_work_area_ = nullptr;
4660 twa = d.currentTabWorkArea();
4661 // Switch to the next GuiWorkArea in the found TabWorkArea.
4663 // Make sure the work area is up to date.
4664 setCurrentWorkArea(twa->currentWorkArea());
4666 setCurrentWorkArea(nullptr);
4671 case LFUN_COMPLETION_INLINE:
4672 if (d.current_work_area_)
4673 d.current_work_area_->completer().showInline();
4676 case LFUN_COMPLETION_POPUP:
4677 if (d.current_work_area_)
4678 d.current_work_area_->completer().showPopup();
4683 if (d.current_work_area_)
4684 d.current_work_area_->completer().tab();
4687 case LFUN_COMPLETION_CANCEL:
4688 if (d.current_work_area_) {
4689 if (d.current_work_area_->completer().popupVisible())
4690 d.current_work_area_->completer().hidePopup();
4692 d.current_work_area_->completer().hideInline();
4696 case LFUN_COMPLETION_ACCEPT:
4697 if (d.current_work_area_)
4698 d.current_work_area_->completer().activate();
4701 case LFUN_BUFFER_ZOOM_IN:
4702 case LFUN_BUFFER_ZOOM_OUT:
4703 case LFUN_BUFFER_ZOOM: {
4704 if (cmd.argument().empty()) {
4705 if (cmd.action() == LFUN_BUFFER_ZOOM)
4707 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4712 if (cmd.action() == LFUN_BUFFER_ZOOM)
4713 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4714 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4715 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4717 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4720 // Actual zoom value: default zoom + fractional extra value
4721 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4722 if (zoom < static_cast<int>(zoom_min_))
4725 setCurrentZoom(zoom);
4727 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4728 lyxrc.currentZoom, lyxrc.defaultZoom));
4730 guiApp->fontLoader().update();
4731 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4735 case LFUN_VC_REGISTER:
4736 case LFUN_VC_RENAME:
4738 case LFUN_VC_CHECK_IN:
4739 case LFUN_VC_CHECK_OUT:
4740 case LFUN_VC_REPO_UPDATE:
4741 case LFUN_VC_LOCKING_TOGGLE:
4742 case LFUN_VC_REVERT:
4743 case LFUN_VC_UNDO_LAST:
4744 case LFUN_VC_COMMAND:
4745 case LFUN_VC_COMPARE:
4746 dispatchVC(cmd, dr);
4749 case LFUN_SERVER_GOTO_FILE_ROW:
4750 if(goToFileRow(to_utf8(cmd.argument())))
4751 dr.screenUpdate(Update::Force | Update::FitCursor);
4754 case LFUN_LYX_ACTIVATE:
4758 case LFUN_WINDOW_RAISE:
4764 case LFUN_FORWARD_SEARCH: {
4765 // it seems safe to assume we have a document buffer, since
4766 // getStatus wants one.
4767 LASSERT(doc_buffer, break);
4768 Buffer const * doc_master = doc_buffer->masterBuffer();
4769 FileName const path(doc_master->temppath());
4770 string const texname = doc_master->isChild(doc_buffer)
4771 ? DocFileName(changeExtension(
4772 doc_buffer->absFileName(),
4773 "tex")).mangledFileName()
4774 : doc_buffer->latexName();
4775 string const fulltexname =
4776 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4777 string const mastername =
4778 removeExtension(doc_master->latexName());
4779 FileName const dviname(addName(path.absFileName(),
4780 addExtension(mastername, "dvi")));
4781 FileName const pdfname(addName(path.absFileName(),
4782 addExtension(mastername, "pdf")));
4783 bool const have_dvi = dviname.exists();
4784 bool const have_pdf = pdfname.exists();
4785 if (!have_dvi && !have_pdf) {
4786 dr.setMessage(_("Please, preview the document first."));
4789 string outname = dviname.onlyFileName();
4790 string command = lyxrc.forward_search_dvi;
4791 if (!have_dvi || (have_pdf &&
4792 pdfname.lastModified() > dviname.lastModified())) {
4793 outname = pdfname.onlyFileName();
4794 command = lyxrc.forward_search_pdf;
4797 DocIterator cur = bv->cursor();
4798 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4799 LYXERR(Debug::ACTION, "Forward search: row:" << row
4801 if (row == -1 || command.empty()) {
4802 dr.setMessage(_("Couldn't proceed."));
4805 string texrow = convert<string>(row);
4807 command = subst(command, "$$n", texrow);
4808 command = subst(command, "$$f", fulltexname);
4809 command = subst(command, "$$t", texname);
4810 command = subst(command, "$$o", outname);
4812 volatile PathChanger p(path);
4814 one.startscript(Systemcall::DontWait, command);
4818 case LFUN_SPELLING_CONTINUOUSLY:
4819 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4820 dr.screenUpdate(Update::Force);
4823 case LFUN_CITATION_OPEN: {
4825 if (theFormats().getFormat("pdf"))
4826 pdfv = theFormats().getFormat("pdf")->viewer();
4827 if (theFormats().getFormat("ps"))
4828 psv = theFormats().getFormat("ps")->viewer();
4829 frontend::showTarget(argument, pdfv, psv);
4834 // The LFUN must be for one of BufferView, Buffer or Cursor;
4836 dispatchToBufferView(cmd, dr);
4840 // Need to update bv because many LFUNs here might have destroyed it
4841 bv = currentBufferView();
4843 // Clear non-empty selections
4844 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4846 Cursor & cur = bv->cursor();
4847 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4848 cur.clearSelection();
4854 bool GuiView::lfunUiToggle(string const & ui_component)
4856 if (ui_component == "scrollbar") {
4857 // hide() is of no help
4858 if (d.current_work_area_->verticalScrollBarPolicy() ==
4859 Qt::ScrollBarAlwaysOff)
4861 d.current_work_area_->setVerticalScrollBarPolicy(
4862 Qt::ScrollBarAsNeeded);
4864 d.current_work_area_->setVerticalScrollBarPolicy(
4865 Qt::ScrollBarAlwaysOff);
4866 } else if (ui_component == "statusbar") {
4867 statusBar()->setVisible(!statusBar()->isVisible());
4868 } else if (ui_component == "menubar") {
4869 menuBar()->setVisible(!menuBar()->isVisible());
4870 } else if (ui_component == "zoom") {
4871 zoom_value_->setVisible(!zoom_value_->isVisible());
4872 } else if (ui_component == "zoomslider") {
4873 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4874 zoom_in_->setVisible(zoom_slider_->isVisible());
4875 zoom_out_->setVisible(zoom_slider_->isVisible());
4876 } else if (ui_component == "frame") {
4877 int const l = contentsMargins().left();
4879 //are the frames in default state?
4880 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4882 #if QT_VERSION > 0x050903
4883 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4885 setContentsMargins(-2, -2, -2, -2);
4887 #if QT_VERSION > 0x050903
4888 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4890 setContentsMargins(0, 0, 0, 0);
4893 if (ui_component == "fullscreen") {
4901 void GuiView::toggleFullScreen()
4903 setWindowState(windowState() ^ Qt::WindowFullScreen);
4907 Buffer const * GuiView::updateInset(Inset const * inset)
4912 Buffer const * inset_buffer = &(inset->buffer());
4914 for (int i = 0; i != d.splitter_->count(); ++i) {
4915 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4918 Buffer const * buffer = &(wa->bufferView().buffer());
4919 if (inset_buffer == buffer)
4920 wa->scheduleRedraw(true);
4922 return inset_buffer;
4926 void GuiView::restartCaret()
4928 /* When we move around, or type, it's nice to be able to see
4929 * the caret immediately after the keypress.
4931 if (d.current_work_area_)
4932 d.current_work_area_->startBlinkingCaret();
4934 // Take this occasion to update the other GUI elements.
4940 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4942 if (d.current_work_area_)
4943 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4948 // This list should be kept in sync with the list of insets in
4949 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4950 // dialog should have the same name as the inset.
4951 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4952 // docs in LyXAction.cpp.
4954 char const * const dialognames[] = {
4956 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4957 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4958 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4959 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4960 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4961 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4962 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4963 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4965 char const * const * const end_dialognames =
4966 dialognames + (sizeof(dialognames) / sizeof(char *));
4970 cmpCStr(char const * name) : name_(name) {}
4971 bool operator()(char const * other) {
4972 return strcmp(other, name_) == 0;
4979 bool isValidName(string const & name)
4981 return find_if(dialognames, end_dialognames,
4982 cmpCStr(name.c_str())) != end_dialognames;
4988 void GuiView::resetDialogs()
4990 // Make sure that no LFUN uses any GuiView.
4991 guiApp->setCurrentView(nullptr);
4995 constructToolbars();
4996 guiApp->menus().fillMenuBar(menuBar(), this, false);
4997 d.layout_->updateContents(true);
4998 // Now update controls with current buffer.
4999 guiApp->setCurrentView(this);
5005 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5007 for (QObject * child: widget->children()) {
5008 if (child->inherits("QGroupBox")) {
5009 QGroupBox * box = (QGroupBox*) child;
5012 flatGroupBoxes(child, flag);
5018 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5020 if (!isValidName(name))
5023 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5025 if (it != d.dialogs_.end()) {
5027 it->second->hideView();
5028 return it->second.get();
5031 Dialog * dialog = build(name);
5032 d.dialogs_[name].reset(dialog);
5033 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5034 // Force a uniform style for group boxes
5035 // On Mac non-flat works better, on Linux flat is standard
5036 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5038 if (lyxrc.allow_geometry_session)
5039 dialog->restoreSession();
5046 void GuiView::showDialog(string const & name, string const & sdata,
5049 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5053 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5059 const string name = fromqstr(qname);
5060 const string sdata = fromqstr(qdata);
5064 Dialog * dialog = findOrBuild(name, false);
5066 bool const visible = dialog->isVisibleView();
5067 dialog->showData(sdata);
5068 if (currentBufferView())
5069 currentBufferView()->editInset(name, inset);
5070 // We only set the focus to the new dialog if it was not yet
5071 // visible in order not to change the existing previous behaviour
5073 // activateWindow is needed for floating dockviews
5074 dialog->asQWidget()->raise();
5075 dialog->asQWidget()->activateWindow();
5076 if (dialog->wantInitialFocus())
5077 dialog->asQWidget()->setFocus();
5081 catch (ExceptionMessage const &) {
5089 bool GuiView::isDialogVisible(string const & name) const
5091 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5092 if (it == d.dialogs_.end())
5094 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5098 void GuiView::hideDialog(string const & name, Inset * inset)
5100 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5101 if (it == d.dialogs_.end())
5105 if (!currentBufferView())
5107 if (inset != currentBufferView()->editedInset(name))
5111 Dialog * const dialog = it->second.get();
5112 if (dialog->isVisibleView())
5114 if (currentBufferView())
5115 currentBufferView()->editInset(name, nullptr);
5119 void GuiView::disconnectDialog(string const & name)
5121 if (!isValidName(name))
5123 if (currentBufferView())
5124 currentBufferView()->editInset(name, nullptr);
5128 void GuiView::hideAll() const
5130 for(auto const & dlg_p : d.dialogs_)
5131 dlg_p.second->hideView();
5135 void GuiView::updateDialogs()
5137 for(auto const & dlg_p : d.dialogs_) {
5138 Dialog * dialog = dlg_p.second.get();
5140 if (dialog->needBufferOpen() && !documentBufferView())
5141 hideDialog(fromqstr(dialog->name()), nullptr);
5142 else if (dialog->isVisibleView())
5143 dialog->checkStatus();
5151 Dialog * GuiView::build(string const & name)
5153 return createDialog(*this, name);
5157 SEMenu::SEMenu(QWidget * parent)
5159 QAction * action = addAction(qt_("Disable Shell Escape"));
5160 connect(action, SIGNAL(triggered()),
5161 parent, SLOT(disableShellEscape()));
5165 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5167 if (event->button() == Qt::LeftButton) {
5172 } // namespace frontend
5175 #include "moc_GuiView.cpp"