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);
1581 case QEvent::WindowActivate: {
1582 GuiView * old_view = guiApp->currentView();
1583 if (this == old_view) {
1585 return QMainWindow::event(e);
1587 if (old_view && old_view->currentBufferView()) {
1588 // save current selection to the selection buffer to allow
1589 // middle-button paste in this window.
1590 cap::saveSelection(old_view->currentBufferView()->cursor());
1592 guiApp->setCurrentView(this);
1593 if (d.current_work_area_)
1594 on_currentWorkAreaChanged(d.current_work_area_);
1598 return QMainWindow::event(e);
1601 case QEvent::ShortcutOverride: {
1603 if (isFullScreen() && menuBar()->isHidden()) {
1604 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1605 // FIXME: we should also try to detect special LyX shortcut such as
1606 // Alt-P and Alt-M. Right now there is a hack in
1607 // GuiWorkArea::processKeySym() that hides again the menubar for
1609 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1611 return QMainWindow::event(e);
1614 return QMainWindow::event(e);
1617 case QEvent::ApplicationPaletteChange: {
1618 // runtime switch from/to dark mode
1620 return QMainWindow::event(e);
1624 return QMainWindow::event(e);
1628 void GuiView::resetWindowTitle()
1630 setWindowTitle(qt_("LyX"));
1633 bool GuiView::focusNextPrevChild(bool /*next*/)
1640 bool GuiView::busy() const
1646 void GuiView::setBusy(bool busy)
1648 bool const busy_before = busy_ > 0;
1649 busy ? ++busy_ : --busy_;
1650 if ((busy_ > 0) == busy_before)
1651 // busy state didn't change
1655 QApplication::setOverrideCursor(Qt::WaitCursor);
1658 QApplication::restoreOverrideCursor();
1663 void GuiView::resetCommandExecute()
1665 command_execute_ = false;
1670 double GuiView::pixelRatio() const
1672 #if QT_VERSION >= 0x050000
1673 return qt_scale_factor * devicePixelRatio();
1680 GuiWorkArea * GuiView::workArea(int index)
1682 if (TabWorkArea * twa = d.currentTabWorkArea())
1683 if (index < twa->count())
1684 return twa->workArea(index);
1689 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1691 if (currentWorkArea()
1692 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1693 return currentWorkArea();
1694 if (TabWorkArea * twa = d.currentTabWorkArea())
1695 return twa->workArea(buffer);
1700 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1702 // Automatically create a TabWorkArea if there are none yet.
1703 TabWorkArea * tab_widget = d.splitter_->count()
1704 ? d.currentTabWorkArea() : addTabWorkArea();
1705 return tab_widget->addWorkArea(buffer, *this);
1709 TabWorkArea * GuiView::addTabWorkArea()
1711 TabWorkArea * twa = new TabWorkArea;
1712 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1713 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1714 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1715 this, SLOT(on_lastWorkAreaRemoved()));
1717 d.splitter_->addWidget(twa);
1718 d.stack_widget_->setCurrentWidget(d.splitter_);
1723 GuiWorkArea const * GuiView::currentWorkArea() const
1725 return d.current_work_area_;
1729 GuiWorkArea * GuiView::currentWorkArea()
1731 return d.current_work_area_;
1735 GuiWorkArea const * GuiView::currentMainWorkArea() const
1737 if (!d.currentTabWorkArea())
1739 return d.currentTabWorkArea()->currentWorkArea();
1743 GuiWorkArea * GuiView::currentMainWorkArea()
1745 if (!d.currentTabWorkArea())
1747 return d.currentTabWorkArea()->currentWorkArea();
1751 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1753 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1755 d.current_work_area_ = nullptr;
1757 Q_EMIT bufferViewChanged();
1761 // FIXME: I've no clue why this is here and why it accesses
1762 // theGuiApp()->currentView, which might be 0 (bug 6464).
1763 // See also 27525 (vfr).
1764 if (theGuiApp()->currentView() == this
1765 && theGuiApp()->currentView()->currentWorkArea() == wa)
1768 if (currentBufferView())
1769 cap::saveSelection(currentBufferView()->cursor());
1771 theGuiApp()->setCurrentView(this);
1772 d.current_work_area_ = wa;
1774 // We need to reset this now, because it will need to be
1775 // right if the tabWorkArea gets reset in the for loop. We
1776 // will change it back if we aren't in that case.
1777 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1778 d.current_main_work_area_ = wa;
1780 for (int i = 0; i != d.splitter_->count(); ++i) {
1781 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1782 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1783 << ", Current main wa: " << currentMainWorkArea());
1788 d.current_main_work_area_ = old_cmwa;
1790 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1791 on_currentWorkAreaChanged(wa);
1792 BufferView & bv = wa->bufferView();
1793 bv.cursor().fixIfBroken();
1795 wa->setUpdatesEnabled(true);
1796 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1800 void GuiView::removeWorkArea(GuiWorkArea * wa)
1802 LASSERT(wa, return);
1803 if (wa == d.current_work_area_) {
1805 disconnectBufferView();
1806 d.current_work_area_ = nullptr;
1807 d.current_main_work_area_ = nullptr;
1810 bool found_twa = false;
1811 for (int i = 0; i != d.splitter_->count(); ++i) {
1812 TabWorkArea * twa = d.tabWorkArea(i);
1813 if (twa->removeWorkArea(wa)) {
1814 // Found in this tab group, and deleted the GuiWorkArea.
1816 if (twa->count() != 0) {
1817 if (d.current_work_area_ == nullptr)
1818 // This means that we are closing the current GuiWorkArea, so
1819 // switch to the next GuiWorkArea in the found TabWorkArea.
1820 setCurrentWorkArea(twa->currentWorkArea());
1822 // No more WorkAreas in this tab group, so delete it.
1829 // It is not a tabbed work area (i.e., the search work area), so it
1830 // should be deleted by other means.
1831 LASSERT(found_twa, return);
1833 if (d.current_work_area_ == nullptr) {
1834 if (d.splitter_->count() != 0) {
1835 TabWorkArea * twa = d.currentTabWorkArea();
1836 setCurrentWorkArea(twa->currentWorkArea());
1838 // No more work areas, switch to the background widget.
1839 setCurrentWorkArea(nullptr);
1845 LayoutBox * GuiView::getLayoutDialog() const
1851 void GuiView::updateLayoutList()
1854 d.layout_->updateContents(false);
1858 void GuiView::updateToolbars()
1860 if (d.current_work_area_) {
1862 if (d.current_work_area_->bufferView().cursor().inMathed()
1863 && !d.current_work_area_->bufferView().cursor().inRegexped())
1864 context |= Toolbars::MATH;
1865 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1866 context |= Toolbars::TABLE;
1867 if (currentBufferView()->buffer().areChangesPresent()
1868 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1869 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1870 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1871 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1872 context |= Toolbars::REVIEW;
1873 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1874 context |= Toolbars::MATHMACROTEMPLATE;
1875 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1876 context |= Toolbars::IPA;
1877 if (command_execute_)
1878 context |= Toolbars::MINIBUFFER;
1879 if (minibuffer_focus_) {
1880 context |= Toolbars::MINIBUFFER_FOCUS;
1881 minibuffer_focus_ = false;
1884 for (auto const & tb_p : d.toolbars_)
1885 tb_p.second->update(context);
1887 for (auto const & tb_p : d.toolbars_)
1888 tb_p.second->update();
1892 void GuiView::refillToolbars()
1894 DynamicMenuButton::resetIconCache();
1895 for (auto const & tb_p : d.toolbars_)
1896 tb_p.second->refill();
1900 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1902 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1903 LASSERT(newBuffer, return);
1905 GuiWorkArea * wa = workArea(*newBuffer);
1906 if (wa == nullptr) {
1908 newBuffer->masterBuffer()->updateBuffer();
1910 wa = addWorkArea(*newBuffer);
1911 // scroll to the position when the BufferView was last closed
1912 if (lyxrc.use_lastfilepos) {
1913 LastFilePosSection::FilePos filepos =
1914 theSession().lastFilePos().load(newBuffer->fileName());
1915 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1918 //Disconnect the old buffer...there's no new one.
1921 connectBuffer(*newBuffer);
1922 connectBufferView(wa->bufferView());
1924 setCurrentWorkArea(wa);
1928 void GuiView::connectBuffer(Buffer & buf)
1930 buf.setGuiDelegate(this);
1934 void GuiView::disconnectBuffer()
1936 if (d.current_work_area_)
1937 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1941 void GuiView::connectBufferView(BufferView & bv)
1943 bv.setGuiDelegate(this);
1947 void GuiView::disconnectBufferView()
1949 if (d.current_work_area_)
1950 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1954 void GuiView::errors(string const & error_type, bool from_master)
1956 BufferView const * const bv = currentBufferView();
1960 ErrorList const & el = from_master ?
1961 bv->buffer().masterBuffer()->errorList(error_type) :
1962 bv->buffer().errorList(error_type);
1967 string err = error_type;
1969 err = "from_master|" + error_type;
1970 showDialog("errorlist", err);
1974 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1976 d.toc_models_.updateItem(toqstr(type), dit);
1980 void GuiView::structureChanged()
1982 // This is called from the Buffer, which has no way to ensure that cursors
1983 // in BufferView remain valid.
1984 if (documentBufferView())
1985 documentBufferView()->cursor().sanitize();
1986 // FIXME: This is slightly expensive, though less than the tocBackend update
1987 // (#9880). This also resets the view in the Toc Widget (#6675).
1988 d.toc_models_.reset(documentBufferView());
1989 // Navigator needs more than a simple update in this case. It needs to be
1991 updateDialog("toc", "");
1995 void GuiView::updateDialog(string const & name, string const & sdata)
1997 if (!isDialogVisible(name))
2000 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2001 if (it == d.dialogs_.end())
2004 Dialog * const dialog = it->second.get();
2005 if (dialog->isVisibleView())
2006 dialog->initialiseParams(sdata);
2010 BufferView * GuiView::documentBufferView()
2012 return currentMainWorkArea()
2013 ? ¤tMainWorkArea()->bufferView()
2018 BufferView const * GuiView::documentBufferView() const
2020 return currentMainWorkArea()
2021 ? ¤tMainWorkArea()->bufferView()
2026 BufferView * GuiView::currentBufferView()
2028 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2032 BufferView const * GuiView::currentBufferView() const
2034 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2038 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2039 Buffer const * orig, Buffer * clone)
2041 bool const success = clone->autoSave();
2043 busyBuffers.remove(orig);
2045 ? _("Automatic save done.")
2046 : _("Automatic save failed!");
2050 void GuiView::autoSave()
2052 LYXERR(Debug::INFO, "Running autoSave()");
2054 Buffer * buffer = documentBufferView()
2055 ? &documentBufferView()->buffer() : nullptr;
2057 resetAutosaveTimers();
2061 GuiViewPrivate::busyBuffers.insert(buffer);
2062 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2063 buffer, buffer->cloneBufferOnly());
2064 d.autosave_watcher_.setFuture(f);
2065 resetAutosaveTimers();
2069 void GuiView::resetAutosaveTimers()
2072 d.autosave_timeout_.restart();
2076 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2079 Buffer * buf = currentBufferView()
2080 ? ¤tBufferView()->buffer() : nullptr;
2081 Buffer * doc_buffer = documentBufferView()
2082 ? &(documentBufferView()->buffer()) : nullptr;
2085 /* In LyX/Mac, when a dialog is open, the menus of the
2086 application can still be accessed without giving focus to
2087 the main window. In this case, we want to disable the menu
2088 entries that are buffer-related.
2089 This code must not be used on Linux and Windows, since it
2090 would disable buffer-related entries when hovering over the
2091 menu (see bug #9574).
2093 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2099 // Check whether we need a buffer
2100 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2101 // no, exit directly
2102 flag.message(from_utf8(N_("Command not allowed with"
2103 "out any document open")));
2104 flag.setEnabled(false);
2108 if (cmd.origin() == FuncRequest::TOC) {
2109 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2110 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2111 flag.setEnabled(false);
2115 switch(cmd.action()) {
2116 case LFUN_BUFFER_IMPORT:
2119 case LFUN_MASTER_BUFFER_EXPORT:
2121 && (doc_buffer->parent() != nullptr
2122 || doc_buffer->hasChildren())
2123 && !d.processing_thread_watcher_.isRunning()
2124 // this launches a dialog, which would be in the wrong Buffer
2125 && !(::lyx::operator==(cmd.argument(), "custom"));
2128 case LFUN_MASTER_BUFFER_UPDATE:
2129 case LFUN_MASTER_BUFFER_VIEW:
2131 && (doc_buffer->parent() != nullptr
2132 || doc_buffer->hasChildren())
2133 && !d.processing_thread_watcher_.isRunning();
2136 case LFUN_BUFFER_UPDATE:
2137 case LFUN_BUFFER_VIEW: {
2138 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2142 string format = to_utf8(cmd.argument());
2143 if (cmd.argument().empty())
2144 format = doc_buffer->params().getDefaultOutputFormat();
2145 enable = doc_buffer->params().isExportable(format, true);
2149 case LFUN_BUFFER_RELOAD:
2150 enable = doc_buffer && !doc_buffer->isUnnamed()
2151 && doc_buffer->fileName().exists()
2152 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2155 case LFUN_BUFFER_RESET_EXPORT:
2156 enable = doc_buffer != nullptr;
2159 case LFUN_BUFFER_CHILD_OPEN:
2160 enable = doc_buffer != nullptr;
2163 case LFUN_MASTER_BUFFER_FORALL: {
2164 if (doc_buffer == nullptr) {
2165 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2169 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2170 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2171 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2176 for (Buffer * buf : doc_buffer->allRelatives()) {
2177 GuiWorkArea * wa = workArea(*buf);
2180 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2181 enable = flag.enabled();
2188 case LFUN_BUFFER_WRITE:
2189 enable = doc_buffer && (doc_buffer->isUnnamed()
2190 || (!doc_buffer->isClean()
2191 || cmd.argument() == "force"));
2194 //FIXME: This LFUN should be moved to GuiApplication.
2195 case LFUN_BUFFER_WRITE_ALL: {
2196 // We enable the command only if there are some modified buffers
2197 Buffer * first = theBufferList().first();
2202 // We cannot use a for loop as the buffer list is a cycle.
2204 if (!b->isClean()) {
2208 b = theBufferList().next(b);
2209 } while (b != first);
2213 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2214 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2217 case LFUN_BUFFER_EXPORT: {
2218 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2222 return doc_buffer->getStatus(cmd, flag);
2225 case LFUN_BUFFER_EXPORT_AS:
2226 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2231 case LFUN_BUFFER_WRITE_AS:
2232 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2233 enable = doc_buffer != nullptr;
2236 case LFUN_EXPORT_CANCEL:
2237 enable = d.processing_thread_watcher_.isRunning();
2240 case LFUN_BUFFER_CLOSE:
2241 case LFUN_VIEW_CLOSE:
2242 enable = doc_buffer != nullptr;
2245 case LFUN_BUFFER_CLOSE_ALL:
2246 enable = theBufferList().last() != theBufferList().first();
2249 case LFUN_BUFFER_CHKTEX: {
2250 // hide if we have no checktex command
2251 if (lyxrc.chktex_command.empty()) {
2252 flag.setUnknown(true);
2256 if (!doc_buffer || !doc_buffer->params().isLatex()
2257 || d.processing_thread_watcher_.isRunning()) {
2258 // grey out, don't hide
2266 case LFUN_VIEW_SPLIT:
2267 if (cmd.getArg(0) == "vertical")
2268 enable = doc_buffer && (d.splitter_->count() == 1 ||
2269 d.splitter_->orientation() == Qt::Vertical);
2271 enable = doc_buffer && (d.splitter_->count() == 1 ||
2272 d.splitter_->orientation() == Qt::Horizontal);
2275 case LFUN_TAB_GROUP_CLOSE:
2276 enable = d.tabWorkAreaCount() > 1;
2279 case LFUN_DEVEL_MODE_TOGGLE:
2280 flag.setOnOff(devel_mode_);
2283 case LFUN_TOOLBAR_SET: {
2284 string const name = cmd.getArg(0);
2285 string const state = cmd.getArg(1);
2286 if (name.empty() || state.empty()) {
2288 docstring const msg =
2289 _("Function toolbar-set requires two arguments!");
2293 if (state != "on" && state != "off" && state != "auto") {
2295 docstring const msg =
2296 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2301 if (GuiToolbar * t = toolbar(name)) {
2302 bool const autovis = t->visibility() & Toolbars::AUTO;
2304 flag.setOnOff(t->isVisible() && !autovis);
2305 else if (state == "off")
2306 flag.setOnOff(!t->isVisible() && !autovis);
2307 else if (state == "auto")
2308 flag.setOnOff(autovis);
2311 docstring const msg =
2312 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2318 case LFUN_TOOLBAR_TOGGLE: {
2319 string const name = cmd.getArg(0);
2320 if (GuiToolbar * t = toolbar(name))
2321 flag.setOnOff(t->isVisible());
2324 docstring const msg =
2325 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2331 case LFUN_TOOLBAR_MOVABLE: {
2332 string const name = cmd.getArg(0);
2333 // use negation since locked == !movable
2335 // toolbar name * locks all toolbars
2336 flag.setOnOff(!toolbarsMovable_);
2337 else if (GuiToolbar * t = toolbar(name))
2338 flag.setOnOff(!(t->isMovable()));
2341 docstring const msg =
2342 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2348 case LFUN_ICON_SIZE:
2349 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2352 case LFUN_DROP_LAYOUTS_CHOICE:
2353 enable = buf != nullptr;
2356 case LFUN_UI_TOGGLE:
2357 if (cmd.argument() == "zoom") {
2358 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2359 } else if (cmd.argument() == "zoomslider") {
2360 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2362 flag.setOnOff(isFullScreen());
2365 case LFUN_DIALOG_DISCONNECT_INSET:
2368 case LFUN_DIALOG_HIDE:
2369 // FIXME: should we check if the dialog is shown?
2372 case LFUN_DIALOG_TOGGLE:
2373 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2376 case LFUN_DIALOG_SHOW: {
2377 string const name = cmd.getArg(0);
2379 enable = name == "aboutlyx"
2380 || name == "file" //FIXME: should be removed.
2381 || name == "lyxfiles"
2383 || name == "texinfo"
2384 || name == "progress"
2385 || name == "compare";
2386 else if (name == "character" || name == "symbols"
2387 || name == "mathdelimiter" || name == "mathmatrix") {
2388 if (!buf || buf->isReadonly())
2391 Cursor const & cur = currentBufferView()->cursor();
2392 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2395 else if (name == "latexlog")
2396 enable = FileName(doc_buffer->logName()).isReadableFile();
2397 else if (name == "spellchecker")
2398 enable = theSpellChecker()
2399 && !doc_buffer->text().empty();
2400 else if (name == "vclog")
2401 enable = doc_buffer->lyxvc().inUse();
2405 case LFUN_DIALOG_UPDATE: {
2406 string const name = cmd.getArg(0);
2408 enable = name == "prefs";
2412 case LFUN_COMMAND_EXECUTE:
2414 case LFUN_MENU_OPEN:
2415 // Nothing to check.
2418 case LFUN_COMPLETION_INLINE:
2419 if (!d.current_work_area_
2420 || !d.current_work_area_->completer().inlinePossible(
2421 currentBufferView()->cursor()))
2425 case LFUN_COMPLETION_POPUP:
2426 if (!d.current_work_area_
2427 || !d.current_work_area_->completer().popupPossible(
2428 currentBufferView()->cursor()))
2433 if (!d.current_work_area_
2434 || !d.current_work_area_->completer().inlinePossible(
2435 currentBufferView()->cursor()))
2439 case LFUN_COMPLETION_ACCEPT:
2440 if (!d.current_work_area_
2441 || (!d.current_work_area_->completer().popupVisible()
2442 && !d.current_work_area_->completer().inlineVisible()
2443 && !d.current_work_area_->completer().completionAvailable()))
2447 case LFUN_COMPLETION_CANCEL:
2448 if (!d.current_work_area_
2449 || (!d.current_work_area_->completer().popupVisible()
2450 && !d.current_work_area_->completer().inlineVisible()))
2454 case LFUN_BUFFER_ZOOM_OUT:
2455 case LFUN_BUFFER_ZOOM_IN: {
2456 // only diff between these two is that the default for ZOOM_OUT
2458 bool const neg_zoom =
2459 convert<int>(cmd.argument()) < 0 ||
2460 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2461 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2462 docstring const msg =
2463 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2467 enable = doc_buffer;
2471 case LFUN_BUFFER_ZOOM: {
2472 bool const less_than_min_zoom =
2473 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2474 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2475 docstring const msg =
2476 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2479 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2482 enable = doc_buffer;
2486 case LFUN_BUFFER_MOVE_NEXT:
2487 case LFUN_BUFFER_MOVE_PREVIOUS:
2488 // we do not cycle when moving
2489 case LFUN_BUFFER_NEXT:
2490 case LFUN_BUFFER_PREVIOUS:
2491 // because we cycle, it doesn't matter whether on first or last
2492 enable = (d.currentTabWorkArea()->count() > 1);
2494 case LFUN_BUFFER_SWITCH:
2495 // toggle on the current buffer, but do not toggle off
2496 // the other ones (is that a good idea?)
2498 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2499 flag.setOnOff(true);
2502 case LFUN_VC_REGISTER:
2503 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2505 case LFUN_VC_RENAME:
2506 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2509 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2511 case LFUN_VC_CHECK_IN:
2512 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2514 case LFUN_VC_CHECK_OUT:
2515 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2517 case LFUN_VC_LOCKING_TOGGLE:
2518 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2519 && doc_buffer->lyxvc().lockingToggleEnabled();
2520 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2522 case LFUN_VC_REVERT:
2523 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2524 && !doc_buffer->hasReadonlyFlag();
2526 case LFUN_VC_UNDO_LAST:
2527 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2529 case LFUN_VC_REPO_UPDATE:
2530 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2532 case LFUN_VC_COMMAND: {
2533 if (cmd.argument().empty())
2535 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2539 case LFUN_VC_COMPARE:
2540 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2543 case LFUN_SERVER_GOTO_FILE_ROW:
2544 case LFUN_LYX_ACTIVATE:
2545 case LFUN_WINDOW_RAISE:
2547 case LFUN_FORWARD_SEARCH:
2548 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2551 case LFUN_FILE_INSERT_PLAINTEXT:
2552 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2553 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2556 case LFUN_SPELLING_CONTINUOUSLY:
2557 flag.setOnOff(lyxrc.spellcheck_continuously);
2560 case LFUN_CITATION_OPEN:
2569 flag.setEnabled(false);
2575 static FileName selectTemplateFile()
2577 FileDialog dlg(qt_("Select template file"));
2578 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2579 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2581 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2582 QStringList(qt_("LyX Documents (*.lyx)")));
2584 if (result.first == FileDialog::Later)
2586 if (result.second.isEmpty())
2588 return FileName(fromqstr(result.second));
2592 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2596 Buffer * newBuffer = nullptr;
2598 newBuffer = checkAndLoadLyXFile(filename);
2599 } catch (ExceptionMessage const &) {
2606 message(_("Document not loaded."));
2610 setBuffer(newBuffer);
2611 newBuffer->errors("Parse");
2614 theSession().lastFiles().add(filename);
2615 theSession().writeFile();
2622 void GuiView::openDocument(string const & fname)
2624 string initpath = lyxrc.document_path;
2626 if (documentBufferView()) {
2627 string const trypath = documentBufferView()->buffer().filePath();
2628 // If directory is writeable, use this as default.
2629 if (FileName(trypath).isDirWritable())
2635 if (fname.empty()) {
2636 FileDialog dlg(qt_("Select document to open"));
2637 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2638 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2640 QStringList const filter({
2641 qt_("LyX Documents (*.lyx)"),
2642 qt_("LyX Document Backups (*.lyx~)"),
2643 qt_("All Files (*.*)")
2645 FileDialog::Result result =
2646 dlg.open(toqstr(initpath), filter);
2648 if (result.first == FileDialog::Later)
2651 filename = fromqstr(result.second);
2653 // check selected filename
2654 if (filename.empty()) {
2655 message(_("Canceled."));
2661 // get absolute path of file and add ".lyx" to the filename if
2663 FileName const fullname =
2664 fileSearch(string(), filename, "lyx", support::may_not_exist);
2665 if (!fullname.empty())
2666 filename = fullname.absFileName();
2668 if (!fullname.onlyPath().isDirectory()) {
2669 Alert::warning(_("Invalid filename"),
2670 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2671 from_utf8(fullname.absFileName())));
2675 // if the file doesn't exist and isn't already open (bug 6645),
2676 // let the user create one
2677 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2678 !LyXVC::file_not_found_hook(fullname)) {
2679 // the user specifically chose this name. Believe him.
2680 Buffer * const b = newFile(filename, string(), true);
2686 docstring const disp_fn = makeDisplayPath(filename);
2687 message(bformat(_("Opening document %1$s..."), disp_fn));
2690 Buffer * buf = loadDocument(fullname);
2692 str2 = bformat(_("Document %1$s opened."), disp_fn);
2693 if (buf->lyxvc().inUse())
2694 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2695 " " + _("Version control detected.");
2697 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2702 // FIXME: clean that
2703 static bool import(GuiView * lv, FileName const & filename,
2704 string const & format, ErrorList & errorList)
2706 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2708 string loader_format;
2709 vector<string> loaders = theConverters().loaders();
2710 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2711 for (string const & loader : loaders) {
2712 if (!theConverters().isReachable(format, loader))
2715 string const tofile =
2716 support::changeExtension(filename.absFileName(),
2717 theFormats().extension(loader));
2718 if (theConverters().convert(nullptr, filename, FileName(tofile),
2719 filename, format, loader, errorList) != Converters::SUCCESS)
2721 loader_format = loader;
2724 if (loader_format.empty()) {
2725 frontend::Alert::error(_("Couldn't import file"),
2726 bformat(_("No information for importing the format %1$s."),
2727 translateIfPossible(theFormats().prettyName(format))));
2731 loader_format = format;
2733 if (loader_format == "lyx") {
2734 Buffer * buf = lv->loadDocument(lyxfile);
2738 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2742 bool as_paragraphs = loader_format == "textparagraph";
2743 string filename2 = (loader_format == format) ? filename.absFileName()
2744 : support::changeExtension(filename.absFileName(),
2745 theFormats().extension(loader_format));
2746 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2748 guiApp->setCurrentView(lv);
2749 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2756 void GuiView::importDocument(string const & argument)
2759 string filename = split(argument, format, ' ');
2761 LYXERR(Debug::INFO, format << " file: " << filename);
2763 // need user interaction
2764 if (filename.empty()) {
2765 string initpath = lyxrc.document_path;
2766 if (documentBufferView()) {
2767 string const trypath = documentBufferView()->buffer().filePath();
2768 // If directory is writeable, use this as default.
2769 if (FileName(trypath).isDirWritable())
2773 docstring const text = bformat(_("Select %1$s file to import"),
2774 translateIfPossible(theFormats().prettyName(format)));
2776 FileDialog dlg(toqstr(text));
2777 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2778 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2780 docstring filter = translateIfPossible(theFormats().prettyName(format));
2783 filter += from_utf8(theFormats().extensions(format));
2786 FileDialog::Result result =
2787 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2789 if (result.first == FileDialog::Later)
2792 filename = fromqstr(result.second);
2794 // check selected filename
2795 if (filename.empty())
2796 message(_("Canceled."));
2799 if (filename.empty())
2802 // get absolute path of file
2803 FileName const fullname(support::makeAbsPath(filename));
2805 // Can happen if the user entered a path into the dialog
2807 if (fullname.onlyFileName().empty()) {
2808 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2809 "Aborting import."),
2810 from_utf8(fullname.absFileName()));
2811 frontend::Alert::error(_("File name error"), msg);
2812 message(_("Canceled."));
2817 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2819 // Check if the document already is open
2820 Buffer * buf = theBufferList().getBuffer(lyxfile);
2823 if (!closeBuffer()) {
2824 message(_("Canceled."));
2829 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2831 // if the file exists already, and we didn't do
2832 // -i lyx thefile.lyx, warn
2833 if (lyxfile.exists() && fullname != lyxfile) {
2835 docstring text = bformat(_("The document %1$s already exists.\n\n"
2836 "Do you want to overwrite that document?"), displaypath);
2837 int const ret = Alert::prompt(_("Overwrite document?"),
2838 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2841 message(_("Canceled."));
2846 message(bformat(_("Importing %1$s..."), displaypath));
2847 ErrorList errorList;
2848 if (import(this, fullname, format, errorList))
2849 message(_("imported."));
2851 message(_("file not imported!"));
2853 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2857 void GuiView::newDocument(string const & filename, string templatefile,
2860 FileName initpath(lyxrc.document_path);
2861 if (documentBufferView()) {
2862 FileName const trypath(documentBufferView()->buffer().filePath());
2863 // If directory is writeable, use this as default.
2864 if (trypath.isDirWritable())
2868 if (from_template) {
2869 if (templatefile.empty())
2870 templatefile = selectTemplateFile().absFileName();
2871 if (templatefile.empty())
2876 if (filename.empty())
2877 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2879 b = newFile(filename, templatefile, true);
2884 // If no new document could be created, it is unsure
2885 // whether there is a valid BufferView.
2886 if (currentBufferView())
2887 // Ensure the cursor is correctly positioned on screen.
2888 currentBufferView()->showCursor();
2892 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2894 BufferView * bv = documentBufferView();
2899 FileName filename(to_utf8(fname));
2900 if (filename.empty()) {
2901 // Launch a file browser
2903 string initpath = lyxrc.document_path;
2904 string const trypath = bv->buffer().filePath();
2905 // If directory is writeable, use this as default.
2906 if (FileName(trypath).isDirWritable())
2910 FileDialog dlg(qt_("Select LyX document to insert"));
2911 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2912 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2914 FileDialog::Result result = dlg.open(toqstr(initpath),
2915 QStringList(qt_("LyX Documents (*.lyx)")));
2917 if (result.first == FileDialog::Later)
2921 filename.set(fromqstr(result.second));
2923 // check selected filename
2924 if (filename.empty()) {
2925 // emit message signal.
2926 message(_("Canceled."));
2931 bv->insertLyXFile(filename, ignorelang);
2932 bv->buffer().errors("Parse");
2937 string const GuiView::getTemplatesPath(Buffer & b)
2939 // We start off with the user's templates path
2940 string result = addPath(package().user_support().absFileName(), "templates");
2941 // Check for the document language
2942 string const langcode = b.params().language->code();
2943 string const shortcode = langcode.substr(0, 2);
2944 if (!langcode.empty() && shortcode != "en") {
2945 string subpath = addPath(result, shortcode);
2946 string subpath_long = addPath(result, langcode);
2947 // If we have a subdirectory for the language already,
2949 FileName sp = FileName(subpath);
2950 if (sp.isDirectory())
2952 else if (FileName(subpath_long).isDirectory())
2953 result = subpath_long;
2955 // Ask whether we should create such a subdirectory
2956 docstring const text =
2957 bformat(_("It is suggested to save the template in a subdirectory\n"
2958 "appropriate to the document language (%1$s).\n"
2959 "This subdirectory does not exists yet.\n"
2960 "Do you want to create it?"),
2961 _(b.params().language->display()));
2962 if (Alert::prompt(_("Create Language Directory?"),
2963 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2964 // If the user agreed, we try to create it and report if this failed.
2965 if (!sp.createDirectory(0777))
2966 Alert::error(_("Subdirectory creation failed!"),
2967 _("Could not create subdirectory.\n"
2968 "The template will be saved in the parent directory."));
2974 // Do we have a layout category?
2975 string const cat = b.params().baseClass() ?
2976 b.params().baseClass()->category()
2979 string subpath = addPath(result, cat);
2980 // If we have a subdirectory for the category already,
2982 FileName sp = FileName(subpath);
2983 if (sp.isDirectory())
2986 // Ask whether we should create such a subdirectory
2987 docstring const text =
2988 bformat(_("It is suggested to save the template in a subdirectory\n"
2989 "appropriate to the layout category (%1$s).\n"
2990 "This subdirectory does not exists yet.\n"
2991 "Do you want to create it?"),
2993 if (Alert::prompt(_("Create Category Directory?"),
2994 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2995 // If the user agreed, we try to create it and report if this failed.
2996 if (!sp.createDirectory(0777))
2997 Alert::error(_("Subdirectory creation failed!"),
2998 _("Could not create subdirectory.\n"
2999 "The template will be saved in the parent directory."));
3009 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3011 FileName fname = b.fileName();
3012 FileName const oldname = fname;
3013 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3015 if (!newname.empty()) {
3018 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3020 fname = support::makeAbsPath(to_utf8(newname),
3021 oldname.onlyPath().absFileName());
3023 // Switch to this Buffer.
3026 // No argument? Ask user through dialog.
3028 QString const title = as_template ? qt_("Choose a filename to save template as")
3029 : qt_("Choose a filename to save document as");
3030 FileDialog dlg(title);
3031 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3032 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3034 if (!isLyXFileName(fname.absFileName()))
3035 fname.changeExtension(".lyx");
3037 string const path = as_template ?
3039 : fname.onlyPath().absFileName();
3040 FileDialog::Result result =
3041 dlg.save(toqstr(path),
3042 QStringList(qt_("LyX Documents (*.lyx)")),
3043 toqstr(fname.onlyFileName()));
3045 if (result.first == FileDialog::Later)
3048 fname.set(fromqstr(result.second));
3053 if (!isLyXFileName(fname.absFileName()))
3054 fname.changeExtension(".lyx");
3057 // fname is now the new Buffer location.
3059 // if there is already a Buffer open with this name, we do not want
3060 // to have another one. (the second test makes sure we're not just
3061 // trying to overwrite ourselves, which is fine.)
3062 if (theBufferList().exists(fname) && fname != oldname
3063 && theBufferList().getBuffer(fname) != &b) {
3064 docstring const text =
3065 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3066 "Please close it before attempting to overwrite it.\n"
3067 "Do you want to choose a new filename?"),
3068 from_utf8(fname.absFileName()));
3069 int const ret = Alert::prompt(_("Chosen File Already Open"),
3070 text, 0, 1, _("&Rename"), _("&Cancel"));
3072 case 0: return renameBuffer(b, docstring(), kind);
3073 case 1: return false;
3078 bool const existsLocal = fname.exists();
3079 bool const existsInVC = LyXVC::fileInVC(fname);
3080 if (existsLocal || existsInVC) {
3081 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3082 if (kind != LV_WRITE_AS && existsInVC) {
3083 // renaming to a name that is already in VC
3085 docstring text = bformat(_("The document %1$s "
3086 "is already registered.\n\n"
3087 "Do you want to choose a new name?"),
3089 docstring const title = (kind == LV_VC_RENAME) ?
3090 _("Rename document?") : _("Copy document?");
3091 docstring const button = (kind == LV_VC_RENAME) ?
3092 _("&Rename") : _("&Copy");
3093 int const ret = Alert::prompt(title, text, 0, 1,
3094 button, _("&Cancel"));
3096 case 0: return renameBuffer(b, docstring(), kind);
3097 case 1: return false;
3102 docstring text = bformat(_("The document %1$s "
3103 "already exists.\n\n"
3104 "Do you want to overwrite that document?"),
3106 int const ret = Alert::prompt(_("Overwrite document?"),
3107 text, 0, 2, _("&Overwrite"),
3108 _("&Rename"), _("&Cancel"));
3111 case 1: return renameBuffer(b, docstring(), kind);
3112 case 2: return false;
3118 case LV_VC_RENAME: {
3119 string msg = b.lyxvc().rename(fname);
3122 message(from_utf8(msg));
3126 string msg = b.lyxvc().copy(fname);
3129 message(from_utf8(msg));
3133 case LV_WRITE_AS_TEMPLATE:
3136 // LyXVC created the file already in case of LV_VC_RENAME or
3137 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3138 // relative paths of included stuff right if we moved e.g. from
3139 // /a/b.lyx to /a/c/b.lyx.
3141 bool const saved = saveBuffer(b, fname);
3148 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3150 FileName fname = b.fileName();
3152 FileDialog dlg(qt_("Choose a filename to export the document as"));
3153 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3156 QString const anyformat = qt_("Guess from extension (*.*)");
3159 vector<Format const *> export_formats;
3160 for (Format const & f : theFormats())
3161 if (f.documentFormat())
3162 export_formats.push_back(&f);
3163 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3164 map<QString, string> fmap;
3167 for (Format const * f : export_formats) {
3168 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3169 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3171 from_ascii(f->extension())));
3172 types << loc_filter;
3173 fmap[loc_filter] = f->name();
3174 if (from_ascii(f->name()) == iformat) {
3175 filter = loc_filter;
3176 ext = f->extension();
3179 string ofname = fname.onlyFileName();
3181 ofname = support::changeExtension(ofname, ext);
3182 FileDialog::Result result =
3183 dlg.save(toqstr(fname.onlyPath().absFileName()),
3187 if (result.first != FileDialog::Chosen)
3191 fname.set(fromqstr(result.second));
3192 if (filter == anyformat)
3193 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3195 fmt_name = fmap[filter];
3196 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3197 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3199 if (fmt_name.empty() || fname.empty())
3202 // fname is now the new Buffer location.
3203 if (fname.exists()) {
3204 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3205 docstring text = bformat(_("The document %1$s already "
3206 "exists.\n\nDo you want to "
3207 "overwrite that document?"),
3209 int const ret = Alert::prompt(_("Overwrite document?"),
3210 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3213 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3214 case 2: return false;
3218 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3221 return dr.dispatched();
3225 bool GuiView::saveBuffer(Buffer & b)
3227 return saveBuffer(b, FileName());
3231 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3233 if (workArea(b) && workArea(b)->inDialogMode())
3236 if (fn.empty() && b.isUnnamed())
3237 return renameBuffer(b, docstring());
3239 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3241 theSession().lastFiles().add(b.fileName());
3242 theSession().writeFile();
3246 // Switch to this Buffer.
3249 // FIXME: we don't tell the user *WHY* the save failed !!
3250 docstring const file = makeDisplayPath(b.absFileName(), 30);
3251 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3252 "Do you want to rename the document and "
3253 "try again?"), file);
3254 int const ret = Alert::prompt(_("Rename and save?"),
3255 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3258 if (!renameBuffer(b, docstring()))
3267 return saveBuffer(b, fn);
3271 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3273 return closeWorkArea(wa, false);
3277 // We only want to close the buffer if it is not visible in other workareas
3278 // of the same view, nor in other views, and if this is not a child
3279 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3281 Buffer & buf = wa->bufferView().buffer();
3283 bool last_wa = d.countWorkAreasOf(buf) == 1
3284 && !inOtherView(buf) && !buf.parent();
3286 bool close_buffer = last_wa;
3289 if (lyxrc.close_buffer_with_last_view == "yes")
3291 else if (lyxrc.close_buffer_with_last_view == "no")
3292 close_buffer = false;
3295 if (buf.isUnnamed())
3296 file = from_utf8(buf.fileName().onlyFileName());
3298 file = buf.fileName().displayName(30);
3299 docstring const text = bformat(
3300 _("Last view on document %1$s is being closed.\n"
3301 "Would you like to close or hide the document?\n"
3303 "Hidden documents can be displayed back through\n"
3304 "the menu: View->Hidden->...\n"
3306 "To remove this question, set your preference in:\n"
3307 " Tools->Preferences->Look&Feel->UserInterface\n"
3309 int ret = Alert::prompt(_("Close or hide document?"),
3310 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3313 close_buffer = (ret == 0);
3317 return closeWorkArea(wa, close_buffer);
3321 bool GuiView::closeBuffer()
3323 GuiWorkArea * wa = currentMainWorkArea();
3324 // coverity complained about this
3325 // it seems unnecessary, but perhaps is worth the check
3326 LASSERT(wa, return false);
3328 setCurrentWorkArea(wa);
3329 Buffer & buf = wa->bufferView().buffer();
3330 return closeWorkArea(wa, !buf.parent());
3334 void GuiView::writeSession() const {
3335 GuiWorkArea const * active_wa = currentMainWorkArea();
3336 for (int i = 0; i < d.splitter_->count(); ++i) {
3337 TabWorkArea * twa = d.tabWorkArea(i);
3338 for (int j = 0; j < twa->count(); ++j) {
3339 GuiWorkArea * wa = twa->workArea(j);
3340 Buffer & buf = wa->bufferView().buffer();
3341 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3347 bool GuiView::closeBufferAll()
3350 for (auto & buf : theBufferList()) {
3351 if (!saveBufferIfNeeded(*buf, false)) {
3352 // Closing has been cancelled, so abort.
3357 // Close the workareas in all other views
3358 QList<int> const ids = guiApp->viewIds();
3359 for (int i = 0; i != ids.size(); ++i) {
3360 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3364 // Close our own workareas
3365 if (!closeWorkAreaAll())
3372 bool GuiView::closeWorkAreaAll()
3374 setCurrentWorkArea(currentMainWorkArea());
3376 // We might be in a situation that there is still a tabWorkArea, but
3377 // there are no tabs anymore. This can happen when we get here after a
3378 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3379 // many TabWorkArea's have no documents anymore.
3382 // We have to call count() each time, because it can happen that
3383 // more than one splitter will disappear in one iteration (bug 5998).
3384 while (d.splitter_->count() > empty_twa) {
3385 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3387 if (twa->count() == 0)
3390 setCurrentWorkArea(twa->currentWorkArea());
3391 if (!closeTabWorkArea(twa))
3399 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3404 Buffer & buf = wa->bufferView().buffer();
3406 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3407 Alert::warning(_("Close document"),
3408 _("Document could not be closed because it is being processed by LyX."));
3413 return closeBuffer(buf);
3415 if (!inMultiTabs(wa))
3416 if (!saveBufferIfNeeded(buf, true))
3424 bool GuiView::closeBuffer(Buffer & buf)
3426 bool success = true;
3427 for (Buffer * child_buf : buf.getChildren()) {
3428 if (theBufferList().isOthersChild(&buf, child_buf)) {
3429 child_buf->setParent(nullptr);
3433 // FIXME: should we look in other tabworkareas?
3434 // ANSWER: I don't think so. I've tested, and if the child is
3435 // open in some other window, it closes without a problem.
3436 GuiWorkArea * child_wa = workArea(*child_buf);
3439 // If we are in a close_event all children will be closed in some time,
3440 // so no need to do it here. This will ensure that the children end up
3441 // in the session file in the correct order. If we close the master
3442 // buffer, we can close or release the child buffers here too.
3444 success = closeWorkArea(child_wa, true);
3448 // In this case the child buffer is open but hidden.
3449 // Even in this case, children can be dirty (e.g.,
3450 // after a label change in the master, see #11405).
3451 // Therefore, check this
3452 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3453 // If we are in a close_event all children will be closed in some time,
3454 // so no need to do it here. This will ensure that the children end up
3455 // in the session file in the correct order. If we close the master
3456 // buffer, we can close or release the child buffers here too.
3459 // Save dirty buffers also if closing_!
3460 if (saveBufferIfNeeded(*child_buf, false)) {
3461 child_buf->removeAutosaveFile();
3462 theBufferList().release(child_buf);
3464 // Saving of dirty children has been cancelled.
3465 // Cancel the whole process.
3472 // goto bookmark to update bookmark pit.
3473 // FIXME: we should update only the bookmarks related to this buffer!
3474 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3475 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3476 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3477 guiApp->gotoBookmark(i, false, false);
3479 if (saveBufferIfNeeded(buf, false)) {
3480 buf.removeAutosaveFile();
3481 theBufferList().release(&buf);
3485 // open all children again to avoid a crash because of dangling
3486 // pointers (bug 6603)
3492 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3494 while (twa == d.currentTabWorkArea()) {
3495 twa->setCurrentIndex(twa->count() - 1);
3497 GuiWorkArea * wa = twa->currentWorkArea();
3498 Buffer & b = wa->bufferView().buffer();
3500 // We only want to close the buffer if the same buffer is not visible
3501 // in another view, and if this is not a child and if we are closing
3502 // a view (not a tabgroup).
3503 bool const close_buffer =
3504 !inOtherView(b) && !b.parent() && closing_;
3506 if (!closeWorkArea(wa, close_buffer))
3513 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3515 if (buf.isClean() || buf.paragraphs().empty())
3518 // Switch to this Buffer.
3524 if (buf.isUnnamed()) {
3525 file = from_utf8(buf.fileName().onlyFileName());
3528 FileName filename = buf.fileName();
3530 file = filename.displayName(30);
3531 exists = filename.exists();
3534 // Bring this window to top before asking questions.
3539 if (hiding && buf.isUnnamed()) {
3540 docstring const text = bformat(_("The document %1$s has not been "
3541 "saved yet.\n\nDo you want to save "
3542 "the document?"), file);
3543 ret = Alert::prompt(_("Save new document?"),
3544 text, 0, 1, _("&Save"), _("&Cancel"));
3548 docstring const text = exists ?
3549 bformat(_("The document %1$s has unsaved changes."
3550 "\n\nDo you want to save the document or "
3551 "discard the changes?"), file) :
3552 bformat(_("The document %1$s has not been saved yet."
3553 "\n\nDo you want to save the document or "
3554 "discard it entirely?"), file);
3555 docstring const title = exists ?
3556 _("Save changed document?") : _("Save document?");
3557 ret = Alert::prompt(title, text, 0, 2,
3558 _("&Save"), _("&Discard"), _("&Cancel"));
3563 if (!saveBuffer(buf))
3567 // If we crash after this we could have no autosave file
3568 // but I guess this is really improbable (Jug).
3569 // Sometimes improbable things happen:
3570 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3571 // buf.removeAutosaveFile();
3573 // revert all changes
3584 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3586 Buffer & buf = wa->bufferView().buffer();
3588 for (int i = 0; i != d.splitter_->count(); ++i) {
3589 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3590 if (wa_ && wa_ != wa)
3593 return inOtherView(buf);
3597 bool GuiView::inOtherView(Buffer & buf)
3599 QList<int> const ids = guiApp->viewIds();
3601 for (int i = 0; i != ids.size(); ++i) {
3605 if (guiApp->view(ids[i]).workArea(buf))
3612 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3614 if (!documentBufferView())
3617 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3618 Buffer * const curbuf = &documentBufferView()->buffer();
3619 int nwa = twa->count();
3620 for (int i = 0; i < nwa; ++i) {
3621 if (&workArea(i)->bufferView().buffer() == curbuf) {
3623 if (np == NEXTBUFFER)
3624 next_index = (i == nwa - 1 ? 0 : i + 1);
3626 next_index = (i == 0 ? nwa - 1 : i - 1);
3628 twa->moveTab(i, next_index);
3630 setBuffer(&workArea(next_index)->bufferView().buffer());
3638 /// make sure the document is saved
3639 static bool ensureBufferClean(Buffer * buffer)
3641 LASSERT(buffer, return false);
3642 if (buffer->isClean() && !buffer->isUnnamed())
3645 docstring const file = buffer->fileName().displayName(30);
3648 if (!buffer->isUnnamed()) {
3649 text = bformat(_("The document %1$s has unsaved "
3650 "changes.\n\nDo you want to save "
3651 "the document?"), file);
3652 title = _("Save changed document?");
3655 text = bformat(_("The document %1$s has not been "
3656 "saved yet.\n\nDo you want to save "
3657 "the document?"), file);
3658 title = _("Save new document?");
3660 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3663 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3665 return buffer->isClean() && !buffer->isUnnamed();
3669 bool GuiView::reloadBuffer(Buffer & buf)
3671 currentBufferView()->cursor().reset();
3672 Buffer::ReadStatus status = buf.reload();
3673 return status == Buffer::ReadSuccess;
3677 void GuiView::checkExternallyModifiedBuffers()
3679 for (Buffer * buf : theBufferList()) {
3680 if (buf->fileName().exists() && buf->isChecksumModified()) {
3681 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3682 " Reload now? Any local changes will be lost."),
3683 from_utf8(buf->absFileName()));
3684 int const ret = Alert::prompt(_("Reload externally changed document?"),
3685 text, 0, 1, _("&Reload"), _("&Cancel"));
3693 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3695 Buffer * buffer = documentBufferView()
3696 ? &(documentBufferView()->buffer()) : nullptr;
3698 switch (cmd.action()) {
3699 case LFUN_VC_REGISTER:
3700 if (!buffer || !ensureBufferClean(buffer))
3702 if (!buffer->lyxvc().inUse()) {
3703 if (buffer->lyxvc().registrer()) {
3704 reloadBuffer(*buffer);
3705 dr.clearMessageUpdate();
3710 case LFUN_VC_RENAME:
3711 case LFUN_VC_COPY: {
3712 if (!buffer || !ensureBufferClean(buffer))
3714 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3715 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3716 // Some changes are not yet committed.
3717 // We test here and not in getStatus(), since
3718 // this test is expensive.
3720 LyXVC::CommandResult ret =
3721 buffer->lyxvc().checkIn(log);
3723 if (ret == LyXVC::ErrorCommand ||
3724 ret == LyXVC::VCSuccess)
3725 reloadBuffer(*buffer);
3726 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3727 frontend::Alert::error(
3728 _("Revision control error."),
3729 _("Document could not be checked in."));
3733 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3734 LV_VC_RENAME : LV_VC_COPY;
3735 renameBuffer(*buffer, cmd.argument(), kind);
3740 case LFUN_VC_CHECK_IN:
3741 if (!buffer || !ensureBufferClean(buffer))
3743 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3745 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3747 // Only skip reloading if the checkin was cancelled or
3748 // an error occurred before the real checkin VCS command
3749 // was executed, since the VCS might have changed the
3750 // file even if it could not checkin successfully.
3751 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3752 reloadBuffer(*buffer);
3756 case LFUN_VC_CHECK_OUT:
3757 if (!buffer || !ensureBufferClean(buffer))
3759 if (buffer->lyxvc().inUse()) {
3760 dr.setMessage(buffer->lyxvc().checkOut());
3761 reloadBuffer(*buffer);
3765 case LFUN_VC_LOCKING_TOGGLE:
3766 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3768 if (buffer->lyxvc().inUse()) {
3769 string res = buffer->lyxvc().lockingToggle();
3771 frontend::Alert::error(_("Revision control error."),
3772 _("Error when setting the locking property."));
3775 reloadBuffer(*buffer);
3780 case LFUN_VC_REVERT:
3783 if (buffer->lyxvc().revert()) {
3784 reloadBuffer(*buffer);
3785 dr.clearMessageUpdate();
3789 case LFUN_VC_UNDO_LAST:
3792 buffer->lyxvc().undoLast();
3793 reloadBuffer(*buffer);
3794 dr.clearMessageUpdate();
3797 case LFUN_VC_REPO_UPDATE:
3800 if (ensureBufferClean(buffer)) {
3801 dr.setMessage(buffer->lyxvc().repoUpdate());
3802 checkExternallyModifiedBuffers();
3806 case LFUN_VC_COMMAND: {
3807 string flag = cmd.getArg(0);
3808 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3811 if (contains(flag, 'M')) {
3812 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3815 string path = cmd.getArg(1);
3816 if (contains(path, "$$p") && buffer)
3817 path = subst(path, "$$p", buffer->filePath());
3818 LYXERR(Debug::LYXVC, "Directory: " << path);
3820 if (!pp.isReadableDirectory()) {
3821 lyxerr << _("Directory is not accessible.") << endl;
3824 support::PathChanger p(pp);
3826 string command = cmd.getArg(2);
3827 if (command.empty())
3830 command = subst(command, "$$i", buffer->absFileName());
3831 command = subst(command, "$$p", buffer->filePath());
3833 command = subst(command, "$$m", to_utf8(message));
3834 LYXERR(Debug::LYXVC, "Command: " << command);
3836 one.startscript(Systemcall::Wait, command);
3840 if (contains(flag, 'I'))
3841 buffer->markDirty();
3842 if (contains(flag, 'R'))
3843 reloadBuffer(*buffer);
3848 case LFUN_VC_COMPARE: {
3849 if (cmd.argument().empty()) {
3850 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3856 string rev1 = cmd.getArg(0);
3860 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3863 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3864 f2 = buffer->absFileName();
3866 string rev2 = cmd.getArg(1);
3870 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3874 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3875 f1 << "\n" << f2 << "\n" );
3876 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3877 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3887 void GuiView::openChildDocument(string const & fname)
3889 LASSERT(documentBufferView(), return);
3890 Buffer & buffer = documentBufferView()->buffer();
3891 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3892 documentBufferView()->saveBookmark(false);
3893 Buffer * child = nullptr;
3894 if (theBufferList().exists(filename)) {
3895 child = theBufferList().getBuffer(filename);
3898 message(bformat(_("Opening child document %1$s..."),
3899 makeDisplayPath(filename.absFileName())));
3900 child = loadDocument(filename, false);
3902 // Set the parent name of the child document.
3903 // This makes insertion of citations and references in the child work,
3904 // when the target is in the parent or another child document.
3906 child->setParent(&buffer);
3910 bool GuiView::goToFileRow(string const & argument)
3914 size_t i = argument.find_last_of(' ');
3915 if (i != string::npos) {
3916 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3917 istringstream is(argument.substr(i + 1));
3922 if (i == string::npos) {
3923 LYXERR0("Wrong argument: " << argument);
3926 Buffer * buf = nullptr;
3927 string const realtmp = package().temp_dir().realPath();
3928 // We have to use os::path_prefix_is() here, instead of
3929 // simply prefixIs(), because the file name comes from
3930 // an external application and may need case adjustment.
3931 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3932 buf = theBufferList().getBufferFromTmp(file_name, true);
3933 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3934 << (buf ? " success" : " failed"));
3936 // Must replace extension of the file to be .lyx
3937 // and get full path
3938 FileName const s = fileSearch(string(),
3939 support::changeExtension(file_name, ".lyx"), "lyx");
3940 // Either change buffer or load the file
3941 if (theBufferList().exists(s))
3942 buf = theBufferList().getBuffer(s);
3943 else if (s.exists()) {
3944 buf = loadDocument(s);
3949 _("File does not exist: %1$s"),
3950 makeDisplayPath(file_name)));
3956 _("No buffer for file: %1$s."),
3957 makeDisplayPath(file_name))
3962 bool success = documentBufferView()->setCursorFromRow(row);
3964 LYXERR(Debug::LATEX,
3965 "setCursorFromRow: invalid position for row " << row);
3966 frontend::Alert::error(_("Inverse Search Failed"),
3967 _("Invalid position requested by inverse search.\n"
3968 "You may need to update the viewed document."));
3974 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3976 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3977 menu->exec(QCursor::pos());
3982 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3983 Buffer const * orig, Buffer * clone, string const & format)
3985 Buffer::ExportStatus const status = func(format);
3987 // the cloning operation will have produced a clone of the entire set of
3988 // documents, starting from the master. so we must delete those.
3989 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3991 busyBuffers.remove(orig);
3996 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3997 Buffer const * orig, Buffer * clone, string const & format)
3999 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4001 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4005 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4006 Buffer const * orig, Buffer * clone, string const & format)
4008 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4010 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4014 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4015 Buffer const * orig, Buffer * clone, string const & format)
4017 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4019 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4023 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4024 Buffer const * used_buffer,
4025 docstring const & msg,
4026 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4027 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4028 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4029 bool allow_async, bool use_tmpdir)
4034 string format = argument;
4036 format = used_buffer->params().getDefaultOutputFormat();
4037 processing_format = format;
4039 progress_->clearMessages();
4042 #if EXPORT_in_THREAD
4044 GuiViewPrivate::busyBuffers.insert(used_buffer);
4045 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4046 if (!cloned_buffer) {
4047 Alert::error(_("Export Error"),
4048 _("Error cloning the Buffer."));
4051 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4056 setPreviewFuture(f);
4057 last_export_format = used_buffer->params().bufferFormat();
4060 // We are asynchronous, so we don't know here anything about the success
4063 Buffer::ExportStatus status;
4065 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4066 } else if (previewFunc) {
4067 status = (used_buffer->*previewFunc)(format);
4070 handleExportStatus(gv_, status, format);
4072 return (status == Buffer::ExportSuccess
4073 || status == Buffer::PreviewSuccess);
4077 Buffer::ExportStatus status;
4079 status = (used_buffer->*syncFunc)(format, true);
4080 } else if (previewFunc) {
4081 status = (used_buffer->*previewFunc)(format);
4084 handleExportStatus(gv_, status, format);
4086 return (status == Buffer::ExportSuccess
4087 || status == Buffer::PreviewSuccess);
4091 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4093 BufferView * bv = currentBufferView();
4094 LASSERT(bv, return);
4096 // Let the current BufferView dispatch its own actions.
4097 bv->dispatch(cmd, dr);
4098 if (dr.dispatched()) {
4099 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4100 updateDialog("document", "");
4104 // Try with the document BufferView dispatch if any.
4105 BufferView * doc_bv = documentBufferView();
4106 if (doc_bv && doc_bv != bv) {
4107 doc_bv->dispatch(cmd, dr);
4108 if (dr.dispatched()) {
4109 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4110 updateDialog("document", "");
4115 // Then let the current Cursor dispatch its own actions.
4116 bv->cursor().dispatch(cmd);
4118 // update completion. We do it here and not in
4119 // processKeySym to avoid another redraw just for a
4120 // changed inline completion
4121 if (cmd.origin() == FuncRequest::KEYBOARD) {
4122 if (cmd.action() == LFUN_SELF_INSERT
4123 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4124 updateCompletion(bv->cursor(), true, true);
4125 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4126 updateCompletion(bv->cursor(), false, true);
4128 updateCompletion(bv->cursor(), false, false);
4131 dr = bv->cursor().result();
4135 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4137 BufferView * bv = currentBufferView();
4138 // By default we won't need any update.
4139 dr.screenUpdate(Update::None);
4140 // assume cmd will be dispatched
4141 dr.dispatched(true);
4143 Buffer * doc_buffer = documentBufferView()
4144 ? &(documentBufferView()->buffer()) : nullptr;
4146 if (cmd.origin() == FuncRequest::TOC) {
4147 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4148 toc->doDispatch(bv->cursor(), cmd, dr);
4152 string const argument = to_utf8(cmd.argument());
4154 switch(cmd.action()) {
4155 case LFUN_BUFFER_CHILD_OPEN:
4156 openChildDocument(to_utf8(cmd.argument()));
4159 case LFUN_BUFFER_IMPORT:
4160 importDocument(to_utf8(cmd.argument()));
4163 case LFUN_MASTER_BUFFER_EXPORT:
4165 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4167 case LFUN_BUFFER_EXPORT: {
4170 // GCC only sees strfwd.h when building merged
4171 if (::lyx::operator==(cmd.argument(), "custom")) {
4172 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4173 // so the following test should not be needed.
4174 // In principle, we could try to switch to such a view...
4175 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4176 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4180 string const dest = cmd.getArg(1);
4181 FileName target_dir;
4182 if (!dest.empty() && FileName::isAbsolute(dest))
4183 target_dir = FileName(support::onlyPath(dest));
4185 target_dir = doc_buffer->fileName().onlyPath();
4187 string const format = (argument.empty() || argument == "default") ?
4188 doc_buffer->params().getDefaultOutputFormat() : argument;
4190 if ((dest.empty() && doc_buffer->isUnnamed())
4191 || !target_dir.isDirWritable()) {
4192 exportBufferAs(*doc_buffer, from_utf8(format));
4195 /* TODO/Review: Is it a problem to also export the children?
4196 See the update_unincluded flag */
4197 d.asyncBufferProcessing(format,
4200 &GuiViewPrivate::exportAndDestroy,
4202 nullptr, cmd.allowAsync());
4203 // TODO Inform user about success
4207 case LFUN_BUFFER_EXPORT_AS: {
4208 LASSERT(doc_buffer, break);
4209 docstring f = cmd.argument();
4211 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4212 exportBufferAs(*doc_buffer, f);
4216 case LFUN_BUFFER_UPDATE: {
4217 d.asyncBufferProcessing(argument,
4220 &GuiViewPrivate::compileAndDestroy,
4222 nullptr, cmd.allowAsync(), true);
4225 case LFUN_BUFFER_VIEW: {
4226 d.asyncBufferProcessing(argument,
4228 _("Previewing ..."),
4229 &GuiViewPrivate::previewAndDestroy,
4231 &Buffer::preview, cmd.allowAsync());
4234 case LFUN_MASTER_BUFFER_UPDATE: {
4235 d.asyncBufferProcessing(argument,
4236 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4238 &GuiViewPrivate::compileAndDestroy,
4240 nullptr, cmd.allowAsync(), true);
4243 case LFUN_MASTER_BUFFER_VIEW: {
4244 d.asyncBufferProcessing(argument,
4245 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4247 &GuiViewPrivate::previewAndDestroy,
4248 nullptr, &Buffer::preview, cmd.allowAsync());
4251 case LFUN_EXPORT_CANCEL: {
4252 Systemcall::killscript();
4255 case LFUN_BUFFER_SWITCH: {
4256 string const file_name = to_utf8(cmd.argument());
4257 if (!FileName::isAbsolute(file_name)) {
4259 dr.setMessage(_("Absolute filename expected."));
4263 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4266 dr.setMessage(_("Document not loaded"));
4270 // Do we open or switch to the buffer in this view ?
4271 if (workArea(*buffer)
4272 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4277 // Look for the buffer in other views
4278 QList<int> const ids = guiApp->viewIds();
4280 for (; i != ids.size(); ++i) {
4281 GuiView & gv = guiApp->view(ids[i]);
4282 if (gv.workArea(*buffer)) {
4284 gv.activateWindow();
4286 gv.setBuffer(buffer);
4291 // If necessary, open a new window as a last resort
4292 if (i == ids.size()) {
4293 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4299 case LFUN_BUFFER_NEXT:
4300 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4303 case LFUN_BUFFER_MOVE_NEXT:
4304 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4307 case LFUN_BUFFER_PREVIOUS:
4308 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4311 case LFUN_BUFFER_MOVE_PREVIOUS:
4312 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4315 case LFUN_BUFFER_CHKTEX:
4316 LASSERT(doc_buffer, break);
4317 doc_buffer->runChktex();
4320 case LFUN_COMMAND_EXECUTE: {
4321 command_execute_ = true;
4322 minibuffer_focus_ = true;
4325 case LFUN_DROP_LAYOUTS_CHOICE:
4326 d.layout_->showPopup();
4329 case LFUN_MENU_OPEN:
4330 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4331 menu->exec(QCursor::pos());
4334 case LFUN_FILE_INSERT: {
4335 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4336 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4337 dr.forceBufferUpdate();
4338 dr.screenUpdate(Update::Force);
4343 case LFUN_FILE_INSERT_PLAINTEXT:
4344 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4345 string const fname = to_utf8(cmd.argument());
4346 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4347 dr.setMessage(_("Absolute filename expected."));
4351 FileName filename(fname);
4352 if (fname.empty()) {
4353 FileDialog dlg(qt_("Select file to insert"));
4355 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4356 QStringList(qt_("All Files (*)")));
4358 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4359 dr.setMessage(_("Canceled."));
4363 filename.set(fromqstr(result.second));
4367 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4368 bv->dispatch(new_cmd, dr);
4373 case LFUN_BUFFER_RELOAD: {
4374 LASSERT(doc_buffer, break);
4377 bool drop = (cmd.argument() == "dump");
4380 if (!drop && !doc_buffer->isClean()) {
4381 docstring const file =
4382 makeDisplayPath(doc_buffer->absFileName(), 20);
4383 if (doc_buffer->notifiesExternalModification()) {
4384 docstring text = _("The current version will be lost. "
4385 "Are you sure you want to load the version on disk "
4386 "of the document %1$s?");
4387 ret = Alert::prompt(_("Reload saved document?"),
4388 bformat(text, file), 1, 1,
4389 _("&Reload"), _("&Cancel"));
4391 docstring text = _("Any changes will be lost. "
4392 "Are you sure you want to revert to the saved version "
4393 "of the document %1$s?");
4394 ret = Alert::prompt(_("Revert to saved document?"),
4395 bformat(text, file), 1, 1,
4396 _("&Revert"), _("&Cancel"));
4401 doc_buffer->markClean();
4402 reloadBuffer(*doc_buffer);
4403 dr.forceBufferUpdate();
4408 case LFUN_BUFFER_RESET_EXPORT:
4409 LASSERT(doc_buffer, break);
4410 doc_buffer->requireFreshStart(true);
4411 dr.setMessage(_("Buffer export reset."));
4414 case LFUN_BUFFER_WRITE:
4415 LASSERT(doc_buffer, break);
4416 saveBuffer(*doc_buffer);
4419 case LFUN_BUFFER_WRITE_AS:
4420 LASSERT(doc_buffer, break);
4421 renameBuffer(*doc_buffer, cmd.argument());
4424 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4425 LASSERT(doc_buffer, break);
4426 renameBuffer(*doc_buffer, cmd.argument(),
4427 LV_WRITE_AS_TEMPLATE);
4430 case LFUN_BUFFER_WRITE_ALL: {
4431 Buffer * first = theBufferList().first();
4434 message(_("Saving all documents..."));
4435 // We cannot use a for loop as the buffer list cycles.
4438 if (!b->isClean()) {
4440 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4442 b = theBufferList().next(b);
4443 } while (b != first);
4444 dr.setMessage(_("All documents saved."));
4448 case LFUN_MASTER_BUFFER_FORALL: {
4452 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4453 funcToRun.allowAsync(false);
4455 for (Buffer const * buf : doc_buffer->allRelatives()) {
4456 // Switch to other buffer view and resend cmd
4457 lyx::dispatch(FuncRequest(
4458 LFUN_BUFFER_SWITCH, buf->absFileName()));
4459 lyx::dispatch(funcToRun);
4462 lyx::dispatch(FuncRequest(
4463 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4467 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4468 LASSERT(doc_buffer, break);
4469 doc_buffer->clearExternalModification();
4472 case LFUN_BUFFER_CLOSE:
4476 case LFUN_BUFFER_CLOSE_ALL:
4480 case LFUN_DEVEL_MODE_TOGGLE:
4481 devel_mode_ = !devel_mode_;
4483 dr.setMessage(_("Developer mode is now enabled."));
4485 dr.setMessage(_("Developer mode is now disabled."));
4488 case LFUN_TOOLBAR_SET: {
4489 string const name = cmd.getArg(0);
4490 string const state = cmd.getArg(1);
4491 if (GuiToolbar * t = toolbar(name))
4496 case LFUN_TOOLBAR_TOGGLE: {
4497 string const name = cmd.getArg(0);
4498 if (GuiToolbar * t = toolbar(name))
4503 case LFUN_TOOLBAR_MOVABLE: {
4504 string const name = cmd.getArg(0);
4506 // toggle (all) toolbars movablility
4507 toolbarsMovable_ = !toolbarsMovable_;
4508 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4509 GuiToolbar * tb = toolbar(ti.name);
4510 if (tb && tb->isMovable() != toolbarsMovable_)
4511 // toggle toolbar movablity if it does not fit lock
4512 // (all) toolbars positions state silent = true, since
4513 // status bar notifications are slow
4516 if (toolbarsMovable_)
4517 dr.setMessage(_("Toolbars unlocked."));
4519 dr.setMessage(_("Toolbars locked."));
4520 } else if (GuiToolbar * tb = toolbar(name))
4521 // toggle current toolbar movablity
4523 // update lock (all) toolbars positions
4524 updateLockToolbars();
4528 case LFUN_ICON_SIZE: {
4529 QSize size = d.iconSize(cmd.argument());
4531 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4532 size.width(), size.height()));
4536 case LFUN_DIALOG_UPDATE: {
4537 string const name = to_utf8(cmd.argument());
4538 if (name == "prefs" || name == "document")
4539 updateDialog(name, string());
4540 else if (name == "paragraph")
4541 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4542 else if (currentBufferView()) {
4543 Inset * inset = currentBufferView()->editedInset(name);
4544 // Can only update a dialog connected to an existing inset
4546 // FIXME: get rid of this indirection; GuiView ask the inset
4547 // if he is kind enough to update itself...
4548 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4549 //FIXME: pass DispatchResult here?
4550 inset->dispatch(currentBufferView()->cursor(), fr);
4556 case LFUN_DIALOG_TOGGLE: {
4557 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4558 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4559 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4563 case LFUN_DIALOG_DISCONNECT_INSET:
4564 disconnectDialog(to_utf8(cmd.argument()));
4567 case LFUN_DIALOG_HIDE: {
4568 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4572 case LFUN_DIALOG_SHOW: {
4573 string const name = cmd.getArg(0);
4574 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4576 if (name == "latexlog") {
4577 // getStatus checks that
4578 LASSERT(doc_buffer, break);
4579 Buffer::LogType type;
4580 string const logfile = doc_buffer->logName(&type);
4582 case Buffer::latexlog:
4585 case Buffer::buildlog:
4586 sdata = "literate ";
4589 sdata += Lexer::quoteString(logfile);
4590 showDialog("log", sdata);
4591 } else if (name == "vclog") {
4592 // getStatus checks that
4593 LASSERT(doc_buffer, break);
4594 string const sdata2 = "vc " +
4595 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4596 showDialog("log", sdata2);
4597 } else if (name == "symbols") {
4598 sdata = bv->cursor().getEncoding()->name();
4600 showDialog("symbols", sdata);
4601 } else if (name == "findreplace") {
4602 sdata = to_utf8(bv->cursor().selectionAsString(false));
4603 showDialog(name, sdata);
4605 } else if (name == "prefs" && isFullScreen()) {
4606 lfunUiToggle("fullscreen");
4607 showDialog("prefs", sdata);
4609 showDialog(name, sdata);
4614 dr.setMessage(cmd.argument());
4617 case LFUN_UI_TOGGLE: {
4618 string arg = cmd.getArg(0);
4619 if (!lfunUiToggle(arg)) {
4620 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4621 dr.setMessage(bformat(msg, from_utf8(arg)));
4623 // Make sure the keyboard focus stays in the work area.
4628 case LFUN_VIEW_SPLIT: {
4629 LASSERT(doc_buffer, break);
4630 string const orientation = cmd.getArg(0);
4631 d.splitter_->setOrientation(orientation == "vertical"
4632 ? Qt::Vertical : Qt::Horizontal);
4633 TabWorkArea * twa = addTabWorkArea();
4634 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4635 setCurrentWorkArea(wa);
4638 case LFUN_TAB_GROUP_CLOSE:
4639 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4640 closeTabWorkArea(twa);
4641 d.current_work_area_ = nullptr;
4642 twa = d.currentTabWorkArea();
4643 // Switch to the next GuiWorkArea in the found TabWorkArea.
4645 // Make sure the work area is up to date.
4646 setCurrentWorkArea(twa->currentWorkArea());
4648 setCurrentWorkArea(nullptr);
4653 case LFUN_VIEW_CLOSE:
4654 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4655 closeWorkArea(twa->currentWorkArea());
4656 d.current_work_area_ = nullptr;
4657 twa = d.currentTabWorkArea();
4658 // Switch to the next GuiWorkArea in the found TabWorkArea.
4660 // Make sure the work area is up to date.
4661 setCurrentWorkArea(twa->currentWorkArea());
4663 setCurrentWorkArea(nullptr);
4668 case LFUN_COMPLETION_INLINE:
4669 if (d.current_work_area_)
4670 d.current_work_area_->completer().showInline();
4673 case LFUN_COMPLETION_POPUP:
4674 if (d.current_work_area_)
4675 d.current_work_area_->completer().showPopup();
4680 if (d.current_work_area_)
4681 d.current_work_area_->completer().tab();
4684 case LFUN_COMPLETION_CANCEL:
4685 if (d.current_work_area_) {
4686 if (d.current_work_area_->completer().popupVisible())
4687 d.current_work_area_->completer().hidePopup();
4689 d.current_work_area_->completer().hideInline();
4693 case LFUN_COMPLETION_ACCEPT:
4694 if (d.current_work_area_)
4695 d.current_work_area_->completer().activate();
4698 case LFUN_BUFFER_ZOOM_IN:
4699 case LFUN_BUFFER_ZOOM_OUT:
4700 case LFUN_BUFFER_ZOOM: {
4701 if (cmd.argument().empty()) {
4702 if (cmd.action() == LFUN_BUFFER_ZOOM)
4704 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4709 if (cmd.action() == LFUN_BUFFER_ZOOM)
4710 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4711 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4712 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4714 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4717 // Actual zoom value: default zoom + fractional extra value
4718 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4719 if (zoom < static_cast<int>(zoom_min_))
4722 setCurrentZoom(zoom);
4724 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4725 lyxrc.currentZoom, lyxrc.defaultZoom));
4727 guiApp->fontLoader().update();
4728 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4732 case LFUN_VC_REGISTER:
4733 case LFUN_VC_RENAME:
4735 case LFUN_VC_CHECK_IN:
4736 case LFUN_VC_CHECK_OUT:
4737 case LFUN_VC_REPO_UPDATE:
4738 case LFUN_VC_LOCKING_TOGGLE:
4739 case LFUN_VC_REVERT:
4740 case LFUN_VC_UNDO_LAST:
4741 case LFUN_VC_COMMAND:
4742 case LFUN_VC_COMPARE:
4743 dispatchVC(cmd, dr);
4746 case LFUN_SERVER_GOTO_FILE_ROW:
4747 if(goToFileRow(to_utf8(cmd.argument())))
4748 dr.screenUpdate(Update::Force | Update::FitCursor);
4751 case LFUN_LYX_ACTIVATE:
4755 case LFUN_WINDOW_RAISE:
4761 case LFUN_FORWARD_SEARCH: {
4762 // it seems safe to assume we have a document buffer, since
4763 // getStatus wants one.
4764 LASSERT(doc_buffer, break);
4765 Buffer const * doc_master = doc_buffer->masterBuffer();
4766 FileName const path(doc_master->temppath());
4767 string const texname = doc_master->isChild(doc_buffer)
4768 ? DocFileName(changeExtension(
4769 doc_buffer->absFileName(),
4770 "tex")).mangledFileName()
4771 : doc_buffer->latexName();
4772 string const fulltexname =
4773 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4774 string const mastername =
4775 removeExtension(doc_master->latexName());
4776 FileName const dviname(addName(path.absFileName(),
4777 addExtension(mastername, "dvi")));
4778 FileName const pdfname(addName(path.absFileName(),
4779 addExtension(mastername, "pdf")));
4780 bool const have_dvi = dviname.exists();
4781 bool const have_pdf = pdfname.exists();
4782 if (!have_dvi && !have_pdf) {
4783 dr.setMessage(_("Please, preview the document first."));
4786 string outname = dviname.onlyFileName();
4787 string command = lyxrc.forward_search_dvi;
4788 if (!have_dvi || (have_pdf &&
4789 pdfname.lastModified() > dviname.lastModified())) {
4790 outname = pdfname.onlyFileName();
4791 command = lyxrc.forward_search_pdf;
4794 DocIterator cur = bv->cursor();
4795 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4796 LYXERR(Debug::ACTION, "Forward search: row:" << row
4798 if (row == -1 || command.empty()) {
4799 dr.setMessage(_("Couldn't proceed."));
4802 string texrow = convert<string>(row);
4804 command = subst(command, "$$n", texrow);
4805 command = subst(command, "$$f", fulltexname);
4806 command = subst(command, "$$t", texname);
4807 command = subst(command, "$$o", outname);
4809 volatile PathChanger p(path);
4811 one.startscript(Systemcall::DontWait, command);
4815 case LFUN_SPELLING_CONTINUOUSLY:
4816 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4817 dr.screenUpdate(Update::Force);
4820 case LFUN_CITATION_OPEN: {
4822 if (theFormats().getFormat("pdf"))
4823 pdfv = theFormats().getFormat("pdf")->viewer();
4824 if (theFormats().getFormat("ps"))
4825 psv = theFormats().getFormat("ps")->viewer();
4826 frontend::showTarget(argument, pdfv, psv);
4831 // The LFUN must be for one of BufferView, Buffer or Cursor;
4833 dispatchToBufferView(cmd, dr);
4837 // Need to update bv because many LFUNs here might have destroyed it
4838 bv = currentBufferView();
4840 // Clear non-empty selections
4841 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4843 Cursor & cur = bv->cursor();
4844 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4845 cur.clearSelection();
4851 bool GuiView::lfunUiToggle(string const & ui_component)
4853 if (ui_component == "scrollbar") {
4854 // hide() is of no help
4855 if (d.current_work_area_->verticalScrollBarPolicy() ==
4856 Qt::ScrollBarAlwaysOff)
4858 d.current_work_area_->setVerticalScrollBarPolicy(
4859 Qt::ScrollBarAsNeeded);
4861 d.current_work_area_->setVerticalScrollBarPolicy(
4862 Qt::ScrollBarAlwaysOff);
4863 } else if (ui_component == "statusbar") {
4864 statusBar()->setVisible(!statusBar()->isVisible());
4865 } else if (ui_component == "menubar") {
4866 menuBar()->setVisible(!menuBar()->isVisible());
4867 } else if (ui_component == "zoom") {
4868 zoom_value_->setVisible(!zoom_value_->isVisible());
4869 } else if (ui_component == "zoomslider") {
4870 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4871 zoom_in_->setVisible(zoom_slider_->isVisible());
4872 zoom_out_->setVisible(zoom_slider_->isVisible());
4873 } else if (ui_component == "frame") {
4874 int const l = contentsMargins().left();
4876 //are the frames in default state?
4877 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4879 #if QT_VERSION > 0x050903
4880 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4882 setContentsMargins(-2, -2, -2, -2);
4884 #if QT_VERSION > 0x050903
4885 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4887 setContentsMargins(0, 0, 0, 0);
4890 if (ui_component == "fullscreen") {
4898 void GuiView::toggleFullScreen()
4900 setWindowState(windowState() ^ Qt::WindowFullScreen);
4904 Buffer const * GuiView::updateInset(Inset const * inset)
4909 Buffer const * inset_buffer = &(inset->buffer());
4911 for (int i = 0; i != d.splitter_->count(); ++i) {
4912 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4915 Buffer const * buffer = &(wa->bufferView().buffer());
4916 if (inset_buffer == buffer)
4917 wa->scheduleRedraw(true);
4919 return inset_buffer;
4923 void GuiView::restartCaret()
4925 /* When we move around, or type, it's nice to be able to see
4926 * the caret immediately after the keypress.
4928 if (d.current_work_area_)
4929 d.current_work_area_->startBlinkingCaret();
4931 // Take this occasion to update the other GUI elements.
4937 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4939 if (d.current_work_area_)
4940 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4945 // This list should be kept in sync with the list of insets in
4946 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4947 // dialog should have the same name as the inset.
4948 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4949 // docs in LyXAction.cpp.
4951 char const * const dialognames[] = {
4953 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4954 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4955 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4956 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4957 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4958 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4959 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4960 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4962 char const * const * const end_dialognames =
4963 dialognames + (sizeof(dialognames) / sizeof(char *));
4967 cmpCStr(char const * name) : name_(name) {}
4968 bool operator()(char const * other) {
4969 return strcmp(other, name_) == 0;
4976 bool isValidName(string const & name)
4978 return find_if(dialognames, end_dialognames,
4979 cmpCStr(name.c_str())) != end_dialognames;
4985 void GuiView::resetDialogs()
4987 // Make sure that no LFUN uses any GuiView.
4988 guiApp->setCurrentView(nullptr);
4992 constructToolbars();
4993 guiApp->menus().fillMenuBar(menuBar(), this, false);
4994 d.layout_->updateContents(true);
4995 // Now update controls with current buffer.
4996 guiApp->setCurrentView(this);
5002 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5004 for (QObject * child: widget->children()) {
5005 if (child->inherits("QGroupBox")) {
5006 QGroupBox * box = (QGroupBox*) child;
5009 flatGroupBoxes(child, flag);
5015 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5017 if (!isValidName(name))
5020 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5022 if (it != d.dialogs_.end()) {
5024 it->second->hideView();
5025 return it->second.get();
5028 Dialog * dialog = build(name);
5029 d.dialogs_[name].reset(dialog);
5030 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5031 // Force a uniform style for group boxes
5032 // On Mac non-flat works better, on Linux flat is standard
5033 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5035 if (lyxrc.allow_geometry_session)
5036 dialog->restoreSession();
5043 void GuiView::showDialog(string const & name, string const & sdata,
5046 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5050 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5056 const string name = fromqstr(qname);
5057 const string sdata = fromqstr(qdata);
5061 Dialog * dialog = findOrBuild(name, false);
5063 bool const visible = dialog->isVisibleView();
5064 dialog->showData(sdata);
5065 if (currentBufferView())
5066 currentBufferView()->editInset(name, inset);
5067 // We only set the focus to the new dialog if it was not yet
5068 // visible in order not to change the existing previous behaviour
5070 // activateWindow is needed for floating dockviews
5071 dialog->asQWidget()->raise();
5072 dialog->asQWidget()->activateWindow();
5073 if (dialog->wantInitialFocus())
5074 dialog->asQWidget()->setFocus();
5078 catch (ExceptionMessage const &) {
5086 bool GuiView::isDialogVisible(string const & name) const
5088 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5089 if (it == d.dialogs_.end())
5091 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5095 void GuiView::hideDialog(string const & name, Inset * inset)
5097 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5098 if (it == d.dialogs_.end())
5102 if (!currentBufferView())
5104 if (inset != currentBufferView()->editedInset(name))
5108 Dialog * const dialog = it->second.get();
5109 if (dialog->isVisibleView())
5111 if (currentBufferView())
5112 currentBufferView()->editInset(name, nullptr);
5116 void GuiView::disconnectDialog(string const & name)
5118 if (!isValidName(name))
5120 if (currentBufferView())
5121 currentBufferView()->editInset(name, nullptr);
5125 void GuiView::hideAll() const
5127 for(auto const & dlg_p : d.dialogs_)
5128 dlg_p.second->hideView();
5132 void GuiView::updateDialogs()
5134 for(auto const & dlg_p : d.dialogs_) {
5135 Dialog * dialog = dlg_p.second.get();
5137 if (dialog->needBufferOpen() && !documentBufferView())
5138 hideDialog(fromqstr(dialog->name()), nullptr);
5139 else if (dialog->isVisibleView())
5140 dialog->checkStatus();
5148 Dialog * GuiView::build(string const & name)
5150 return createDialog(*this, name);
5154 SEMenu::SEMenu(QWidget * parent)
5156 QAction * action = addAction(qt_("Disable Shell Escape"));
5157 connect(action, SIGNAL(triggered()),
5158 parent, SLOT(disableShellEscape()));
5162 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5164 if (event->button() == Qt::LeftButton) {
5169 } // namespace frontend
5172 #include "moc_GuiView.cpp"