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 for (QString const & seg : titlesegs) {
211 if (fm.width(seg) > wline)
212 wline = fm.width(seg);
214 // The longest line in the reference font (for English)
215 // is 180. Calculate scale factor from that.
216 double const wscale = wline > 0 ? (180.0 / wline) : 1;
217 // Now do the same for the height (necessary for condensed fonts)
218 double const hscale = (34.0 / hline);
219 // take the lower of the two scale factors.
220 double const scale = min(wscale, hscale);
221 // Now rescale. Also consider l7n's offset factor.
222 font.setPointSizeF(hfsize * scale * locscale);
225 pain.drawText(hrect, Qt::AlignLeft, htext);
226 setFocusPolicy(Qt::StrongFocus);
229 void paintEvent(QPaintEvent *) override
231 int const w = width_;
232 int const h = height_;
233 int const x = (width() - w) / 2;
234 int const y = (height() - h) / 2;
236 "widget pixel ratio: " << pixelRatio() <<
237 " splash pixel ratio: " << splashPixelRatio() <<
238 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
240 pain.drawPixmap(x, y, w, h, splash_);
243 void keyPressEvent(QKeyEvent * ev) override
246 setKeySymbol(&sym, ev);
248 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
260 /// Current ratio between physical pixels and device-independent pixels
261 double pixelRatio() const {
262 #if QT_VERSION >= 0x050000
263 return qt_scale_factor * devicePixelRatio();
269 qreal fontSize() const {
270 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
273 QPointF textPosition(bool const heading) const {
274 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
275 : QPointF(width_/2 - 18, height_/2 + 45);
278 QSize splashSize() const {
280 static_cast<unsigned int>(width_ * pixelRatio()),
281 static_cast<unsigned int>(height_ * pixelRatio()));
284 /// Ratio between physical pixels and device-independent pixels of splash image
285 double splashPixelRatio() const {
286 #if QT_VERSION >= 0x050000
287 return splash_.devicePixelRatio();
295 /// Toolbar store providing access to individual toolbars by name.
296 typedef map<string, GuiToolbar *> ToolbarMap;
298 typedef shared_ptr<Dialog> DialogPtr;
303 class GuiView::GuiViewPrivate
306 GuiViewPrivate(GuiViewPrivate const &);
307 void operator=(GuiViewPrivate const &);
309 GuiViewPrivate(GuiView * gv)
310 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
311 layout_(nullptr), autosave_timeout_(5000),
314 // hardcode here the platform specific icon size
315 smallIconSize = 16; // scaling problems
316 normalIconSize = 20; // ok, default if iconsize.png is missing
317 bigIconSize = 26; // better for some math icons
318 hugeIconSize = 32; // better for hires displays
321 // if it exists, use width of iconsize.png as normal size
322 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
323 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
325 QImage image(toqstr(fn.absFileName()));
326 if (image.width() < int(smallIconSize))
327 normalIconSize = smallIconSize;
328 else if (image.width() > int(giantIconSize))
329 normalIconSize = giantIconSize;
331 normalIconSize = image.width();
334 splitter_ = new QSplitter;
335 bg_widget_ = new BackgroundWidget(400, 250);
336 stack_widget_ = new QStackedWidget;
337 stack_widget_->addWidget(bg_widget_);
338 stack_widget_->addWidget(splitter_);
341 // TODO cleanup, remove the singleton, handle multiple Windows?
342 progress_ = ProgressInterface::instance();
343 if (!dynamic_cast<GuiProgress*>(progress_)) {
344 progress_ = new GuiProgress; // TODO who deletes it
345 ProgressInterface::setInstance(progress_);
348 dynamic_cast<GuiProgress*>(progress_),
349 SIGNAL(updateStatusBarMessage(QString const&)),
350 gv, SLOT(updateStatusBarMessage(QString const&)));
352 dynamic_cast<GuiProgress*>(progress_),
353 SIGNAL(clearMessageText()),
354 gv, SLOT(clearMessageText()));
361 delete stack_widget_;
366 stack_widget_->setCurrentWidget(bg_widget_);
367 bg_widget_->setUpdatesEnabled(true);
368 bg_widget_->setFocus();
371 int tabWorkAreaCount()
373 return splitter_->count();
376 TabWorkArea * tabWorkArea(int i)
378 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
381 TabWorkArea * currentTabWorkArea()
383 int areas = tabWorkAreaCount();
385 // The first TabWorkArea is always the first one, if any.
386 return tabWorkArea(0);
388 for (int i = 0; i != areas; ++i) {
389 TabWorkArea * twa = tabWorkArea(i);
390 if (current_main_work_area_ == twa->currentWorkArea())
394 // None has the focus so we just take the first one.
395 return tabWorkArea(0);
398 int countWorkAreasOf(Buffer & buf)
400 int areas = tabWorkAreaCount();
402 for (int i = 0; i != areas; ++i) {
403 TabWorkArea * twa = tabWorkArea(i);
404 if (twa->workArea(buf))
410 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
412 if (processing_thread_watcher_.isRunning()) {
413 // we prefer to cancel this preview in order to keep a snappy
417 processing_thread_watcher_.setFuture(f);
420 QSize iconSize(docstring const & icon_size)
423 if (icon_size == "small")
424 size = smallIconSize;
425 else if (icon_size == "normal")
426 size = normalIconSize;
427 else if (icon_size == "big")
429 else if (icon_size == "huge")
431 else if (icon_size == "giant")
432 size = giantIconSize;
434 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
436 if (size < smallIconSize)
437 size = smallIconSize;
439 return QSize(size, size);
442 QSize iconSize(QString const & icon_size)
444 return iconSize(qstring_to_ucs4(icon_size));
447 string & iconSize(QSize const & qsize)
449 LATTEST(qsize.width() == qsize.height());
451 static string icon_size;
453 unsigned int size = qsize.width();
455 if (size < smallIconSize)
456 size = smallIconSize;
458 if (size == smallIconSize)
460 else if (size == normalIconSize)
461 icon_size = "normal";
462 else if (size == bigIconSize)
464 else if (size == hugeIconSize)
466 else if (size == giantIconSize)
469 icon_size = convert<string>(size);
474 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
475 Buffer * buffer, string const & format);
476 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
483 static Buffer::ExportStatus runAndDestroy(const T& func,
484 Buffer const * orig, Buffer * buffer, string const & format);
486 // TODO syncFunc/previewFunc: use bind
487 bool asyncBufferProcessing(string const & argument,
488 Buffer const * used_buffer,
489 docstring const & msg,
490 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
491 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
492 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
493 bool allow_async, bool use_tmpdir = false);
495 QVector<GuiWorkArea*> guiWorkAreas();
499 GuiWorkArea * current_work_area_;
500 GuiWorkArea * current_main_work_area_;
501 QSplitter * splitter_;
502 QStackedWidget * stack_widget_;
503 BackgroundWidget * bg_widget_;
505 ToolbarMap toolbars_;
506 ProgressInterface* progress_;
507 /// The main layout box.
509 * \warning Don't Delete! The layout box is actually owned by
510 * whichever toolbar contains it. All the GuiView class needs is a
511 * means of accessing it.
513 * FIXME: replace that with a proper model so that we are not limited
514 * to only one dialog.
519 map<string, DialogPtr> dialogs_;
522 QTimer statusbar_timer_;
523 /// auto-saving of buffers
524 Timeout autosave_timeout_;
527 TocModels toc_models_;
530 QFutureWatcher<docstring> autosave_watcher_;
531 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
533 string last_export_format;
534 string processing_format;
536 static QSet<Buffer const *> busyBuffers;
538 unsigned int smallIconSize;
539 unsigned int normalIconSize;
540 unsigned int bigIconSize;
541 unsigned int hugeIconSize;
542 unsigned int giantIconSize;
544 /// flag against a race condition due to multiclicks, see bug #1119
548 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
551 GuiView::GuiView(int id)
552 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
553 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
556 connect(this, SIGNAL(bufferViewChanged()),
557 this, SLOT(onBufferViewChanged()));
559 // GuiToolbars *must* be initialised before the menu bar.
560 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
563 // set ourself as the current view. This is needed for the menu bar
564 // filling, at least for the static special menu item on Mac. Otherwise
565 // they are greyed out.
566 guiApp->setCurrentView(this);
568 // Fill up the menu bar.
569 guiApp->menus().fillMenuBar(menuBar(), this, true);
571 setCentralWidget(d.stack_widget_);
573 // Start autosave timer
574 if (lyxrc.autosave) {
575 // The connection is closed when this is destroyed.
576 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
577 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
578 d.autosave_timeout_.start();
580 connect(&d.statusbar_timer_, SIGNAL(timeout()),
581 this, SLOT(clearMessage()));
583 // We don't want to keep the window in memory if it is closed.
584 setAttribute(Qt::WA_DeleteOnClose, true);
586 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
587 // QIcon::fromTheme was introduced in Qt 4.6
588 #if (QT_VERSION >= 0x040600)
589 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
590 // since the icon is provided in the application bundle. We use a themed
591 // version when available and use the bundled one as fallback.
592 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
594 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
600 // use tabbed dock area for multiple docks
601 // (such as "source" and "messages")
602 setDockOptions(QMainWindow::ForceTabbedDocks);
605 // use document mode tabs on docks
606 setDocumentMode(true);
610 setAcceptDrops(true);
612 // add busy indicator to statusbar
613 search_mode mode = theGuiApp()->imageSearchMode();
614 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
615 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
616 statusBar()->addPermanentWidget(busySVG);
617 // make busy indicator square with 5px margins
618 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
621 connect(&d.processing_thread_watcher_, SIGNAL(started()),
622 busySVG, SLOT(show()));
623 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
624 busySVG, SLOT(hide()));
625 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
627 QFontMetrics const fm(statusBar()->fontMetrics());
629 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
630 // Small size slider for macOS to prevent the status bar from enlarging
631 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
632 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
633 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
635 zoom_slider_->setFixedWidth(fm.width('x') * 15);
637 // Make the defaultZoom center
638 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
639 // Initialize proper zoom value
641 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
642 // Actual zoom value: default zoom + fractional offset
643 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
644 if (zoom < static_cast<int>(zoom_min_))
646 zoom_slider_->setValue(zoom);
647 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
649 // Buttons to change zoom stepwise
650 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
651 QSize s(fm.horizontalAdvance('+'), fm.height());
653 QSize s(fm.width('+'), fm.height());
655 zoom_in_ = new GuiClickableLabel(statusBar());
656 zoom_in_->setText("+");
657 zoom_in_->setFixedSize(s);
658 zoom_in_->setAlignment(Qt::AlignCenter);
659 zoom_out_ = new GuiClickableLabel(statusBar());
660 zoom_out_->setText(QString(QChar(0x2212)));
661 zoom_out_->setFixedSize(s);
662 zoom_out_->setAlignment(Qt::AlignCenter);
664 statusBar()->addPermanentWidget(zoom_out_);
665 zoom_out_->setEnabled(currentBufferView());
666 statusBar()->addPermanentWidget(zoom_slider_);
667 zoom_slider_->setEnabled(currentBufferView());
668 zoom_in_->setEnabled(currentBufferView());
669 statusBar()->addPermanentWidget(zoom_in_);
671 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
672 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
673 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
674 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
675 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
677 // QPalette palette = statusBar()->palette();
679 zoom_value_ = new QLabel(statusBar());
680 // zoom_value_->setPalette(palette);
681 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
682 zoom_value_->setFixedHeight(fm.height());
683 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
684 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
686 zoom_value_->setMinimumWidth(fm.width("444\%"));
688 zoom_value_->setAlignment(Qt::AlignCenter);
689 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
690 statusBar()->addPermanentWidget(zoom_value_);
691 zoom_value_->setEnabled(currentBufferView());
693 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
694 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
695 this, SLOT(showZoomContextMenu()));
697 int const iconheight = max(int(d.normalIconSize), fm.height());
698 QSize const iconsize(iconheight, iconheight);
700 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
701 shell_escape_ = new QLabel(statusBar());
702 shell_escape_->setPixmap(shellescape);
703 shell_escape_->setAlignment(Qt::AlignCenter);
704 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
705 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
706 "external commands for this document. "
707 "Right click to change."));
708 SEMenu * menu = new SEMenu(this);
709 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
710 menu, SLOT(showMenu(QPoint)));
711 shell_escape_->hide();
712 statusBar()->addPermanentWidget(shell_escape_);
714 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
715 read_only_ = new QLabel(statusBar());
716 read_only_->setPixmap(readonly);
717 read_only_->setAlignment(Qt::AlignCenter);
719 statusBar()->addPermanentWidget(read_only_);
721 version_control_ = new QLabel(statusBar());
722 version_control_->setAlignment(Qt::AlignCenter);
723 version_control_->setFrameStyle(QFrame::StyledPanel);
724 version_control_->hide();
725 statusBar()->addPermanentWidget(version_control_);
727 statusBar()->setSizeGripEnabled(true);
730 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
731 SLOT(autoSaveThreadFinished()));
733 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
734 SLOT(processingThreadStarted()));
735 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
736 SLOT(processingThreadFinished()));
738 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
739 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
741 // set custom application bars context menu, e.g. tool bar and menu bar
742 setContextMenuPolicy(Qt::CustomContextMenu);
743 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
744 SLOT(toolBarPopup(const QPoint &)));
746 // Forbid too small unresizable window because it can happen
747 // with some window manager under X11.
748 setMinimumSize(300, 200);
750 if (lyxrc.allow_geometry_session) {
751 // Now take care of session management.
756 // no session handling, default to a sane size.
757 setGeometry(50, 50, 690, 510);
760 // clear session data if any.
761 settings.remove("views");
771 void GuiView::disableShellEscape()
773 BufferView * bv = documentBufferView();
776 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
777 bv->buffer().params().shell_escape = false;
778 bv->processUpdateFlags(Update::Force);
782 void GuiView::checkCancelBackground()
784 docstring const ttl = _("Cancel Export?");
785 docstring const msg = _("Do you want to cancel the background export process?");
787 Alert::prompt(ttl, msg, 1, 1,
788 _("&Cancel export"), _("Co&ntinue"));
790 Systemcall::killscript();
794 void GuiView::zoomSliderMoved(int value)
797 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
798 scheduleRedrawWorkAreas();
799 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
803 void GuiView::zoomValueChanged(int value)
805 if (value != lyxrc.currentZoom)
806 zoomSliderMoved(value);
810 void GuiView::zoomInPressed()
813 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
814 scheduleRedrawWorkAreas();
818 void GuiView::zoomOutPressed()
821 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
822 scheduleRedrawWorkAreas();
826 void GuiView::showZoomContextMenu()
828 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
831 menu->exec(QCursor::pos());
835 void GuiView::scheduleRedrawWorkAreas()
837 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
838 TabWorkArea* ta = d.tabWorkArea(i);
839 for (int u = 0; u < ta->count(); u++) {
840 ta->workArea(u)->scheduleRedraw(true);
846 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
848 QVector<GuiWorkArea*> areas;
849 for (int i = 0; i < tabWorkAreaCount(); i++) {
850 TabWorkArea* ta = tabWorkArea(i);
851 for (int u = 0; u < ta->count(); u++) {
852 areas << ta->workArea(u);
858 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
859 string const & format)
861 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
864 case Buffer::ExportSuccess:
865 msg = bformat(_("Successful export to format: %1$s"), fmt);
867 case Buffer::ExportCancel:
868 msg = _("Document export cancelled.");
870 case Buffer::ExportError:
871 case Buffer::ExportNoPathToFormat:
872 case Buffer::ExportTexPathHasSpaces:
873 case Buffer::ExportConverterError:
874 msg = bformat(_("Error while exporting format: %1$s"), fmt);
876 case Buffer::PreviewSuccess:
877 msg = bformat(_("Successful preview of format: %1$s"), fmt);
879 case Buffer::PreviewError:
880 msg = bformat(_("Error while previewing format: %1$s"), fmt);
882 case Buffer::ExportKilled:
883 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
890 void GuiView::processingThreadStarted()
895 void GuiView::processingThreadFinished()
897 QFutureWatcher<Buffer::ExportStatus> const * watcher =
898 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
900 Buffer::ExportStatus const status = watcher->result();
901 handleExportStatus(this, status, d.processing_format);
904 BufferView const * const bv = currentBufferView();
905 if (bv && !bv->buffer().errorList("Export").empty()) {
910 bool const error = (status != Buffer::ExportSuccess &&
911 status != Buffer::PreviewSuccess &&
912 status != Buffer::ExportCancel);
914 ErrorList & el = bv->buffer().errorList(d.last_export_format);
915 // at this point, we do not know if buffer-view or
916 // master-buffer-view was called. If there was an export error,
917 // and the current buffer's error log is empty, we guess that
918 // it must be master-buffer-view that was called so we set
920 errors(d.last_export_format, el.empty());
925 void GuiView::autoSaveThreadFinished()
927 QFutureWatcher<docstring> const * watcher =
928 static_cast<QFutureWatcher<docstring> const *>(sender());
929 message(watcher->result());
934 void GuiView::saveLayout() const
937 settings.setValue("zoom_ratio", zoom_ratio_);
938 settings.setValue("devel_mode", devel_mode_);
939 settings.beginGroup("views");
940 settings.beginGroup(QString::number(id_));
941 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
942 settings.setValue("pos", pos());
943 settings.setValue("size", size());
945 settings.setValue("geometry", saveGeometry());
946 settings.setValue("layout", saveState(0));
947 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
948 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
949 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
953 void GuiView::saveUISettings() const
957 // Save the toolbar private states
958 for (auto const & tb_p : d.toolbars_)
959 tb_p.second->saveSession(settings);
960 // Now take care of all other dialogs
961 for (auto const & dlg_p : d.dialogs_)
962 dlg_p.second->saveSession(settings);
966 void GuiView::setCurrentZoom(const int v)
968 lyxrc.currentZoom = v;
969 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
970 Q_EMIT currentZoomChanged(v);
974 bool GuiView::restoreLayout()
977 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
978 // Actual zoom value: default zoom + fractional offset
979 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
980 if (zoom < static_cast<int>(zoom_min_))
982 setCurrentZoom(zoom);
983 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
984 settings.beginGroup("views");
985 settings.beginGroup(QString::number(id_));
986 QString const icon_key = "icon_size";
987 if (!settings.contains(icon_key))
990 //code below is skipped when when ~/.config/LyX is (re)created
991 setIconSize(d.iconSize(settings.value(icon_key).toString()));
993 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
995 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
996 zoom_slider_->setVisible(show_zoom_slider);
997 zoom_in_->setVisible(show_zoom_slider);
998 zoom_out_->setVisible(show_zoom_slider);
1000 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1001 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1002 QSize size = settings.value("size", QSize(690, 510)).toSize();
1006 // Work-around for bug #6034: the window ends up in an undetermined
1007 // state when trying to restore a maximized window when it is
1008 // already maximized.
1009 if (!(windowState() & Qt::WindowMaximized))
1010 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1011 setGeometry(50, 50, 690, 510);
1014 // Make sure layout is correctly oriented.
1015 setLayoutDirection(qApp->layoutDirection());
1017 // Allow the toc and view-source dock widget to be restored if needed.
1019 if ((dialog = findOrBuild("toc", true)))
1020 // see bug 5082. At least setup title and enabled state.
1021 // Visibility will be adjusted by restoreState below.
1022 dialog->prepareView();
1023 if ((dialog = findOrBuild("view-source", true)))
1024 dialog->prepareView();
1025 if ((dialog = findOrBuild("progress", true)))
1026 dialog->prepareView();
1028 if (!restoreState(settings.value("layout").toByteArray(), 0))
1031 // init the toolbars that have not been restored
1032 for (auto const & tb_p : guiApp->toolbars()) {
1033 GuiToolbar * tb = toolbar(tb_p.name);
1034 if (tb && !tb->isRestored())
1035 initToolbar(tb_p.name);
1038 // update lock (all) toolbars positions
1039 updateLockToolbars();
1046 GuiToolbar * GuiView::toolbar(string const & name)
1048 ToolbarMap::iterator it = d.toolbars_.find(name);
1049 if (it != d.toolbars_.end())
1052 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1057 void GuiView::updateLockToolbars()
1059 toolbarsMovable_ = false;
1060 for (ToolbarInfo const & info : guiApp->toolbars()) {
1061 GuiToolbar * tb = toolbar(info.name);
1062 if (tb && tb->isMovable())
1063 toolbarsMovable_ = true;
1065 #if QT_VERSION >= 0x050200
1066 // set unified mac toolbars only when not movable as recommended:
1067 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1068 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1073 void GuiView::constructToolbars()
1075 for (auto const & tb_p : d.toolbars_)
1077 d.toolbars_.clear();
1079 // I don't like doing this here, but the standard toolbar
1080 // destroys this object when it's destroyed itself (vfr)
1081 d.layout_ = new LayoutBox(*this);
1082 d.stack_widget_->addWidget(d.layout_);
1083 d.layout_->move(0,0);
1085 // extracts the toolbars from the backend
1086 for (ToolbarInfo const & inf : guiApp->toolbars())
1087 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1089 DynamicMenuButton::resetIconCache();
1093 void GuiView::initToolbars()
1095 // extracts the toolbars from the backend
1096 for (ToolbarInfo const & inf : guiApp->toolbars())
1097 initToolbar(inf.name);
1101 void GuiView::initToolbar(string const & name)
1103 GuiToolbar * tb = toolbar(name);
1106 int const visibility = guiApp->toolbars().defaultVisibility(name);
1107 bool newline = !(visibility & Toolbars::SAMEROW);
1108 tb->setVisible(false);
1109 tb->setVisibility(visibility);
1111 if (visibility & Toolbars::TOP) {
1113 addToolBarBreak(Qt::TopToolBarArea);
1114 addToolBar(Qt::TopToolBarArea, tb);
1117 if (visibility & Toolbars::BOTTOM) {
1119 addToolBarBreak(Qt::BottomToolBarArea);
1120 addToolBar(Qt::BottomToolBarArea, tb);
1123 if (visibility & Toolbars::LEFT) {
1125 addToolBarBreak(Qt::LeftToolBarArea);
1126 addToolBar(Qt::LeftToolBarArea, tb);
1129 if (visibility & Toolbars::RIGHT) {
1131 addToolBarBreak(Qt::RightToolBarArea);
1132 addToolBar(Qt::RightToolBarArea, tb);
1135 if (visibility & Toolbars::ON)
1136 tb->setVisible(true);
1138 tb->setMovable(true);
1142 TocModels & GuiView::tocModels()
1144 return d.toc_models_;
1148 void GuiView::setFocus()
1150 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1151 QMainWindow::setFocus();
1155 bool GuiView::hasFocus() const
1157 if (currentWorkArea())
1158 return currentWorkArea()->hasFocus();
1159 if (currentMainWorkArea())
1160 return currentMainWorkArea()->hasFocus();
1161 return d.bg_widget_->hasFocus();
1165 void GuiView::focusInEvent(QFocusEvent * e)
1167 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1168 QMainWindow::focusInEvent(e);
1169 // Make sure guiApp points to the correct view.
1170 guiApp->setCurrentView(this);
1171 if (currentWorkArea())
1172 currentWorkArea()->setFocus();
1173 else if (currentMainWorkArea())
1174 currentMainWorkArea()->setFocus();
1176 d.bg_widget_->setFocus();
1180 void GuiView::showEvent(QShowEvent * e)
1182 LYXERR(Debug::GUI, "Passed Geometry "
1183 << size().height() << "x" << size().width()
1184 << "+" << pos().x() << "+" << pos().y());
1186 if (d.splitter_->count() == 0)
1187 // No work area, switch to the background widget.
1191 QMainWindow::showEvent(e);
1195 bool GuiView::closeScheduled()
1202 bool GuiView::prepareAllBuffersForLogout()
1204 Buffer * first = theBufferList().first();
1208 // First, iterate over all buffers and ask the users if unsaved
1209 // changes should be saved.
1210 // We cannot use a for loop as the buffer list cycles.
1213 if (!saveBufferIfNeeded(*b, false))
1215 b = theBufferList().next(b);
1216 } while (b != first);
1218 // Next, save session state
1219 // When a view/window was closed before without quitting LyX, there
1220 // are already entries in the lastOpened list.
1221 theSession().lastOpened().clear();
1228 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1229 ** is responsibility of the container (e.g., dialog)
1231 void GuiView::closeEvent(QCloseEvent * close_event)
1233 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1235 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1236 Alert::warning(_("Exit LyX"),
1237 _("LyX could not be closed because documents are being processed by LyX."));
1238 close_event->setAccepted(false);
1242 // If the user pressed the x (so we didn't call closeView
1243 // programmatically), we want to clear all existing entries.
1245 theSession().lastOpened().clear();
1250 // it can happen that this event arrives without selecting the view,
1251 // e.g. when clicking the close button on a background window.
1253 if (!closeWorkAreaAll()) {
1255 close_event->ignore();
1259 // Make sure that nothing will use this to be closed View.
1260 guiApp->unregisterView(this);
1262 if (isFullScreen()) {
1263 // Switch off fullscreen before closing.
1268 // Make sure the timer time out will not trigger a statusbar update.
1269 d.statusbar_timer_.stop();
1271 // Saving fullscreen requires additional tweaks in the toolbar code.
1272 // It wouldn't also work under linux natively.
1273 if (lyxrc.allow_geometry_session) {
1278 close_event->accept();
1282 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1284 if (event->mimeData()->hasUrls())
1286 /// \todo Ask lyx-devel is this is enough:
1287 /// if (event->mimeData()->hasFormat("text/plain"))
1288 /// event->acceptProposedAction();
1292 void GuiView::dropEvent(QDropEvent * event)
1294 QList<QUrl> files = event->mimeData()->urls();
1295 if (files.isEmpty())
1298 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1299 for (int i = 0; i != files.size(); ++i) {
1300 string const file = os::internal_path(fromqstr(
1301 files.at(i).toLocalFile()));
1305 string const ext = support::getExtension(file);
1306 vector<const Format *> found_formats;
1308 // Find all formats that have the correct extension.
1309 for (const Format * fmt : theConverters().importableFormats())
1310 if (fmt->hasExtension(ext))
1311 found_formats.push_back(fmt);
1314 if (!found_formats.empty()) {
1315 if (found_formats.size() > 1) {
1316 //FIXME: show a dialog to choose the correct importable format
1317 LYXERR(Debug::FILES,
1318 "Multiple importable formats found, selecting first");
1320 string const arg = found_formats[0]->name() + " " + file;
1321 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1324 //FIXME: do we have to explicitly check whether it's a lyx file?
1325 LYXERR(Debug::FILES,
1326 "No formats found, trying to open it as a lyx file");
1327 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1329 // add the functions to the queue
1330 guiApp->addToFuncRequestQueue(cmd);
1333 // now process the collected functions. We perform the events
1334 // asynchronously. This prevents potential problems in case the
1335 // BufferView is closed within an event.
1336 guiApp->processFuncRequestQueueAsync();
1340 void GuiView::message(docstring const & str)
1342 if (ForkedProcess::iAmAChild())
1345 // call is moved to GUI-thread by GuiProgress
1346 d.progress_->appendMessage(toqstr(str));
1350 void GuiView::clearMessageText()
1352 message(docstring());
1356 void GuiView::updateStatusBarMessage(QString const & str)
1358 statusBar()->showMessage(str);
1359 d.statusbar_timer_.stop();
1360 d.statusbar_timer_.start(3000);
1364 void GuiView::clearMessage()
1366 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1367 // the hasFocus function mostly returns false, even if the focus is on
1368 // a workarea in this view.
1372 d.statusbar_timer_.stop();
1376 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1378 if (wa != d.current_work_area_
1379 || wa->bufferView().buffer().isInternal())
1381 Buffer const & buf = wa->bufferView().buffer();
1382 // Set the windows title
1383 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1384 if (buf.notifiesExternalModification()) {
1385 title = bformat(_("%1$s (modified externally)"), title);
1386 // If the external modification status has changed, then maybe the status of
1387 // buffer-save has changed too.
1391 title += from_ascii(" - LyX");
1393 setWindowTitle(toqstr(title));
1394 // Sets the path for the window: this is used by OSX to
1395 // allow a context click on the title bar showing a menu
1396 // with the path up to the file
1397 setWindowFilePath(toqstr(buf.absFileName()));
1398 // Tell Qt whether the current document is changed
1399 setWindowModified(!buf.isClean());
1401 if (buf.params().shell_escape)
1402 shell_escape_->show();
1404 shell_escape_->hide();
1406 if (buf.hasReadonlyFlag())
1411 if (buf.lyxvc().inUse()) {
1412 version_control_->show();
1413 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1415 version_control_->hide();
1419 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1421 if (d.current_work_area_)
1422 // disconnect the current work area from all slots
1423 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1425 disconnectBufferView();
1426 connectBufferView(wa->bufferView());
1427 connectBuffer(wa->bufferView().buffer());
1428 d.current_work_area_ = wa;
1429 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1430 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1431 QObject::connect(wa, SIGNAL(busy(bool)),
1432 this, SLOT(setBusy(bool)));
1433 // connection of a signal to a signal
1434 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1435 this, SIGNAL(bufferViewChanged()));
1436 Q_EMIT updateWindowTitle(wa);
1437 Q_EMIT bufferViewChanged();
1441 void GuiView::onBufferViewChanged()
1444 // Buffer-dependent dialogs must be updated. This is done here because
1445 // some dialogs require buffer()->text.
1447 zoom_slider_->setEnabled(currentBufferView());
1448 zoom_value_->setEnabled(currentBufferView());
1449 zoom_in_->setEnabled(currentBufferView());
1450 zoom_out_->setEnabled(currentBufferView());
1454 void GuiView::on_lastWorkAreaRemoved()
1457 // We already are in a close event. Nothing more to do.
1460 if (d.splitter_->count() > 1)
1461 // We have a splitter so don't close anything.
1464 // Reset and updates the dialogs.
1465 Q_EMIT bufferViewChanged();
1470 if (lyxrc.open_buffers_in_tabs)
1471 // Nothing more to do, the window should stay open.
1474 if (guiApp->viewIds().size() > 1) {
1480 // On Mac we also close the last window because the application stay
1481 // resident in memory. On other platforms we don't close the last
1482 // window because this would quit the application.
1488 void GuiView::updateStatusBar()
1490 // let the user see the explicit message
1491 if (d.statusbar_timer_.isActive())
1498 void GuiView::showMessage()
1502 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1503 if (msg.isEmpty()) {
1504 BufferView const * bv = currentBufferView();
1506 msg = toqstr(bv->cursor().currentState(devel_mode_));
1508 msg = qt_("Welcome to LyX!");
1510 statusBar()->showMessage(msg);
1514 bool GuiView::event(QEvent * e)
1518 // Useful debug code:
1519 //case QEvent::ActivationChange:
1520 //case QEvent::WindowDeactivate:
1521 //case QEvent::Paint:
1522 //case QEvent::Enter:
1523 //case QEvent::Leave:
1524 //case QEvent::HoverEnter:
1525 //case QEvent::HoverLeave:
1526 //case QEvent::HoverMove:
1527 //case QEvent::StatusTip:
1528 //case QEvent::DragEnter:
1529 //case QEvent::DragLeave:
1530 //case QEvent::Drop:
1533 case QEvent::WindowStateChange: {
1534 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1535 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1536 bool result = QMainWindow::event(e);
1537 bool nfstate = (windowState() & Qt::WindowFullScreen);
1538 if (!ofstate && nfstate) {
1539 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1540 // switch to full-screen state
1541 if (lyxrc.full_screen_statusbar)
1542 statusBar()->hide();
1543 if (lyxrc.full_screen_menubar)
1545 if (lyxrc.full_screen_toolbars) {
1546 for (auto const & tb_p : d.toolbars_)
1547 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1548 tb_p.second->hide();
1550 for (int i = 0; i != d.splitter_->count(); ++i)
1551 d.tabWorkArea(i)->setFullScreen(true);
1552 #if QT_VERSION > 0x050903
1553 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1554 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1556 setContentsMargins(-2, -2, -2, -2);
1558 hideDialogs("prefs", nullptr);
1559 } else if (ofstate && !nfstate) {
1560 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1561 // switch back from full-screen state
1562 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1563 statusBar()->show();
1564 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1566 if (lyxrc.full_screen_toolbars) {
1567 for (auto const & tb_p : d.toolbars_)
1568 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1569 tb_p.second->show();
1572 for (int i = 0; i != d.splitter_->count(); ++i)
1573 d.tabWorkArea(i)->setFullScreen(false);
1574 #if QT_VERSION > 0x050903
1575 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1577 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 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2360 } else if (cmd.argument() == "zoomslider") {
2361 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2363 flag.setOnOff(isFullScreen());
2366 case LFUN_DIALOG_DISCONNECT_INSET:
2369 case LFUN_DIALOG_HIDE:
2370 // FIXME: should we check if the dialog is shown?
2373 case LFUN_DIALOG_TOGGLE:
2374 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2377 case LFUN_DIALOG_SHOW: {
2378 string const name = cmd.getArg(0);
2380 enable = name == "aboutlyx"
2381 || name == "file" //FIXME: should be removed.
2382 || name == "lyxfiles"
2384 || name == "texinfo"
2385 || name == "progress"
2386 || name == "compare";
2387 else if (name == "character" || name == "symbols"
2388 || name == "mathdelimiter" || name == "mathmatrix") {
2389 if (!buf || buf->isReadonly())
2392 Cursor const & cur = currentBufferView()->cursor();
2393 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2396 else if (name == "latexlog")
2397 enable = FileName(doc_buffer->logName()).isReadableFile();
2398 else if (name == "spellchecker")
2399 enable = theSpellChecker()
2400 && !doc_buffer->text().empty();
2401 else if (name == "vclog")
2402 enable = doc_buffer->lyxvc().inUse();
2406 case LFUN_DIALOG_UPDATE: {
2407 string const name = cmd.getArg(0);
2409 enable = name == "prefs";
2413 case LFUN_COMMAND_EXECUTE:
2415 case LFUN_MENU_OPEN:
2416 // Nothing to check.
2419 case LFUN_COMPLETION_INLINE:
2420 if (!d.current_work_area_
2421 || !d.current_work_area_->completer().inlinePossible(
2422 currentBufferView()->cursor()))
2426 case LFUN_COMPLETION_POPUP:
2427 if (!d.current_work_area_
2428 || !d.current_work_area_->completer().popupPossible(
2429 currentBufferView()->cursor()))
2434 if (!d.current_work_area_
2435 || !d.current_work_area_->completer().inlinePossible(
2436 currentBufferView()->cursor()))
2440 case LFUN_COMPLETION_ACCEPT:
2441 if (!d.current_work_area_
2442 || (!d.current_work_area_->completer().popupVisible()
2443 && !d.current_work_area_->completer().inlineVisible()
2444 && !d.current_work_area_->completer().completionAvailable()))
2448 case LFUN_COMPLETION_CANCEL:
2449 if (!d.current_work_area_
2450 || (!d.current_work_area_->completer().popupVisible()
2451 && !d.current_work_area_->completer().inlineVisible()))
2455 case LFUN_BUFFER_ZOOM_OUT:
2456 case LFUN_BUFFER_ZOOM_IN: {
2457 // only diff between these two is that the default for ZOOM_OUT
2459 bool const neg_zoom =
2460 convert<int>(cmd.argument()) < 0 ||
2461 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2462 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2463 docstring const msg =
2464 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2468 enable = doc_buffer;
2472 case LFUN_BUFFER_ZOOM: {
2473 bool const less_than_min_zoom =
2474 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2475 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2476 docstring const msg =
2477 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2480 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2483 enable = doc_buffer;
2487 case LFUN_BUFFER_MOVE_NEXT:
2488 case LFUN_BUFFER_MOVE_PREVIOUS:
2489 // we do not cycle when moving
2490 case LFUN_BUFFER_NEXT:
2491 case LFUN_BUFFER_PREVIOUS:
2492 // because we cycle, it doesn't matter whether on first or last
2493 enable = (d.currentTabWorkArea()->count() > 1);
2495 case LFUN_BUFFER_SWITCH:
2496 // toggle on the current buffer, but do not toggle off
2497 // the other ones (is that a good idea?)
2499 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2500 flag.setOnOff(true);
2503 case LFUN_VC_REGISTER:
2504 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2506 case LFUN_VC_RENAME:
2507 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2510 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2512 case LFUN_VC_CHECK_IN:
2513 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2515 case LFUN_VC_CHECK_OUT:
2516 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2518 case LFUN_VC_LOCKING_TOGGLE:
2519 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2520 && doc_buffer->lyxvc().lockingToggleEnabled();
2521 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2523 case LFUN_VC_REVERT:
2524 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2525 && !doc_buffer->hasReadonlyFlag();
2527 case LFUN_VC_UNDO_LAST:
2528 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2530 case LFUN_VC_REPO_UPDATE:
2531 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2533 case LFUN_VC_COMMAND: {
2534 if (cmd.argument().empty())
2536 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2540 case LFUN_VC_COMPARE:
2541 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2544 case LFUN_SERVER_GOTO_FILE_ROW:
2545 case LFUN_LYX_ACTIVATE:
2546 case LFUN_WINDOW_RAISE:
2548 case LFUN_FORWARD_SEARCH:
2549 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2552 case LFUN_FILE_INSERT_PLAINTEXT:
2553 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2554 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2557 case LFUN_SPELLING_CONTINUOUSLY:
2558 flag.setOnOff(lyxrc.spellcheck_continuously);
2561 case LFUN_CITATION_OPEN:
2570 flag.setEnabled(false);
2576 static FileName selectTemplateFile()
2578 FileDialog dlg(qt_("Select template file"));
2579 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2580 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2582 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2583 QStringList(qt_("LyX Documents (*.lyx)")));
2585 if (result.first == FileDialog::Later)
2587 if (result.second.isEmpty())
2589 return FileName(fromqstr(result.second));
2593 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2597 Buffer * newBuffer = nullptr;
2599 newBuffer = checkAndLoadLyXFile(filename);
2600 } catch (ExceptionMessage const &) {
2607 message(_("Document not loaded."));
2611 setBuffer(newBuffer);
2612 newBuffer->errors("Parse");
2615 theSession().lastFiles().add(filename);
2616 theSession().writeFile();
2623 void GuiView::openDocument(string const & fname)
2625 string initpath = lyxrc.document_path;
2627 if (documentBufferView()) {
2628 string const trypath = documentBufferView()->buffer().filePath();
2629 // If directory is writeable, use this as default.
2630 if (FileName(trypath).isDirWritable())
2636 if (fname.empty()) {
2637 FileDialog dlg(qt_("Select document to open"));
2638 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2639 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2641 QStringList const filter({
2642 qt_("LyX Documents (*.lyx)"),
2643 qt_("LyX Document Backups (*.lyx~)"),
2644 qt_("All Files (*.*)")
2646 FileDialog::Result result =
2647 dlg.open(toqstr(initpath), filter);
2649 if (result.first == FileDialog::Later)
2652 filename = fromqstr(result.second);
2654 // check selected filename
2655 if (filename.empty()) {
2656 message(_("Canceled."));
2662 // get absolute path of file and add ".lyx" to the filename if
2664 FileName const fullname =
2665 fileSearch(string(), filename, "lyx", support::may_not_exist);
2666 if (!fullname.empty())
2667 filename = fullname.absFileName();
2669 if (!fullname.onlyPath().isDirectory()) {
2670 Alert::warning(_("Invalid filename"),
2671 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2672 from_utf8(fullname.absFileName())));
2676 // if the file doesn't exist and isn't already open (bug 6645),
2677 // let the user create one
2678 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2679 !LyXVC::file_not_found_hook(fullname)) {
2680 // the user specifically chose this name. Believe him.
2681 Buffer * const b = newFile(filename, string(), true);
2687 docstring const disp_fn = makeDisplayPath(filename);
2688 message(bformat(_("Opening document %1$s..."), disp_fn));
2691 Buffer * buf = loadDocument(fullname);
2693 str2 = bformat(_("Document %1$s opened."), disp_fn);
2694 if (buf->lyxvc().inUse())
2695 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2696 " " + _("Version control detected.");
2698 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2703 // FIXME: clean that
2704 static bool import(GuiView * lv, FileName const & filename,
2705 string const & format, ErrorList & errorList)
2707 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2709 string loader_format;
2710 vector<string> loaders = theConverters().loaders();
2711 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2712 for (string const & loader : loaders) {
2713 if (!theConverters().isReachable(format, loader))
2716 string const tofile =
2717 support::changeExtension(filename.absFileName(),
2718 theFormats().extension(loader));
2719 if (theConverters().convert(nullptr, filename, FileName(tofile),
2720 filename, format, loader, errorList) != Converters::SUCCESS)
2722 loader_format = loader;
2725 if (loader_format.empty()) {
2726 frontend::Alert::error(_("Couldn't import file"),
2727 bformat(_("No information for importing the format %1$s."),
2728 translateIfPossible(theFormats().prettyName(format))));
2732 loader_format = format;
2734 if (loader_format == "lyx") {
2735 Buffer * buf = lv->loadDocument(lyxfile);
2739 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2743 bool as_paragraphs = loader_format == "textparagraph";
2744 string filename2 = (loader_format == format) ? filename.absFileName()
2745 : support::changeExtension(filename.absFileName(),
2746 theFormats().extension(loader_format));
2747 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2749 guiApp->setCurrentView(lv);
2750 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2757 void GuiView::importDocument(string const & argument)
2760 string filename = split(argument, format, ' ');
2762 LYXERR(Debug::INFO, format << " file: " << filename);
2764 // need user interaction
2765 if (filename.empty()) {
2766 string initpath = lyxrc.document_path;
2767 if (documentBufferView()) {
2768 string const trypath = documentBufferView()->buffer().filePath();
2769 // If directory is writeable, use this as default.
2770 if (FileName(trypath).isDirWritable())
2774 docstring const text = bformat(_("Select %1$s file to import"),
2775 translateIfPossible(theFormats().prettyName(format)));
2777 FileDialog dlg(toqstr(text));
2778 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2779 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2781 docstring filter = translateIfPossible(theFormats().prettyName(format));
2784 filter += from_utf8(theFormats().extensions(format));
2787 FileDialog::Result result =
2788 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2790 if (result.first == FileDialog::Later)
2793 filename = fromqstr(result.second);
2795 // check selected filename
2796 if (filename.empty())
2797 message(_("Canceled."));
2800 if (filename.empty())
2803 // get absolute path of file
2804 FileName const fullname(support::makeAbsPath(filename));
2806 // Can happen if the user entered a path into the dialog
2808 if (fullname.onlyFileName().empty()) {
2809 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2810 "Aborting import."),
2811 from_utf8(fullname.absFileName()));
2812 frontend::Alert::error(_("File name error"), msg);
2813 message(_("Canceled."));
2818 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2820 // Check if the document already is open
2821 Buffer * buf = theBufferList().getBuffer(lyxfile);
2824 if (!closeBuffer()) {
2825 message(_("Canceled."));
2830 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2832 // if the file exists already, and we didn't do
2833 // -i lyx thefile.lyx, warn
2834 if (lyxfile.exists() && fullname != lyxfile) {
2836 docstring text = bformat(_("The document %1$s already exists.\n\n"
2837 "Do you want to overwrite that document?"), displaypath);
2838 int const ret = Alert::prompt(_("Overwrite document?"),
2839 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2842 message(_("Canceled."));
2847 message(bformat(_("Importing %1$s..."), displaypath));
2848 ErrorList errorList;
2849 if (import(this, fullname, format, errorList))
2850 message(_("imported."));
2852 message(_("file not imported!"));
2854 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2858 void GuiView::newDocument(string const & filename, string templatefile,
2861 FileName initpath(lyxrc.document_path);
2862 if (documentBufferView()) {
2863 FileName const trypath(documentBufferView()->buffer().filePath());
2864 // If directory is writeable, use this as default.
2865 if (trypath.isDirWritable())
2869 if (from_template) {
2870 if (templatefile.empty())
2871 templatefile = selectTemplateFile().absFileName();
2872 if (templatefile.empty())
2877 if (filename.empty())
2878 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2880 b = newFile(filename, templatefile, true);
2885 // If no new document could be created, it is unsure
2886 // whether there is a valid BufferView.
2887 if (currentBufferView())
2888 // Ensure the cursor is correctly positioned on screen.
2889 currentBufferView()->showCursor();
2893 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2895 BufferView * bv = documentBufferView();
2900 FileName filename(to_utf8(fname));
2901 if (filename.empty()) {
2902 // Launch a file browser
2904 string initpath = lyxrc.document_path;
2905 string const trypath = bv->buffer().filePath();
2906 // If directory is writeable, use this as default.
2907 if (FileName(trypath).isDirWritable())
2911 FileDialog dlg(qt_("Select LyX document to insert"));
2912 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2913 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2915 FileDialog::Result result = dlg.open(toqstr(initpath),
2916 QStringList(qt_("LyX Documents (*.lyx)")));
2918 if (result.first == FileDialog::Later)
2922 filename.set(fromqstr(result.second));
2924 // check selected filename
2925 if (filename.empty()) {
2926 // emit message signal.
2927 message(_("Canceled."));
2932 bv->insertLyXFile(filename, ignorelang);
2933 bv->buffer().errors("Parse");
2938 string const GuiView::getTemplatesPath(Buffer & b)
2940 // We start off with the user's templates path
2941 string result = addPath(package().user_support().absFileName(), "templates");
2942 // Check for the document language
2943 string const langcode = b.params().language->code();
2944 string const shortcode = langcode.substr(0, 2);
2945 if (!langcode.empty() && shortcode != "en") {
2946 string subpath = addPath(result, shortcode);
2947 string subpath_long = addPath(result, langcode);
2948 // If we have a subdirectory for the language already,
2950 FileName sp = FileName(subpath);
2951 if (sp.isDirectory())
2953 else if (FileName(subpath_long).isDirectory())
2954 result = subpath_long;
2956 // Ask whether we should create such a subdirectory
2957 docstring const text =
2958 bformat(_("It is suggested to save the template in a subdirectory\n"
2959 "appropriate to the document language (%1$s).\n"
2960 "This subdirectory does not exists yet.\n"
2961 "Do you want to create it?"),
2962 _(b.params().language->display()));
2963 if (Alert::prompt(_("Create Language Directory?"),
2964 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2965 // If the user agreed, we try to create it and report if this failed.
2966 if (!sp.createDirectory(0777))
2967 Alert::error(_("Subdirectory creation failed!"),
2968 _("Could not create subdirectory.\n"
2969 "The template will be saved in the parent directory."));
2975 // Do we have a layout category?
2976 string const cat = b.params().baseClass() ?
2977 b.params().baseClass()->category()
2980 string subpath = addPath(result, cat);
2981 // If we have a subdirectory for the category already,
2983 FileName sp = FileName(subpath);
2984 if (sp.isDirectory())
2987 // Ask whether we should create such a subdirectory
2988 docstring const text =
2989 bformat(_("It is suggested to save the template in a subdirectory\n"
2990 "appropriate to the layout category (%1$s).\n"
2991 "This subdirectory does not exists yet.\n"
2992 "Do you want to create it?"),
2994 if (Alert::prompt(_("Create Category Directory?"),
2995 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2996 // If the user agreed, we try to create it and report if this failed.
2997 if (!sp.createDirectory(0777))
2998 Alert::error(_("Subdirectory creation failed!"),
2999 _("Could not create subdirectory.\n"
3000 "The template will be saved in the parent directory."));
3010 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3012 FileName fname = b.fileName();
3013 FileName const oldname = fname;
3014 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3016 if (!newname.empty()) {
3019 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3021 fname = support::makeAbsPath(to_utf8(newname),
3022 oldname.onlyPath().absFileName());
3024 // Switch to this Buffer.
3027 // No argument? Ask user through dialog.
3029 QString const title = as_template ? qt_("Choose a filename to save template as")
3030 : qt_("Choose a filename to save document as");
3031 FileDialog dlg(title);
3032 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3033 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3035 if (!isLyXFileName(fname.absFileName()))
3036 fname.changeExtension(".lyx");
3038 string const path = as_template ?
3040 : fname.onlyPath().absFileName();
3041 FileDialog::Result result =
3042 dlg.save(toqstr(path),
3043 QStringList(qt_("LyX Documents (*.lyx)")),
3044 toqstr(fname.onlyFileName()));
3046 if (result.first == FileDialog::Later)
3049 fname.set(fromqstr(result.second));
3054 if (!isLyXFileName(fname.absFileName()))
3055 fname.changeExtension(".lyx");
3058 // fname is now the new Buffer location.
3060 // if there is already a Buffer open with this name, we do not want
3061 // to have another one. (the second test makes sure we're not just
3062 // trying to overwrite ourselves, which is fine.)
3063 if (theBufferList().exists(fname) && fname != oldname
3064 && theBufferList().getBuffer(fname) != &b) {
3065 docstring const text =
3066 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3067 "Please close it before attempting to overwrite it.\n"
3068 "Do you want to choose a new filename?"),
3069 from_utf8(fname.absFileName()));
3070 int const ret = Alert::prompt(_("Chosen File Already Open"),
3071 text, 0, 1, _("&Rename"), _("&Cancel"));
3073 case 0: return renameBuffer(b, docstring(), kind);
3074 case 1: return false;
3079 bool const existsLocal = fname.exists();
3080 bool const existsInVC = LyXVC::fileInVC(fname);
3081 if (existsLocal || existsInVC) {
3082 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3083 if (kind != LV_WRITE_AS && existsInVC) {
3084 // renaming to a name that is already in VC
3086 docstring text = bformat(_("The document %1$s "
3087 "is already registered.\n\n"
3088 "Do you want to choose a new name?"),
3090 docstring const title = (kind == LV_VC_RENAME) ?
3091 _("Rename document?") : _("Copy document?");
3092 docstring const button = (kind == LV_VC_RENAME) ?
3093 _("&Rename") : _("&Copy");
3094 int const ret = Alert::prompt(title, text, 0, 1,
3095 button, _("&Cancel"));
3097 case 0: return renameBuffer(b, docstring(), kind);
3098 case 1: return false;
3103 docstring text = bformat(_("The document %1$s "
3104 "already exists.\n\n"
3105 "Do you want to overwrite that document?"),
3107 int const ret = Alert::prompt(_("Overwrite document?"),
3108 text, 0, 2, _("&Overwrite"),
3109 _("&Rename"), _("&Cancel"));
3112 case 1: return renameBuffer(b, docstring(), kind);
3113 case 2: return false;
3119 case LV_VC_RENAME: {
3120 string msg = b.lyxvc().rename(fname);
3123 message(from_utf8(msg));
3127 string msg = b.lyxvc().copy(fname);
3130 message(from_utf8(msg));
3134 case LV_WRITE_AS_TEMPLATE:
3137 // LyXVC created the file already in case of LV_VC_RENAME or
3138 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3139 // relative paths of included stuff right if we moved e.g. from
3140 // /a/b.lyx to /a/c/b.lyx.
3142 bool const saved = saveBuffer(b, fname);
3149 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3151 FileName fname = b.fileName();
3153 FileDialog dlg(qt_("Choose a filename to export the document as"));
3154 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3157 QString const anyformat = qt_("Guess from extension (*.*)");
3160 vector<Format const *> export_formats;
3161 for (Format const & f : theFormats())
3162 if (f.documentFormat())
3163 export_formats.push_back(&f);
3164 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3165 map<QString, string> fmap;
3168 for (Format const * f : export_formats) {
3169 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3170 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3172 from_ascii(f->extension())));
3173 types << loc_filter;
3174 fmap[loc_filter] = f->name();
3175 if (from_ascii(f->name()) == iformat) {
3176 filter = loc_filter;
3177 ext = f->extension();
3180 string ofname = fname.onlyFileName();
3182 ofname = support::changeExtension(ofname, ext);
3183 FileDialog::Result result =
3184 dlg.save(toqstr(fname.onlyPath().absFileName()),
3188 if (result.first != FileDialog::Chosen)
3192 fname.set(fromqstr(result.second));
3193 if (filter == anyformat)
3194 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3196 fmt_name = fmap[filter];
3197 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3198 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3200 if (fmt_name.empty() || fname.empty())
3203 // fname is now the new Buffer location.
3204 if (fname.exists()) {
3205 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3206 docstring text = bformat(_("The document %1$s already "
3207 "exists.\n\nDo you want to "
3208 "overwrite that document?"),
3210 int const ret = Alert::prompt(_("Overwrite document?"),
3211 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3214 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3215 case 2: return false;
3219 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3222 return dr.dispatched();
3226 bool GuiView::saveBuffer(Buffer & b)
3228 return saveBuffer(b, FileName());
3232 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3234 if (workArea(b) && workArea(b)->inDialogMode())
3237 if (fn.empty() && b.isUnnamed())
3238 return renameBuffer(b, docstring());
3240 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3242 theSession().lastFiles().add(b.fileName());
3243 theSession().writeFile();
3247 // Switch to this Buffer.
3250 // FIXME: we don't tell the user *WHY* the save failed !!
3251 docstring const file = makeDisplayPath(b.absFileName(), 30);
3252 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3253 "Do you want to rename the document and "
3254 "try again?"), file);
3255 int const ret = Alert::prompt(_("Rename and save?"),
3256 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3259 if (!renameBuffer(b, docstring()))
3268 return saveBuffer(b, fn);
3272 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3274 return closeWorkArea(wa, false);
3278 // We only want to close the buffer if it is not visible in other workareas
3279 // of the same view, nor in other views, and if this is not a child
3280 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3282 Buffer & buf = wa->bufferView().buffer();
3284 bool last_wa = d.countWorkAreasOf(buf) == 1
3285 && !inOtherView(buf) && !buf.parent();
3287 bool close_buffer = last_wa;
3290 if (lyxrc.close_buffer_with_last_view == "yes")
3292 else if (lyxrc.close_buffer_with_last_view == "no")
3293 close_buffer = false;
3296 if (buf.isUnnamed())
3297 file = from_utf8(buf.fileName().onlyFileName());
3299 file = buf.fileName().displayName(30);
3300 docstring const text = bformat(
3301 _("Last view on document %1$s is being closed.\n"
3302 "Would you like to close or hide the document?\n"
3304 "Hidden documents can be displayed back through\n"
3305 "the menu: View->Hidden->...\n"
3307 "To remove this question, set your preference in:\n"
3308 " Tools->Preferences->Look&Feel->UserInterface\n"
3310 int ret = Alert::prompt(_("Close or hide document?"),
3311 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3314 close_buffer = (ret == 0);
3318 return closeWorkArea(wa, close_buffer);
3322 bool GuiView::closeBuffer()
3324 GuiWorkArea * wa = currentMainWorkArea();
3325 // coverity complained about this
3326 // it seems unnecessary, but perhaps is worth the check
3327 LASSERT(wa, return false);
3329 setCurrentWorkArea(wa);
3330 Buffer & buf = wa->bufferView().buffer();
3331 return closeWorkArea(wa, !buf.parent());
3335 void GuiView::writeSession() const {
3336 GuiWorkArea const * active_wa = currentMainWorkArea();
3337 for (int i = 0; i < d.splitter_->count(); ++i) {
3338 TabWorkArea * twa = d.tabWorkArea(i);
3339 for (int j = 0; j < twa->count(); ++j) {
3340 GuiWorkArea * wa = twa->workArea(j);
3341 Buffer & buf = wa->bufferView().buffer();
3342 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3348 bool GuiView::closeBufferAll()
3351 for (auto & buf : theBufferList()) {
3352 if (!saveBufferIfNeeded(*buf, false)) {
3353 // Closing has been cancelled, so abort.
3358 // Close the workareas in all other views
3359 QList<int> const ids = guiApp->viewIds();
3360 for (int i = 0; i != ids.size(); ++i) {
3361 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3365 // Close our own workareas
3366 if (!closeWorkAreaAll())
3373 bool GuiView::closeWorkAreaAll()
3375 setCurrentWorkArea(currentMainWorkArea());
3377 // We might be in a situation that there is still a tabWorkArea, but
3378 // there are no tabs anymore. This can happen when we get here after a
3379 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3380 // many TabWorkArea's have no documents anymore.
3383 // We have to call count() each time, because it can happen that
3384 // more than one splitter will disappear in one iteration (bug 5998).
3385 while (d.splitter_->count() > empty_twa) {
3386 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3388 if (twa->count() == 0)
3391 setCurrentWorkArea(twa->currentWorkArea());
3392 if (!closeTabWorkArea(twa))
3400 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3405 Buffer & buf = wa->bufferView().buffer();
3407 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3408 Alert::warning(_("Close document"),
3409 _("Document could not be closed because it is being processed by LyX."));
3414 return closeBuffer(buf);
3416 if (!inMultiTabs(wa))
3417 if (!saveBufferIfNeeded(buf, true))
3425 bool GuiView::closeBuffer(Buffer & buf)
3427 bool success = true;
3428 for (Buffer * child_buf : buf.getChildren()) {
3429 if (theBufferList().isOthersChild(&buf, child_buf)) {
3430 child_buf->setParent(nullptr);
3434 // FIXME: should we look in other tabworkareas?
3435 // ANSWER: I don't think so. I've tested, and if the child is
3436 // open in some other window, it closes without a problem.
3437 GuiWorkArea * child_wa = workArea(*child_buf);
3440 // If we are in a close_event all children will be closed in some time,
3441 // so no need to do it here. This will ensure that the children end up
3442 // in the session file in the correct order. If we close the master
3443 // buffer, we can close or release the child buffers here too.
3445 success = closeWorkArea(child_wa, true);
3449 // In this case the child buffer is open but hidden.
3450 // Even in this case, children can be dirty (e.g.,
3451 // after a label change in the master, see #11405).
3452 // Therefore, check this
3453 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3454 // If we are in a close_event all children will be closed in some time,
3455 // so no need to do it here. This will ensure that the children end up
3456 // in the session file in the correct order. If we close the master
3457 // buffer, we can close or release the child buffers here too.
3460 // Save dirty buffers also if closing_!
3461 if (saveBufferIfNeeded(*child_buf, false)) {
3462 child_buf->removeAutosaveFile();
3463 theBufferList().release(child_buf);
3465 // Saving of dirty children has been cancelled.
3466 // Cancel the whole process.
3473 // goto bookmark to update bookmark pit.
3474 // FIXME: we should update only the bookmarks related to this buffer!
3475 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3476 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3477 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3478 guiApp->gotoBookmark(i, false, false);
3480 if (saveBufferIfNeeded(buf, false)) {
3481 buf.removeAutosaveFile();
3482 theBufferList().release(&buf);
3486 // open all children again to avoid a crash because of dangling
3487 // pointers (bug 6603)
3493 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3495 while (twa == d.currentTabWorkArea()) {
3496 twa->setCurrentIndex(twa->count() - 1);
3498 GuiWorkArea * wa = twa->currentWorkArea();
3499 Buffer & b = wa->bufferView().buffer();
3501 // We only want to close the buffer if the same buffer is not visible
3502 // in another view, and if this is not a child and if we are closing
3503 // a view (not a tabgroup).
3504 bool const close_buffer =
3505 !inOtherView(b) && !b.parent() && closing_;
3507 if (!closeWorkArea(wa, close_buffer))
3514 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3516 if (buf.isClean() || buf.paragraphs().empty())
3519 // Switch to this Buffer.
3525 if (buf.isUnnamed()) {
3526 file = from_utf8(buf.fileName().onlyFileName());
3529 FileName filename = buf.fileName();
3531 file = filename.displayName(30);
3532 exists = filename.exists();
3535 // Bring this window to top before asking questions.
3540 if (hiding && buf.isUnnamed()) {
3541 docstring const text = bformat(_("The document %1$s has not been "
3542 "saved yet.\n\nDo you want to save "
3543 "the document?"), file);
3544 ret = Alert::prompt(_("Save new document?"),
3545 text, 0, 1, _("&Save"), _("&Cancel"));
3549 docstring const text = exists ?
3550 bformat(_("The document %1$s has unsaved changes."
3551 "\n\nDo you want to save the document or "
3552 "discard the changes?"), file) :
3553 bformat(_("The document %1$s has not been saved yet."
3554 "\n\nDo you want to save the document or "
3555 "discard it entirely?"), file);
3556 docstring const title = exists ?
3557 _("Save changed document?") : _("Save document?");
3558 ret = Alert::prompt(title, text, 0, 2,
3559 _("&Save"), _("&Discard"), _("&Cancel"));
3564 if (!saveBuffer(buf))
3568 // If we crash after this we could have no autosave file
3569 // but I guess this is really improbable (Jug).
3570 // Sometimes improbable things happen:
3571 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3572 // buf.removeAutosaveFile();
3574 // revert all changes
3585 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3587 Buffer & buf = wa->bufferView().buffer();
3589 for (int i = 0; i != d.splitter_->count(); ++i) {
3590 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3591 if (wa_ && wa_ != wa)
3594 return inOtherView(buf);
3598 bool GuiView::inOtherView(Buffer & buf)
3600 QList<int> const ids = guiApp->viewIds();
3602 for (int i = 0; i != ids.size(); ++i) {
3606 if (guiApp->view(ids[i]).workArea(buf))
3613 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3615 if (!documentBufferView())
3618 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3619 Buffer * const curbuf = &documentBufferView()->buffer();
3620 int nwa = twa->count();
3621 for (int i = 0; i < nwa; ++i) {
3622 if (&workArea(i)->bufferView().buffer() == curbuf) {
3624 if (np == NEXTBUFFER)
3625 next_index = (i == nwa - 1 ? 0 : i + 1);
3627 next_index = (i == 0 ? nwa - 1 : i - 1);
3629 twa->moveTab(i, next_index);
3631 setBuffer(&workArea(next_index)->bufferView().buffer());
3639 /// make sure the document is saved
3640 static bool ensureBufferClean(Buffer * buffer)
3642 LASSERT(buffer, return false);
3643 if (buffer->isClean() && !buffer->isUnnamed())
3646 docstring const file = buffer->fileName().displayName(30);
3649 if (!buffer->isUnnamed()) {
3650 text = bformat(_("The document %1$s has unsaved "
3651 "changes.\n\nDo you want to save "
3652 "the document?"), file);
3653 title = _("Save changed document?");
3656 text = bformat(_("The document %1$s has not been "
3657 "saved yet.\n\nDo you want to save "
3658 "the document?"), file);
3659 title = _("Save new document?");
3661 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3664 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3666 return buffer->isClean() && !buffer->isUnnamed();
3670 bool GuiView::reloadBuffer(Buffer & buf)
3672 currentBufferView()->cursor().reset();
3673 Buffer::ReadStatus status = buf.reload();
3674 return status == Buffer::ReadSuccess;
3678 void GuiView::checkExternallyModifiedBuffers()
3680 for (Buffer * buf : theBufferList()) {
3681 if (buf->fileName().exists() && buf->isChecksumModified()) {
3682 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3683 " Reload now? Any local changes will be lost."),
3684 from_utf8(buf->absFileName()));
3685 int const ret = Alert::prompt(_("Reload externally changed document?"),
3686 text, 0, 1, _("&Reload"), _("&Cancel"));
3694 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3696 Buffer * buffer = documentBufferView()
3697 ? &(documentBufferView()->buffer()) : nullptr;
3699 switch (cmd.action()) {
3700 case LFUN_VC_REGISTER:
3701 if (!buffer || !ensureBufferClean(buffer))
3703 if (!buffer->lyxvc().inUse()) {
3704 if (buffer->lyxvc().registrer()) {
3705 reloadBuffer(*buffer);
3706 dr.clearMessageUpdate();
3711 case LFUN_VC_RENAME:
3712 case LFUN_VC_COPY: {
3713 if (!buffer || !ensureBufferClean(buffer))
3715 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3716 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3717 // Some changes are not yet committed.
3718 // We test here and not in getStatus(), since
3719 // this test is expensive.
3721 LyXVC::CommandResult ret =
3722 buffer->lyxvc().checkIn(log);
3724 if (ret == LyXVC::ErrorCommand ||
3725 ret == LyXVC::VCSuccess)
3726 reloadBuffer(*buffer);
3727 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3728 frontend::Alert::error(
3729 _("Revision control error."),
3730 _("Document could not be checked in."));
3734 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3735 LV_VC_RENAME : LV_VC_COPY;
3736 renameBuffer(*buffer, cmd.argument(), kind);
3741 case LFUN_VC_CHECK_IN:
3742 if (!buffer || !ensureBufferClean(buffer))
3744 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3746 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3748 // Only skip reloading if the checkin was cancelled or
3749 // an error occurred before the real checkin VCS command
3750 // was executed, since the VCS might have changed the
3751 // file even if it could not checkin successfully.
3752 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3753 reloadBuffer(*buffer);
3757 case LFUN_VC_CHECK_OUT:
3758 if (!buffer || !ensureBufferClean(buffer))
3760 if (buffer->lyxvc().inUse()) {
3761 dr.setMessage(buffer->lyxvc().checkOut());
3762 reloadBuffer(*buffer);
3766 case LFUN_VC_LOCKING_TOGGLE:
3767 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3769 if (buffer->lyxvc().inUse()) {
3770 string res = buffer->lyxvc().lockingToggle();
3772 frontend::Alert::error(_("Revision control error."),
3773 _("Error when setting the locking property."));
3776 reloadBuffer(*buffer);
3781 case LFUN_VC_REVERT:
3784 if (buffer->lyxvc().revert()) {
3785 reloadBuffer(*buffer);
3786 dr.clearMessageUpdate();
3790 case LFUN_VC_UNDO_LAST:
3793 buffer->lyxvc().undoLast();
3794 reloadBuffer(*buffer);
3795 dr.clearMessageUpdate();
3798 case LFUN_VC_REPO_UPDATE:
3801 if (ensureBufferClean(buffer)) {
3802 dr.setMessage(buffer->lyxvc().repoUpdate());
3803 checkExternallyModifiedBuffers();
3807 case LFUN_VC_COMMAND: {
3808 string flag = cmd.getArg(0);
3809 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3812 if (contains(flag, 'M')) {
3813 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3816 string path = cmd.getArg(1);
3817 if (contains(path, "$$p") && buffer)
3818 path = subst(path, "$$p", buffer->filePath());
3819 LYXERR(Debug::LYXVC, "Directory: " << path);
3821 if (!pp.isReadableDirectory()) {
3822 lyxerr << _("Directory is not accessible.") << endl;
3825 support::PathChanger p(pp);
3827 string command = cmd.getArg(2);
3828 if (command.empty())
3831 command = subst(command, "$$i", buffer->absFileName());
3832 command = subst(command, "$$p", buffer->filePath());
3834 command = subst(command, "$$m", to_utf8(message));
3835 LYXERR(Debug::LYXVC, "Command: " << command);
3837 one.startscript(Systemcall::Wait, command);
3841 if (contains(flag, 'I'))
3842 buffer->markDirty();
3843 if (contains(flag, 'R'))
3844 reloadBuffer(*buffer);
3849 case LFUN_VC_COMPARE: {
3850 if (cmd.argument().empty()) {
3851 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3857 string rev1 = cmd.getArg(0);
3861 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3864 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3865 f2 = buffer->absFileName();
3867 string rev2 = cmd.getArg(1);
3871 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3875 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3876 f1 << "\n" << f2 << "\n" );
3877 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3878 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3888 void GuiView::openChildDocument(string const & fname)
3890 LASSERT(documentBufferView(), return);
3891 Buffer & buffer = documentBufferView()->buffer();
3892 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3893 documentBufferView()->saveBookmark(false);
3894 Buffer * child = nullptr;
3895 if (theBufferList().exists(filename)) {
3896 child = theBufferList().getBuffer(filename);
3899 message(bformat(_("Opening child document %1$s..."),
3900 makeDisplayPath(filename.absFileName())));
3901 child = loadDocument(filename, false);
3903 // Set the parent name of the child document.
3904 // This makes insertion of citations and references in the child work,
3905 // when the target is in the parent or another child document.
3907 child->setParent(&buffer);
3911 bool GuiView::goToFileRow(string const & argument)
3915 size_t i = argument.find_last_of(' ');
3916 if (i != string::npos) {
3917 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3918 istringstream is(argument.substr(i + 1));
3923 if (i == string::npos) {
3924 LYXERR0("Wrong argument: " << argument);
3927 Buffer * buf = nullptr;
3928 string const realtmp = package().temp_dir().realPath();
3929 // We have to use os::path_prefix_is() here, instead of
3930 // simply prefixIs(), because the file name comes from
3931 // an external application and may need case adjustment.
3932 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3933 buf = theBufferList().getBufferFromTmp(file_name, true);
3934 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3935 << (buf ? " success" : " failed"));
3937 // Must replace extension of the file to be .lyx
3938 // and get full path
3939 FileName const s = fileSearch(string(),
3940 support::changeExtension(file_name, ".lyx"), "lyx");
3941 // Either change buffer or load the file
3942 if (theBufferList().exists(s))
3943 buf = theBufferList().getBuffer(s);
3944 else if (s.exists()) {
3945 buf = loadDocument(s);
3950 _("File does not exist: %1$s"),
3951 makeDisplayPath(file_name)));
3957 _("No buffer for file: %1$s."),
3958 makeDisplayPath(file_name))
3963 bool success = documentBufferView()->setCursorFromRow(row);
3965 LYXERR(Debug::LATEX,
3966 "setCursorFromRow: invalid position for row " << row);
3967 frontend::Alert::error(_("Inverse Search Failed"),
3968 _("Invalid position requested by inverse search.\n"
3969 "You may need to update the viewed document."));
3975 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3977 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3978 menu->exec(QCursor::pos());
3983 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3984 Buffer const * orig, Buffer * clone, string const & format)
3986 Buffer::ExportStatus const status = func(format);
3988 // the cloning operation will have produced a clone of the entire set of
3989 // documents, starting from the master. so we must delete those.
3990 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3992 busyBuffers.remove(orig);
3997 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3998 Buffer const * orig, Buffer * clone, string const & format)
4000 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4002 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4006 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4007 Buffer const * orig, Buffer * clone, string const & format)
4009 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4011 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4015 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4016 Buffer const * orig, Buffer * clone, string const & format)
4018 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4020 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4024 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4025 Buffer const * used_buffer,
4026 docstring const & msg,
4027 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4028 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4029 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4030 bool allow_async, bool use_tmpdir)
4035 string format = argument;
4037 format = used_buffer->params().getDefaultOutputFormat();
4038 processing_format = format;
4040 progress_->clearMessages();
4043 #if EXPORT_in_THREAD
4045 GuiViewPrivate::busyBuffers.insert(used_buffer);
4046 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4047 if (!cloned_buffer) {
4048 Alert::error(_("Export Error"),
4049 _("Error cloning the Buffer."));
4052 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4057 setPreviewFuture(f);
4058 last_export_format = used_buffer->params().bufferFormat();
4061 // We are asynchronous, so we don't know here anything about the success
4064 Buffer::ExportStatus status;
4066 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4067 } else if (previewFunc) {
4068 status = (used_buffer->*previewFunc)(format);
4071 handleExportStatus(gv_, status, format);
4073 return (status == Buffer::ExportSuccess
4074 || status == Buffer::PreviewSuccess);
4078 Buffer::ExportStatus status;
4080 status = (used_buffer->*syncFunc)(format, true);
4081 } else if (previewFunc) {
4082 status = (used_buffer->*previewFunc)(format);
4085 handleExportStatus(gv_, status, format);
4087 return (status == Buffer::ExportSuccess
4088 || status == Buffer::PreviewSuccess);
4092 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4094 BufferView * bv = currentBufferView();
4095 LASSERT(bv, return);
4097 // Let the current BufferView dispatch its own actions.
4098 bv->dispatch(cmd, dr);
4099 if (dr.dispatched()) {
4100 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4101 updateDialog("document", "");
4105 // Try with the document BufferView dispatch if any.
4106 BufferView * doc_bv = documentBufferView();
4107 if (doc_bv && doc_bv != bv) {
4108 doc_bv->dispatch(cmd, dr);
4109 if (dr.dispatched()) {
4110 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4111 updateDialog("document", "");
4116 // Then let the current Cursor dispatch its own actions.
4117 bv->cursor().dispatch(cmd);
4119 // update completion. We do it here and not in
4120 // processKeySym to avoid another redraw just for a
4121 // changed inline completion
4122 if (cmd.origin() == FuncRequest::KEYBOARD) {
4123 if (cmd.action() == LFUN_SELF_INSERT
4124 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4125 updateCompletion(bv->cursor(), true, true);
4126 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4127 updateCompletion(bv->cursor(), false, true);
4129 updateCompletion(bv->cursor(), false, false);
4132 dr = bv->cursor().result();
4136 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4138 BufferView * bv = currentBufferView();
4139 // By default we won't need any update.
4140 dr.screenUpdate(Update::None);
4141 // assume cmd will be dispatched
4142 dr.dispatched(true);
4144 Buffer * doc_buffer = documentBufferView()
4145 ? &(documentBufferView()->buffer()) : nullptr;
4147 if (cmd.origin() == FuncRequest::TOC) {
4148 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4149 toc->doDispatch(bv->cursor(), cmd, dr);
4153 string const argument = to_utf8(cmd.argument());
4155 switch(cmd.action()) {
4156 case LFUN_BUFFER_CHILD_OPEN:
4157 openChildDocument(to_utf8(cmd.argument()));
4160 case LFUN_BUFFER_IMPORT:
4161 importDocument(to_utf8(cmd.argument()));
4164 case LFUN_MASTER_BUFFER_EXPORT:
4166 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4168 case LFUN_BUFFER_EXPORT: {
4171 // GCC only sees strfwd.h when building merged
4172 if (::lyx::operator==(cmd.argument(), "custom")) {
4173 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4174 // so the following test should not be needed.
4175 // In principle, we could try to switch to such a view...
4176 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4177 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4181 string const dest = cmd.getArg(1);
4182 FileName target_dir;
4183 if (!dest.empty() && FileName::isAbsolute(dest))
4184 target_dir = FileName(support::onlyPath(dest));
4186 target_dir = doc_buffer->fileName().onlyPath();
4188 string const format = (argument.empty() || argument == "default") ?
4189 doc_buffer->params().getDefaultOutputFormat() : argument;
4191 if ((dest.empty() && doc_buffer->isUnnamed())
4192 || !target_dir.isDirWritable()) {
4193 exportBufferAs(*doc_buffer, from_utf8(format));
4196 /* TODO/Review: Is it a problem to also export the children?
4197 See the update_unincluded flag */
4198 d.asyncBufferProcessing(format,
4201 &GuiViewPrivate::exportAndDestroy,
4203 nullptr, cmd.allowAsync());
4204 // TODO Inform user about success
4208 case LFUN_BUFFER_EXPORT_AS: {
4209 LASSERT(doc_buffer, break);
4210 docstring f = cmd.argument();
4212 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4213 exportBufferAs(*doc_buffer, f);
4217 case LFUN_BUFFER_UPDATE: {
4218 d.asyncBufferProcessing(argument,
4221 &GuiViewPrivate::compileAndDestroy,
4223 nullptr, cmd.allowAsync(), true);
4226 case LFUN_BUFFER_VIEW: {
4227 d.asyncBufferProcessing(argument,
4229 _("Previewing ..."),
4230 &GuiViewPrivate::previewAndDestroy,
4232 &Buffer::preview, cmd.allowAsync());
4235 case LFUN_MASTER_BUFFER_UPDATE: {
4236 d.asyncBufferProcessing(argument,
4237 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4239 &GuiViewPrivate::compileAndDestroy,
4241 nullptr, cmd.allowAsync(), true);
4244 case LFUN_MASTER_BUFFER_VIEW: {
4245 d.asyncBufferProcessing(argument,
4246 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4248 &GuiViewPrivate::previewAndDestroy,
4249 nullptr, &Buffer::preview, cmd.allowAsync());
4252 case LFUN_EXPORT_CANCEL: {
4253 Systemcall::killscript();
4256 case LFUN_BUFFER_SWITCH: {
4257 string const file_name = to_utf8(cmd.argument());
4258 if (!FileName::isAbsolute(file_name)) {
4260 dr.setMessage(_("Absolute filename expected."));
4264 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4267 dr.setMessage(_("Document not loaded"));
4271 // Do we open or switch to the buffer in this view ?
4272 if (workArea(*buffer)
4273 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4278 // Look for the buffer in other views
4279 QList<int> const ids = guiApp->viewIds();
4281 for (; i != ids.size(); ++i) {
4282 GuiView & gv = guiApp->view(ids[i]);
4283 if (gv.workArea(*buffer)) {
4285 gv.activateWindow();
4287 gv.setBuffer(buffer);
4292 // If necessary, open a new window as a last resort
4293 if (i == ids.size()) {
4294 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4300 case LFUN_BUFFER_NEXT:
4301 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4304 case LFUN_BUFFER_MOVE_NEXT:
4305 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4308 case LFUN_BUFFER_PREVIOUS:
4309 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4312 case LFUN_BUFFER_MOVE_PREVIOUS:
4313 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4316 case LFUN_BUFFER_CHKTEX:
4317 LASSERT(doc_buffer, break);
4318 doc_buffer->runChktex();
4321 case LFUN_COMMAND_EXECUTE: {
4322 command_execute_ = true;
4323 minibuffer_focus_ = true;
4326 case LFUN_DROP_LAYOUTS_CHOICE:
4327 d.layout_->showPopup();
4330 case LFUN_MENU_OPEN:
4331 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4332 menu->exec(QCursor::pos());
4335 case LFUN_FILE_INSERT: {
4336 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4337 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4338 dr.forceBufferUpdate();
4339 dr.screenUpdate(Update::Force);
4344 case LFUN_FILE_INSERT_PLAINTEXT:
4345 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4346 string const fname = to_utf8(cmd.argument());
4347 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4348 dr.setMessage(_("Absolute filename expected."));
4352 FileName filename(fname);
4353 if (fname.empty()) {
4354 FileDialog dlg(qt_("Select file to insert"));
4356 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4357 QStringList(qt_("All Files (*)")));
4359 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4360 dr.setMessage(_("Canceled."));
4364 filename.set(fromqstr(result.second));
4368 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4369 bv->dispatch(new_cmd, dr);
4374 case LFUN_BUFFER_RELOAD: {
4375 LASSERT(doc_buffer, break);
4378 bool drop = (cmd.argument() == "dump");
4381 if (!drop && !doc_buffer->isClean()) {
4382 docstring const file =
4383 makeDisplayPath(doc_buffer->absFileName(), 20);
4384 if (doc_buffer->notifiesExternalModification()) {
4385 docstring text = _("The current version will be lost. "
4386 "Are you sure you want to load the version on disk "
4387 "of the document %1$s?");
4388 ret = Alert::prompt(_("Reload saved document?"),
4389 bformat(text, file), 1, 1,
4390 _("&Reload"), _("&Cancel"));
4392 docstring text = _("Any changes will be lost. "
4393 "Are you sure you want to revert to the saved version "
4394 "of the document %1$s?");
4395 ret = Alert::prompt(_("Revert to saved document?"),
4396 bformat(text, file), 1, 1,
4397 _("&Revert"), _("&Cancel"));
4402 doc_buffer->markClean();
4403 reloadBuffer(*doc_buffer);
4404 dr.forceBufferUpdate();
4409 case LFUN_BUFFER_RESET_EXPORT:
4410 LASSERT(doc_buffer, break);
4411 doc_buffer->requireFreshStart(true);
4412 dr.setMessage(_("Buffer export reset."));
4415 case LFUN_BUFFER_WRITE:
4416 LASSERT(doc_buffer, break);
4417 saveBuffer(*doc_buffer);
4420 case LFUN_BUFFER_WRITE_AS:
4421 LASSERT(doc_buffer, break);
4422 renameBuffer(*doc_buffer, cmd.argument());
4425 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4426 LASSERT(doc_buffer, break);
4427 renameBuffer(*doc_buffer, cmd.argument(),
4428 LV_WRITE_AS_TEMPLATE);
4431 case LFUN_BUFFER_WRITE_ALL: {
4432 Buffer * first = theBufferList().first();
4435 message(_("Saving all documents..."));
4436 // We cannot use a for loop as the buffer list cycles.
4439 if (!b->isClean()) {
4441 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4443 b = theBufferList().next(b);
4444 } while (b != first);
4445 dr.setMessage(_("All documents saved."));
4449 case LFUN_MASTER_BUFFER_FORALL: {
4453 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4454 funcToRun.allowAsync(false);
4456 for (Buffer const * buf : doc_buffer->allRelatives()) {
4457 // Switch to other buffer view and resend cmd
4458 lyx::dispatch(FuncRequest(
4459 LFUN_BUFFER_SWITCH, buf->absFileName()));
4460 lyx::dispatch(funcToRun);
4463 lyx::dispatch(FuncRequest(
4464 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4468 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4469 LASSERT(doc_buffer, break);
4470 doc_buffer->clearExternalModification();
4473 case LFUN_BUFFER_CLOSE:
4477 case LFUN_BUFFER_CLOSE_ALL:
4481 case LFUN_DEVEL_MODE_TOGGLE:
4482 devel_mode_ = !devel_mode_;
4484 dr.setMessage(_("Developer mode is now enabled."));
4486 dr.setMessage(_("Developer mode is now disabled."));
4489 case LFUN_TOOLBAR_SET: {
4490 string const name = cmd.getArg(0);
4491 string const state = cmd.getArg(1);
4492 if (GuiToolbar * t = toolbar(name))
4497 case LFUN_TOOLBAR_TOGGLE: {
4498 string const name = cmd.getArg(0);
4499 if (GuiToolbar * t = toolbar(name))
4504 case LFUN_TOOLBAR_MOVABLE: {
4505 string const name = cmd.getArg(0);
4507 // toggle (all) toolbars movablility
4508 toolbarsMovable_ = !toolbarsMovable_;
4509 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4510 GuiToolbar * tb = toolbar(ti.name);
4511 if (tb && tb->isMovable() != toolbarsMovable_)
4512 // toggle toolbar movablity if it does not fit lock
4513 // (all) toolbars positions state silent = true, since
4514 // status bar notifications are slow
4517 if (toolbarsMovable_)
4518 dr.setMessage(_("Toolbars unlocked."));
4520 dr.setMessage(_("Toolbars locked."));
4521 } else if (GuiToolbar * tb = toolbar(name))
4522 // toggle current toolbar movablity
4524 // update lock (all) toolbars positions
4525 updateLockToolbars();
4529 case LFUN_ICON_SIZE: {
4530 QSize size = d.iconSize(cmd.argument());
4532 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4533 size.width(), size.height()));
4537 case LFUN_DIALOG_UPDATE: {
4538 string const name = to_utf8(cmd.argument());
4539 if (name == "prefs" || name == "document")
4540 updateDialog(name, string());
4541 else if (name == "paragraph")
4542 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4543 else if (currentBufferView()) {
4544 Inset * inset = currentBufferView()->editedInset(name);
4545 // Can only update a dialog connected to an existing inset
4547 // FIXME: get rid of this indirection; GuiView ask the inset
4548 // if he is kind enough to update itself...
4549 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4550 //FIXME: pass DispatchResult here?
4551 inset->dispatch(currentBufferView()->cursor(), fr);
4557 case LFUN_DIALOG_TOGGLE: {
4558 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4559 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4560 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4564 case LFUN_DIALOG_DISCONNECT_INSET:
4565 disconnectDialog(to_utf8(cmd.argument()));
4568 case LFUN_DIALOG_HIDE: {
4569 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4573 case LFUN_DIALOG_SHOW: {
4574 string const name = cmd.getArg(0);
4575 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4577 if (name == "latexlog") {
4578 // getStatus checks that
4579 LASSERT(doc_buffer, break);
4580 Buffer::LogType type;
4581 string const logfile = doc_buffer->logName(&type);
4583 case Buffer::latexlog:
4586 case Buffer::buildlog:
4587 sdata = "literate ";
4590 sdata += Lexer::quoteString(logfile);
4591 showDialog("log", sdata);
4592 } else if (name == "vclog") {
4593 // getStatus checks that
4594 LASSERT(doc_buffer, break);
4595 string const sdata2 = "vc " +
4596 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4597 showDialog("log", sdata2);
4598 } else if (name == "symbols") {
4599 sdata = bv->cursor().getEncoding()->name();
4601 showDialog("symbols", sdata);
4602 } else if (name == "findreplace") {
4603 sdata = to_utf8(bv->cursor().selectionAsString(false));
4604 showDialog(name, sdata);
4606 } else if (name == "prefs" && isFullScreen()) {
4607 lfunUiToggle("fullscreen");
4608 showDialog("prefs", sdata);
4610 showDialog(name, sdata);
4615 dr.setMessage(cmd.argument());
4618 case LFUN_UI_TOGGLE: {
4619 string arg = cmd.getArg(0);
4620 if (!lfunUiToggle(arg)) {
4621 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4622 dr.setMessage(bformat(msg, from_utf8(arg)));
4624 // Make sure the keyboard focus stays in the work area.
4629 case LFUN_VIEW_SPLIT: {
4630 LASSERT(doc_buffer, break);
4631 string const orientation = cmd.getArg(0);
4632 d.splitter_->setOrientation(orientation == "vertical"
4633 ? Qt::Vertical : Qt::Horizontal);
4634 TabWorkArea * twa = addTabWorkArea();
4635 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4636 setCurrentWorkArea(wa);
4639 case LFUN_TAB_GROUP_CLOSE:
4640 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4641 closeTabWorkArea(twa);
4642 d.current_work_area_ = nullptr;
4643 twa = d.currentTabWorkArea();
4644 // Switch to the next GuiWorkArea in the found TabWorkArea.
4646 // Make sure the work area is up to date.
4647 setCurrentWorkArea(twa->currentWorkArea());
4649 setCurrentWorkArea(nullptr);
4654 case LFUN_VIEW_CLOSE:
4655 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4656 closeWorkArea(twa->currentWorkArea());
4657 d.current_work_area_ = nullptr;
4658 twa = d.currentTabWorkArea();
4659 // Switch to the next GuiWorkArea in the found TabWorkArea.
4661 // Make sure the work area is up to date.
4662 setCurrentWorkArea(twa->currentWorkArea());
4664 setCurrentWorkArea(nullptr);
4669 case LFUN_COMPLETION_INLINE:
4670 if (d.current_work_area_)
4671 d.current_work_area_->completer().showInline();
4674 case LFUN_COMPLETION_POPUP:
4675 if (d.current_work_area_)
4676 d.current_work_area_->completer().showPopup();
4681 if (d.current_work_area_)
4682 d.current_work_area_->completer().tab();
4685 case LFUN_COMPLETION_CANCEL:
4686 if (d.current_work_area_) {
4687 if (d.current_work_area_->completer().popupVisible())
4688 d.current_work_area_->completer().hidePopup();
4690 d.current_work_area_->completer().hideInline();
4694 case LFUN_COMPLETION_ACCEPT:
4695 if (d.current_work_area_)
4696 d.current_work_area_->completer().activate();
4699 case LFUN_BUFFER_ZOOM_IN:
4700 case LFUN_BUFFER_ZOOM_OUT:
4701 case LFUN_BUFFER_ZOOM: {
4702 if (cmd.argument().empty()) {
4703 if (cmd.action() == LFUN_BUFFER_ZOOM)
4705 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4710 if (cmd.action() == LFUN_BUFFER_ZOOM)
4711 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4712 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4713 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4715 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4718 // Actual zoom value: default zoom + fractional extra value
4719 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4720 if (zoom < static_cast<int>(zoom_min_))
4723 setCurrentZoom(zoom);
4725 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4726 lyxrc.currentZoom, lyxrc.defaultZoom));
4728 guiApp->fontLoader().update();
4729 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4733 case LFUN_VC_REGISTER:
4734 case LFUN_VC_RENAME:
4736 case LFUN_VC_CHECK_IN:
4737 case LFUN_VC_CHECK_OUT:
4738 case LFUN_VC_REPO_UPDATE:
4739 case LFUN_VC_LOCKING_TOGGLE:
4740 case LFUN_VC_REVERT:
4741 case LFUN_VC_UNDO_LAST:
4742 case LFUN_VC_COMMAND:
4743 case LFUN_VC_COMPARE:
4744 dispatchVC(cmd, dr);
4747 case LFUN_SERVER_GOTO_FILE_ROW:
4748 if(goToFileRow(to_utf8(cmd.argument())))
4749 dr.screenUpdate(Update::Force | Update::FitCursor);
4752 case LFUN_LYX_ACTIVATE:
4756 case LFUN_WINDOW_RAISE:
4762 case LFUN_FORWARD_SEARCH: {
4763 // it seems safe to assume we have a document buffer, since
4764 // getStatus wants one.
4765 LASSERT(doc_buffer, break);
4766 Buffer const * doc_master = doc_buffer->masterBuffer();
4767 FileName const path(doc_master->temppath());
4768 string const texname = doc_master->isChild(doc_buffer)
4769 ? DocFileName(changeExtension(
4770 doc_buffer->absFileName(),
4771 "tex")).mangledFileName()
4772 : doc_buffer->latexName();
4773 string const fulltexname =
4774 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4775 string const mastername =
4776 removeExtension(doc_master->latexName());
4777 FileName const dviname(addName(path.absFileName(),
4778 addExtension(mastername, "dvi")));
4779 FileName const pdfname(addName(path.absFileName(),
4780 addExtension(mastername, "pdf")));
4781 bool const have_dvi = dviname.exists();
4782 bool const have_pdf = pdfname.exists();
4783 if (!have_dvi && !have_pdf) {
4784 dr.setMessage(_("Please, preview the document first."));
4787 string outname = dviname.onlyFileName();
4788 string command = lyxrc.forward_search_dvi;
4789 if (!have_dvi || (have_pdf &&
4790 pdfname.lastModified() > dviname.lastModified())) {
4791 outname = pdfname.onlyFileName();
4792 command = lyxrc.forward_search_pdf;
4795 DocIterator cur = bv->cursor();
4796 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4797 LYXERR(Debug::ACTION, "Forward search: row:" << row
4799 if (row == -1 || command.empty()) {
4800 dr.setMessage(_("Couldn't proceed."));
4803 string texrow = convert<string>(row);
4805 command = subst(command, "$$n", texrow);
4806 command = subst(command, "$$f", fulltexname);
4807 command = subst(command, "$$t", texname);
4808 command = subst(command, "$$o", outname);
4810 volatile PathChanger p(path);
4812 one.startscript(Systemcall::DontWait, command);
4816 case LFUN_SPELLING_CONTINUOUSLY:
4817 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4818 dr.screenUpdate(Update::Force);
4821 case LFUN_CITATION_OPEN: {
4823 if (theFormats().getFormat("pdf"))
4824 pdfv = theFormats().getFormat("pdf")->viewer();
4825 if (theFormats().getFormat("ps"))
4826 psv = theFormats().getFormat("ps")->viewer();
4827 frontend::showTarget(argument, pdfv, psv);
4832 // The LFUN must be for one of BufferView, Buffer or Cursor;
4834 dispatchToBufferView(cmd, dr);
4838 // Need to update bv because many LFUNs here might have destroyed it
4839 bv = currentBufferView();
4841 // Clear non-empty selections
4842 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4844 Cursor & cur = bv->cursor();
4845 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4846 cur.clearSelection();
4852 bool GuiView::lfunUiToggle(string const & ui_component)
4854 if (ui_component == "scrollbar") {
4855 // hide() is of no help
4856 if (d.current_work_area_->verticalScrollBarPolicy() ==
4857 Qt::ScrollBarAlwaysOff)
4859 d.current_work_area_->setVerticalScrollBarPolicy(
4860 Qt::ScrollBarAsNeeded);
4862 d.current_work_area_->setVerticalScrollBarPolicy(
4863 Qt::ScrollBarAlwaysOff);
4864 } else if (ui_component == "statusbar") {
4865 statusBar()->setVisible(!statusBar()->isVisible());
4866 } else if (ui_component == "menubar") {
4867 menuBar()->setVisible(!menuBar()->isVisible());
4868 } else if (ui_component == "zoom") {
4869 zoom_value_->setVisible(!zoom_value_->isVisible());
4870 } else if (ui_component == "zoomslider") {
4871 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4872 zoom_in_->setVisible(zoom_slider_->isVisible());
4873 zoom_out_->setVisible(zoom_slider_->isVisible());
4874 } else if (ui_component == "frame") {
4875 int const l = contentsMargins().left();
4877 //are the frames in default state?
4878 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4880 #if QT_VERSION > 0x050903
4881 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4883 setContentsMargins(-2, -2, -2, -2);
4885 #if QT_VERSION > 0x050903
4886 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4888 setContentsMargins(0, 0, 0, 0);
4891 if (ui_component == "fullscreen") {
4899 void GuiView::toggleFullScreen()
4901 setWindowState(windowState() ^ Qt::WindowFullScreen);
4905 Buffer const * GuiView::updateInset(Inset const * inset)
4910 Buffer const * inset_buffer = &(inset->buffer());
4912 for (int i = 0; i != d.splitter_->count(); ++i) {
4913 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4916 Buffer const * buffer = &(wa->bufferView().buffer());
4917 if (inset_buffer == buffer)
4918 wa->scheduleRedraw(true);
4920 return inset_buffer;
4924 void GuiView::restartCaret()
4926 /* When we move around, or type, it's nice to be able to see
4927 * the caret immediately after the keypress.
4929 if (d.current_work_area_)
4930 d.current_work_area_->startBlinkingCaret();
4932 // Take this occasion to update the other GUI elements.
4938 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4940 if (d.current_work_area_)
4941 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4946 // This list should be kept in sync with the list of insets in
4947 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4948 // dialog should have the same name as the inset.
4949 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4950 // docs in LyXAction.cpp.
4952 char const * const dialognames[] = {
4954 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4955 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4956 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4957 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4958 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4959 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4960 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4961 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4963 char const * const * const end_dialognames =
4964 dialognames + (sizeof(dialognames) / sizeof(char *));
4968 cmpCStr(char const * name) : name_(name) {}
4969 bool operator()(char const * other) {
4970 return strcmp(other, name_) == 0;
4977 bool isValidName(string const & name)
4979 return find_if(dialognames, end_dialognames,
4980 cmpCStr(name.c_str())) != end_dialognames;
4986 void GuiView::resetDialogs()
4988 // Make sure that no LFUN uses any GuiView.
4989 guiApp->setCurrentView(nullptr);
4993 constructToolbars();
4994 guiApp->menus().fillMenuBar(menuBar(), this, false);
4995 d.layout_->updateContents(true);
4996 // Now update controls with current buffer.
4997 guiApp->setCurrentView(this);
5003 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5005 for (QObject * child: widget->children()) {
5006 if (child->inherits("QGroupBox")) {
5007 QGroupBox * box = (QGroupBox*) child;
5010 flatGroupBoxes(child, flag);
5016 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5018 if (!isValidName(name))
5021 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5023 if (it != d.dialogs_.end()) {
5025 it->second->hideView();
5026 return it->second.get();
5029 Dialog * dialog = build(name);
5030 d.dialogs_[name].reset(dialog);
5031 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5032 // Force a uniform style for group boxes
5033 // On Mac non-flat works better, on Linux flat is standard
5034 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5036 if (lyxrc.allow_geometry_session)
5037 dialog->restoreSession();
5044 void GuiView::showDialog(string const & name, string const & sdata,
5047 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5051 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5057 const string name = fromqstr(qname);
5058 const string sdata = fromqstr(qdata);
5062 Dialog * dialog = findOrBuild(name, false);
5064 bool const visible = dialog->isVisibleView();
5065 dialog->showData(sdata);
5066 if (currentBufferView())
5067 currentBufferView()->editInset(name, inset);
5068 // We only set the focus to the new dialog if it was not yet
5069 // visible in order not to change the existing previous behaviour
5071 // activateWindow is needed for floating dockviews
5072 dialog->asQWidget()->raise();
5073 dialog->asQWidget()->activateWindow();
5074 if (dialog->wantInitialFocus())
5075 dialog->asQWidget()->setFocus();
5079 catch (ExceptionMessage const &) {
5087 bool GuiView::isDialogVisible(string const & name) const
5089 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5090 if (it == d.dialogs_.end())
5092 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5096 void GuiView::hideDialog(string const & name, Inset * inset)
5098 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5099 if (it == d.dialogs_.end())
5103 if (!currentBufferView())
5105 if (inset != currentBufferView()->editedInset(name))
5109 Dialog * const dialog = it->second.get();
5110 if (dialog->isVisibleView())
5112 if (currentBufferView())
5113 currentBufferView()->editInset(name, nullptr);
5117 void GuiView::disconnectDialog(string const & name)
5119 if (!isValidName(name))
5121 if (currentBufferView())
5122 currentBufferView()->editInset(name, nullptr);
5126 void GuiView::hideAll() const
5128 for(auto const & dlg_p : d.dialogs_)
5129 dlg_p.second->hideView();
5133 void GuiView::updateDialogs()
5135 for(auto const & dlg_p : d.dialogs_) {
5136 Dialog * dialog = dlg_p.second.get();
5138 if (dialog->needBufferOpen() && !documentBufferView())
5139 hideDialog(fromqstr(dialog->name()), nullptr);
5140 else if (dialog->isVisibleView())
5141 dialog->checkStatus();
5149 Dialog * GuiView::build(string const & name)
5151 return createDialog(*this, name);
5155 SEMenu::SEMenu(QWidget * parent)
5157 QAction * action = addAction(qt_("Disable Shell Escape"));
5158 connect(action, SIGNAL(triggered()),
5159 parent, SLOT(disableShellEscape()));
5163 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5165 if (event->button() == Qt::LeftButton) {
5170 } // namespace frontend
5173 #include "moc_GuiView.cpp"