3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
116 // sync with GuiAlert.cpp
117 #define EXPORT_in_THREAD 1
120 #include "support/bind.h"
124 #ifdef HAVE_SYS_TIME_H
125 # include <sys/time.h>
133 using namespace lyx::support;
137 using support::addExtension;
138 using support::changeExtension;
139 using support::removeExtension;
145 class BackgroundWidget : public QWidget
148 BackgroundWidget(int width, int height)
149 : width_(width), height_(height)
151 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
152 if (!lyxrc.show_banner)
154 /// The text to be written on top of the pixmap
155 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
156 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
157 /// The text to be written on top of the pixmap
158 QString const text = lyx_version ?
159 qt_("version ") + lyx_version : qt_("unknown version");
160 #if QT_VERSION >= 0x050000
161 QString imagedir = "images/";
162 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
163 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
164 if (svgRenderer.isValid()) {
165 splash_ = QPixmap(splashSize());
166 QPainter painter(&splash_);
167 svgRenderer.render(&painter);
168 splash_.setDevicePixelRatio(pixelRatio());
170 splash_ = getPixmap("images/", "banner", "png");
173 splash_ = getPixmap("images/", "banner", "svgz,png");
176 QPainter pain(&splash_);
177 pain.setPen(QColor(0, 0, 0));
178 qreal const fsize = fontSize();
181 qreal locscale = htextsize.toFloat(&ok);
184 QPointF const position = textPosition(false);
185 QPointF const hposition = textPosition(true);
186 QRectF const hrect(hposition, splashSize());
188 "widget pixel ratio: " << pixelRatio() <<
189 " splash pixel ratio: " << splashPixelRatio() <<
190 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
192 // The font used to display the version info
193 font.setStyleHint(QFont::SansSerif);
194 font.setWeight(QFont::Bold);
195 font.setPointSizeF(fsize);
197 pain.drawText(position, text);
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Normal);
201 font.setPointSizeF(hfsize);
202 // Check how long the logo gets with the current font
203 // and adapt if the font is running wider than what
205 GuiFontMetrics fm(font);
206 // Split the title into lines to measure the longest line
207 // in the current l7n.
208 QStringList titlesegs = htext.split('\n');
210 int hline = fm.maxHeight();
211 QStringList::const_iterator sit;
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
616 statusBar()->addPermanentWidget(busylabel);
617 search_mode mode = theGuiApp()->imageSearchMode();
618 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
619 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
620 busylabel->setMovie(busyanim);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busylabel, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busylabel, SLOT(hide()));
628 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
633 // Small size slider for macOS to prevent the status bar from enlarging
634 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
635 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
636 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
638 zoom_slider_->setFixedWidth(fm.width('x') * 15);
640 // Make the defaultZoom center
641 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
642 // Initialize proper zoom value
644 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
645 // Actual zoom value: default zoom + fractional offset
646 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
647 if (zoom < static_cast<int>(zoom_min_))
649 zoom_slider_->setValue(zoom);
650 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
652 // Buttons to change zoom stepwise
653 zoom_in_ = new QPushButton(statusBar());
654 zoom_in_->setText("+");
655 zoom_in_->setFlat(true);
656 zoom_in_->setFixedSize(QSize(fm.height(), fm.height()));
657 zoom_out_ = new QPushButton(statusBar());
658 zoom_out_->setText(QString(QChar(0x2212)));
659 zoom_out_->setFixedSize(QSize(fm.height(), fm.height()));
660 zoom_out_->setFlat(true);
662 statusBar()->addPermanentWidget(zoom_out_);
663 zoom_out_->setEnabled(currentBufferView());
664 statusBar()->addPermanentWidget(zoom_slider_);
665 zoom_slider_->setEnabled(currentBufferView());
666 zoom_in_->setEnabled(currentBufferView());
667 statusBar()->addPermanentWidget(zoom_in_);
669 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
670 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
671 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
672 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
673 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
675 zoom_value_ = new QLabel(statusBar());
676 zoom_value_->setFixedHeight(fm.height());
677 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
678 zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
680 zoom_value_->setMinimumWidth(fm.width("000%"));
682 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
683 statusBar()->addPermanentWidget(zoom_value_);
684 zoom_value_->setEnabled(currentBufferView());
685 zoom_value_->setContextMenuPolicy(Qt::CustomContextMenu);
687 connect(zoom_value_, SIGNAL(customContextMenuRequested(QPoint)),
688 this, SLOT(showZoomContextMenu()));
690 int const iconheight = max(int(d.normalIconSize), fm.height());
691 QSize const iconsize(iconheight, iconheight);
693 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
694 shell_escape_ = new QLabel(statusBar());
695 shell_escape_->setPixmap(shellescape);
696 shell_escape_->setAlignment(Qt::AlignCenter);
697 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
698 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
699 "external commands for this document. "
700 "Right click to change."));
701 SEMenu * menu = new SEMenu(this);
702 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
703 menu, SLOT(showMenu(QPoint)));
704 shell_escape_->hide();
705 statusBar()->addPermanentWidget(shell_escape_);
707 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
708 read_only_ = new QLabel(statusBar());
709 read_only_->setPixmap(readonly);
710 read_only_->setAlignment(Qt::AlignCenter);
712 statusBar()->addPermanentWidget(read_only_);
714 version_control_ = new QLabel(statusBar());
715 version_control_->setAlignment(Qt::AlignCenter);
716 version_control_->setFrameStyle(QFrame::StyledPanel);
717 version_control_->hide();
718 statusBar()->addPermanentWidget(version_control_);
720 statusBar()->setSizeGripEnabled(true);
723 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
724 SLOT(autoSaveThreadFinished()));
726 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
727 SLOT(processingThreadStarted()));
728 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
729 SLOT(processingThreadFinished()));
731 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
732 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
734 // set custom application bars context menu, e.g. tool bar and menu bar
735 setContextMenuPolicy(Qt::CustomContextMenu);
736 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
737 SLOT(toolBarPopup(const QPoint &)));
739 // Forbid too small unresizable window because it can happen
740 // with some window manager under X11.
741 setMinimumSize(300, 200);
743 if (lyxrc.allow_geometry_session) {
744 // Now take care of session management.
749 // no session handling, default to a sane size.
750 setGeometry(50, 50, 690, 510);
753 // clear session data if any.
754 settings.remove("views");
764 void GuiView::disableShellEscape()
766 BufferView * bv = documentBufferView();
769 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
770 bv->buffer().params().shell_escape = false;
771 bv->processUpdateFlags(Update::Force);
775 void GuiView::checkCancelBackground()
777 docstring const ttl = _("Cancel Export?");
778 docstring const msg = _("Do you want to cancel the background export process?");
780 Alert::prompt(ttl, msg, 1, 1,
781 _("&Cancel export"), _("Co&ntinue"));
783 Systemcall::killscript();
787 void GuiView::zoomSliderMoved(int value)
790 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
791 currentWorkArea()->scheduleRedraw(true);
792 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
796 void GuiView::zoomValueChanged(int value)
798 if (value != lyxrc.currentZoom)
799 zoomSliderMoved(value);
803 void GuiView::zoomInPressed()
806 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
807 currentWorkArea()->scheduleRedraw(true);
811 void GuiView::zoomOutPressed()
814 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
815 currentWorkArea()->scheduleRedraw(true);
819 void GuiView::showZoomContextMenu()
821 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
824 menu->exec(QCursor::pos());
828 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
830 QVector<GuiWorkArea*> areas;
831 for (int i = 0; i < tabWorkAreaCount(); i++) {
832 TabWorkArea* ta = tabWorkArea(i);
833 for (int u = 0; u < ta->count(); u++) {
834 areas << ta->workArea(u);
840 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
841 string const & format)
843 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
846 case Buffer::ExportSuccess:
847 msg = bformat(_("Successful export to format: %1$s"), fmt);
849 case Buffer::ExportCancel:
850 msg = _("Document export cancelled.");
852 case Buffer::ExportError:
853 case Buffer::ExportNoPathToFormat:
854 case Buffer::ExportTexPathHasSpaces:
855 case Buffer::ExportConverterError:
856 msg = bformat(_("Error while exporting format: %1$s"), fmt);
858 case Buffer::PreviewSuccess:
859 msg = bformat(_("Successful preview of format: %1$s"), fmt);
861 case Buffer::PreviewError:
862 msg = bformat(_("Error while previewing format: %1$s"), fmt);
864 case Buffer::ExportKilled:
865 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
872 void GuiView::processingThreadStarted()
877 void GuiView::processingThreadFinished()
879 QFutureWatcher<Buffer::ExportStatus> const * watcher =
880 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
882 Buffer::ExportStatus const status = watcher->result();
883 handleExportStatus(this, status, d.processing_format);
886 BufferView const * const bv = currentBufferView();
887 if (bv && !bv->buffer().errorList("Export").empty()) {
892 bool const error = (status != Buffer::ExportSuccess &&
893 status != Buffer::PreviewSuccess &&
894 status != Buffer::ExportCancel);
896 ErrorList & el = bv->buffer().errorList(d.last_export_format);
897 // at this point, we do not know if buffer-view or
898 // master-buffer-view was called. If there was an export error,
899 // and the current buffer's error log is empty, we guess that
900 // it must be master-buffer-view that was called so we set
902 errors(d.last_export_format, el.empty());
907 void GuiView::autoSaveThreadFinished()
909 QFutureWatcher<docstring> const * watcher =
910 static_cast<QFutureWatcher<docstring> const *>(sender());
911 message(watcher->result());
916 void GuiView::saveLayout() const
919 settings.setValue("zoom_ratio", zoom_ratio_);
920 settings.setValue("devel_mode", devel_mode_);
921 settings.beginGroup("views");
922 settings.beginGroup(QString::number(id_));
923 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
924 settings.setValue("pos", pos());
925 settings.setValue("size", size());
927 settings.setValue("geometry", saveGeometry());
928 settings.setValue("layout", saveState(0));
929 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
930 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
934 void GuiView::saveUISettings() const
938 // Save the toolbar private states
939 for (auto const & tb_p : d.toolbars_)
940 tb_p.second->saveSession(settings);
941 // Now take care of all other dialogs
942 for (auto const & dlg_p : d.dialogs_)
943 dlg_p.second->saveSession(settings);
947 void GuiView::setCurrentZoom(const int v)
949 lyxrc.currentZoom = v;
950 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
951 Q_EMIT currentZoomChanged(v);
955 bool GuiView::restoreLayout()
958 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
959 // Actual zoom value: default zoom + fractional offset
960 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
961 if (zoom < static_cast<int>(zoom_min_))
963 setCurrentZoom(zoom);
964 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
965 settings.beginGroup("views");
966 settings.beginGroup(QString::number(id_));
967 QString const icon_key = "icon_size";
968 if (!settings.contains(icon_key))
971 //code below is skipped when when ~/.config/LyX is (re)created
972 setIconSize(d.iconSize(settings.value(icon_key).toString()));
974 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
975 zoom_slider_->setVisible(show_zoom_slider);
976 zoom_in_->setVisible(show_zoom_slider);
977 zoom_out_->setVisible(show_zoom_slider);
979 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
980 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
981 QSize size = settings.value("size", QSize(690, 510)).toSize();
985 // Work-around for bug #6034: the window ends up in an undetermined
986 // state when trying to restore a maximized window when it is
987 // already maximized.
988 if (!(windowState() & Qt::WindowMaximized))
989 if (!restoreGeometry(settings.value("geometry").toByteArray()))
990 setGeometry(50, 50, 690, 510);
993 // Make sure layout is correctly oriented.
994 setLayoutDirection(qApp->layoutDirection());
996 // Allow the toc and view-source dock widget to be restored if needed.
998 if ((dialog = findOrBuild("toc", true)))
999 // see bug 5082. At least setup title and enabled state.
1000 // Visibility will be adjusted by restoreState below.
1001 dialog->prepareView();
1002 if ((dialog = findOrBuild("view-source", true)))
1003 dialog->prepareView();
1004 if ((dialog = findOrBuild("progress", true)))
1005 dialog->prepareView();
1007 if (!restoreState(settings.value("layout").toByteArray(), 0))
1010 // init the toolbars that have not been restored
1011 for (auto const & tb_p : guiApp->toolbars()) {
1012 GuiToolbar * tb = toolbar(tb_p.name);
1013 if (tb && !tb->isRestored())
1014 initToolbar(tb_p.name);
1017 // update lock (all) toolbars positions
1018 updateLockToolbars();
1025 GuiToolbar * GuiView::toolbar(string const & name)
1027 ToolbarMap::iterator it = d.toolbars_.find(name);
1028 if (it != d.toolbars_.end())
1031 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1036 void GuiView::updateLockToolbars()
1038 toolbarsMovable_ = false;
1039 for (ToolbarInfo const & info : guiApp->toolbars()) {
1040 GuiToolbar * tb = toolbar(info.name);
1041 if (tb && tb->isMovable())
1042 toolbarsMovable_ = true;
1047 void GuiView::constructToolbars()
1049 for (auto const & tb_p : d.toolbars_)
1051 d.toolbars_.clear();
1053 // I don't like doing this here, but the standard toolbar
1054 // destroys this object when it's destroyed itself (vfr)
1055 d.layout_ = new LayoutBox(*this);
1056 d.stack_widget_->addWidget(d.layout_);
1057 d.layout_->move(0,0);
1059 // extracts the toolbars from the backend
1060 for (ToolbarInfo const & inf : guiApp->toolbars())
1061 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1063 DynamicMenuButton::resetIconCache();
1067 void GuiView::initToolbars()
1069 // extracts the toolbars from the backend
1070 for (ToolbarInfo const & inf : guiApp->toolbars())
1071 initToolbar(inf.name);
1075 void GuiView::initToolbar(string const & name)
1077 GuiToolbar * tb = toolbar(name);
1080 int const visibility = guiApp->toolbars().defaultVisibility(name);
1081 bool newline = !(visibility & Toolbars::SAMEROW);
1082 tb->setVisible(false);
1083 tb->setVisibility(visibility);
1085 if (visibility & Toolbars::TOP) {
1087 addToolBarBreak(Qt::TopToolBarArea);
1088 addToolBar(Qt::TopToolBarArea, tb);
1091 if (visibility & Toolbars::BOTTOM) {
1093 addToolBarBreak(Qt::BottomToolBarArea);
1094 addToolBar(Qt::BottomToolBarArea, tb);
1097 if (visibility & Toolbars::LEFT) {
1099 addToolBarBreak(Qt::LeftToolBarArea);
1100 addToolBar(Qt::LeftToolBarArea, tb);
1103 if (visibility & Toolbars::RIGHT) {
1105 addToolBarBreak(Qt::RightToolBarArea);
1106 addToolBar(Qt::RightToolBarArea, tb);
1109 if (visibility & Toolbars::ON)
1110 tb->setVisible(true);
1112 tb->setMovable(true);
1116 TocModels & GuiView::tocModels()
1118 return d.toc_models_;
1122 void GuiView::setFocus()
1124 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1125 QMainWindow::setFocus();
1129 bool GuiView::hasFocus() const
1131 if (currentWorkArea())
1132 return currentWorkArea()->hasFocus();
1133 if (currentMainWorkArea())
1134 return currentMainWorkArea()->hasFocus();
1135 return d.bg_widget_->hasFocus();
1139 void GuiView::focusInEvent(QFocusEvent * e)
1141 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1142 QMainWindow::focusInEvent(e);
1143 // Make sure guiApp points to the correct view.
1144 guiApp->setCurrentView(this);
1145 if (currentWorkArea())
1146 currentWorkArea()->setFocus();
1147 else if (currentMainWorkArea())
1148 currentMainWorkArea()->setFocus();
1150 d.bg_widget_->setFocus();
1154 void GuiView::showEvent(QShowEvent * e)
1156 LYXERR(Debug::GUI, "Passed Geometry "
1157 << size().height() << "x" << size().width()
1158 << "+" << pos().x() << "+" << pos().y());
1160 if (d.splitter_->count() == 0)
1161 // No work area, switch to the background widget.
1165 QMainWindow::showEvent(e);
1169 bool GuiView::closeScheduled()
1176 bool GuiView::prepareAllBuffersForLogout()
1178 Buffer * first = theBufferList().first();
1182 // First, iterate over all buffers and ask the users if unsaved
1183 // changes should be saved.
1184 // We cannot use a for loop as the buffer list cycles.
1187 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1189 b = theBufferList().next(b);
1190 } while (b != first);
1192 // Next, save session state
1193 // When a view/window was closed before without quitting LyX, there
1194 // are already entries in the lastOpened list.
1195 theSession().lastOpened().clear();
1202 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1203 ** is responsibility of the container (e.g., dialog)
1205 void GuiView::closeEvent(QCloseEvent * close_event)
1207 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1209 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1210 Alert::warning(_("Exit LyX"),
1211 _("LyX could not be closed because documents are being processed by LyX."));
1212 close_event->setAccepted(false);
1216 // If the user pressed the x (so we didn't call closeView
1217 // programmatically), we want to clear all existing entries.
1219 theSession().lastOpened().clear();
1224 // it can happen that this event arrives without selecting the view,
1225 // e.g. when clicking the close button on a background window.
1227 if (!closeWorkAreaAll()) {
1229 close_event->ignore();
1233 // Make sure that nothing will use this to be closed View.
1234 guiApp->unregisterView(this);
1236 if (isFullScreen()) {
1237 // Switch off fullscreen before closing.
1242 // Make sure the timer time out will not trigger a statusbar update.
1243 d.statusbar_timer_.stop();
1245 // Saving fullscreen requires additional tweaks in the toolbar code.
1246 // It wouldn't also work under linux natively.
1247 if (lyxrc.allow_geometry_session) {
1252 close_event->accept();
1256 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1258 if (event->mimeData()->hasUrls())
1260 /// \todo Ask lyx-devel is this is enough:
1261 /// if (event->mimeData()->hasFormat("text/plain"))
1262 /// event->acceptProposedAction();
1266 void GuiView::dropEvent(QDropEvent * event)
1268 QList<QUrl> files = event->mimeData()->urls();
1269 if (files.isEmpty())
1272 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1273 for (int i = 0; i != files.size(); ++i) {
1274 string const file = os::internal_path(fromqstr(
1275 files.at(i).toLocalFile()));
1279 string const ext = support::getExtension(file);
1280 vector<const Format *> found_formats;
1282 // Find all formats that have the correct extension.
1283 for (const Format * fmt : theConverters().importableFormats())
1284 if (fmt->hasExtension(ext))
1285 found_formats.push_back(fmt);
1288 if (!found_formats.empty()) {
1289 if (found_formats.size() > 1) {
1290 //FIXME: show a dialog to choose the correct importable format
1291 LYXERR(Debug::FILES,
1292 "Multiple importable formats found, selecting first");
1294 string const arg = found_formats[0]->name() + " " + file;
1295 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1298 //FIXME: do we have to explicitly check whether it's a lyx file?
1299 LYXERR(Debug::FILES,
1300 "No formats found, trying to open it as a lyx file");
1301 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1303 // add the functions to the queue
1304 guiApp->addToFuncRequestQueue(cmd);
1307 // now process the collected functions. We perform the events
1308 // asynchronously. This prevents potential problems in case the
1309 // BufferView is closed within an event.
1310 guiApp->processFuncRequestQueueAsync();
1314 void GuiView::message(docstring const & str)
1316 if (ForkedProcess::iAmAChild())
1319 // call is moved to GUI-thread by GuiProgress
1320 d.progress_->appendMessage(toqstr(str));
1324 void GuiView::clearMessageText()
1326 message(docstring());
1330 void GuiView::updateStatusBarMessage(QString const & str)
1332 statusBar()->showMessage(str);
1333 d.statusbar_timer_.stop();
1334 d.statusbar_timer_.start(3000);
1338 void GuiView::clearMessage()
1340 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1341 // the hasFocus function mostly returns false, even if the focus is on
1342 // a workarea in this view.
1346 d.statusbar_timer_.stop();
1350 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1352 if (wa != d.current_work_area_
1353 || wa->bufferView().buffer().isInternal())
1355 Buffer const & buf = wa->bufferView().buffer();
1356 // Set the windows title
1357 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1358 if (buf.notifiesExternalModification()) {
1359 title = bformat(_("%1$s (modified externally)"), title);
1360 // If the external modification status has changed, then maybe the status of
1361 // buffer-save has changed too.
1365 title += from_ascii(" - LyX");
1367 setWindowTitle(toqstr(title));
1368 // Sets the path for the window: this is used by OSX to
1369 // allow a context click on the title bar showing a menu
1370 // with the path up to the file
1371 setWindowFilePath(toqstr(buf.absFileName()));
1372 // Tell Qt whether the current document is changed
1373 setWindowModified(!buf.isClean());
1375 if (buf.params().shell_escape)
1376 shell_escape_->show();
1378 shell_escape_->hide();
1380 if (buf.hasReadonlyFlag())
1385 if (buf.lyxvc().inUse()) {
1386 version_control_->show();
1387 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1389 version_control_->hide();
1393 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1395 if (d.current_work_area_)
1396 // disconnect the current work area from all slots
1397 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1399 disconnectBufferView();
1400 connectBufferView(wa->bufferView());
1401 connectBuffer(wa->bufferView().buffer());
1402 d.current_work_area_ = wa;
1403 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1404 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1405 QObject::connect(wa, SIGNAL(busy(bool)),
1406 this, SLOT(setBusy(bool)));
1407 // connection of a signal to a signal
1408 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1409 this, SIGNAL(bufferViewChanged()));
1410 Q_EMIT updateWindowTitle(wa);
1411 Q_EMIT bufferViewChanged();
1415 void GuiView::onBufferViewChanged()
1418 // Buffer-dependent dialogs must be updated. This is done here because
1419 // some dialogs require buffer()->text.
1421 zoom_slider_->setEnabled(currentBufferView());
1422 zoom_value_->setEnabled(currentBufferView());
1423 zoom_in_->setEnabled(currentBufferView());
1424 zoom_out_->setEnabled(currentBufferView());
1428 void GuiView::on_lastWorkAreaRemoved()
1431 // We already are in a close event. Nothing more to do.
1434 if (d.splitter_->count() > 1)
1435 // We have a splitter so don't close anything.
1438 // Reset and updates the dialogs.
1439 Q_EMIT bufferViewChanged();
1444 if (lyxrc.open_buffers_in_tabs)
1445 // Nothing more to do, the window should stay open.
1448 if (guiApp->viewIds().size() > 1) {
1454 // On Mac we also close the last window because the application stay
1455 // resident in memory. On other platforms we don't close the last
1456 // window because this would quit the application.
1462 void GuiView::updateStatusBar()
1464 // let the user see the explicit message
1465 if (d.statusbar_timer_.isActive())
1472 void GuiView::showMessage()
1476 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1477 if (msg.isEmpty()) {
1478 BufferView const * bv = currentBufferView();
1480 msg = toqstr(bv->cursor().currentState(devel_mode_));
1482 msg = qt_("Welcome to LyX!");
1484 statusBar()->showMessage(msg);
1488 bool GuiView::event(QEvent * e)
1492 // Useful debug code:
1493 //case QEvent::ActivationChange:
1494 //case QEvent::WindowDeactivate:
1495 //case QEvent::Paint:
1496 //case QEvent::Enter:
1497 //case QEvent::Leave:
1498 //case QEvent::HoverEnter:
1499 //case QEvent::HoverLeave:
1500 //case QEvent::HoverMove:
1501 //case QEvent::StatusTip:
1502 //case QEvent::DragEnter:
1503 //case QEvent::DragLeave:
1504 //case QEvent::Drop:
1507 case QEvent::WindowStateChange: {
1508 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1509 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1510 bool result = QMainWindow::event(e);
1511 bool nfstate = (windowState() & Qt::WindowFullScreen);
1512 if (!ofstate && nfstate) {
1513 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1514 // switch to full-screen state
1515 if (lyxrc.full_screen_statusbar)
1516 statusBar()->hide();
1517 if (lyxrc.full_screen_menubar)
1519 if (lyxrc.full_screen_toolbars) {
1520 for (auto const & tb_p : d.toolbars_)
1521 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1522 tb_p.second->hide();
1524 for (int i = 0; i != d.splitter_->count(); ++i)
1525 d.tabWorkArea(i)->setFullScreen(true);
1526 #if QT_VERSION > 0x050903
1527 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1528 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1530 setContentsMargins(-2, -2, -2, -2);
1532 hideDialogs("prefs", nullptr);
1533 } else if (ofstate && !nfstate) {
1534 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1535 // switch back from full-screen state
1536 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1537 statusBar()->show();
1538 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1540 if (lyxrc.full_screen_toolbars) {
1541 for (auto const & tb_p : d.toolbars_)
1542 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1543 tb_p.second->show();
1546 for (int i = 0; i != d.splitter_->count(); ++i)
1547 d.tabWorkArea(i)->setFullScreen(false);
1548 #if QT_VERSION > 0x050903
1549 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1551 setContentsMargins(0, 0, 0, 0);
1555 case QEvent::WindowActivate: {
1556 GuiView * old_view = guiApp->currentView();
1557 if (this == old_view) {
1559 return QMainWindow::event(e);
1561 if (old_view && old_view->currentBufferView()) {
1562 // save current selection to the selection buffer to allow
1563 // middle-button paste in this window.
1564 cap::saveSelection(old_view->currentBufferView()->cursor());
1566 guiApp->setCurrentView(this);
1567 if (d.current_work_area_)
1568 on_currentWorkAreaChanged(d.current_work_area_);
1572 return QMainWindow::event(e);
1575 case QEvent::ShortcutOverride: {
1577 if (isFullScreen() && menuBar()->isHidden()) {
1578 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1579 // FIXME: we should also try to detect special LyX shortcut such as
1580 // Alt-P and Alt-M. Right now there is a hack in
1581 // GuiWorkArea::processKeySym() that hides again the menubar for
1583 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1585 return QMainWindow::event(e);
1588 return QMainWindow::event(e);
1591 case QEvent::ApplicationPaletteChange: {
1592 // runtime switch from/to dark mode
1594 return QMainWindow::event(e);
1598 return QMainWindow::event(e);
1602 void GuiView::resetWindowTitle()
1604 setWindowTitle(qt_("LyX"));
1607 bool GuiView::focusNextPrevChild(bool /*next*/)
1614 bool GuiView::busy() const
1620 void GuiView::setBusy(bool busy)
1622 bool const busy_before = busy_ > 0;
1623 busy ? ++busy_ : --busy_;
1624 if ((busy_ > 0) == busy_before)
1625 // busy state didn't change
1629 QApplication::setOverrideCursor(Qt::WaitCursor);
1632 QApplication::restoreOverrideCursor();
1637 void GuiView::resetCommandExecute()
1639 command_execute_ = false;
1644 double GuiView::pixelRatio() const
1646 #if QT_VERSION >= 0x050000
1647 return qt_scale_factor * devicePixelRatio();
1654 GuiWorkArea * GuiView::workArea(int index)
1656 if (TabWorkArea * twa = d.currentTabWorkArea())
1657 if (index < twa->count())
1658 return twa->workArea(index);
1663 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1665 if (currentWorkArea()
1666 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1667 return currentWorkArea();
1668 if (TabWorkArea * twa = d.currentTabWorkArea())
1669 return twa->workArea(buffer);
1674 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1676 // Automatically create a TabWorkArea if there are none yet.
1677 TabWorkArea * tab_widget = d.splitter_->count()
1678 ? d.currentTabWorkArea() : addTabWorkArea();
1679 return tab_widget->addWorkArea(buffer, *this);
1683 TabWorkArea * GuiView::addTabWorkArea()
1685 TabWorkArea * twa = new TabWorkArea;
1686 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1687 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1688 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1689 this, SLOT(on_lastWorkAreaRemoved()));
1691 d.splitter_->addWidget(twa);
1692 d.stack_widget_->setCurrentWidget(d.splitter_);
1697 GuiWorkArea const * GuiView::currentWorkArea() const
1699 return d.current_work_area_;
1703 GuiWorkArea * GuiView::currentWorkArea()
1705 return d.current_work_area_;
1709 GuiWorkArea const * GuiView::currentMainWorkArea() const
1711 if (!d.currentTabWorkArea())
1713 return d.currentTabWorkArea()->currentWorkArea();
1717 GuiWorkArea * GuiView::currentMainWorkArea()
1719 if (!d.currentTabWorkArea())
1721 return d.currentTabWorkArea()->currentWorkArea();
1725 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1727 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1729 d.current_work_area_ = nullptr;
1731 Q_EMIT bufferViewChanged();
1735 // FIXME: I've no clue why this is here and why it accesses
1736 // theGuiApp()->currentView, which might be 0 (bug 6464).
1737 // See also 27525 (vfr).
1738 if (theGuiApp()->currentView() == this
1739 && theGuiApp()->currentView()->currentWorkArea() == wa)
1742 if (currentBufferView())
1743 cap::saveSelection(currentBufferView()->cursor());
1745 theGuiApp()->setCurrentView(this);
1746 d.current_work_area_ = wa;
1748 // We need to reset this now, because it will need to be
1749 // right if the tabWorkArea gets reset in the for loop. We
1750 // will change it back if we aren't in that case.
1751 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1752 d.current_main_work_area_ = wa;
1754 for (int i = 0; i != d.splitter_->count(); ++i) {
1755 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1756 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1757 << ", Current main wa: " << currentMainWorkArea());
1762 d.current_main_work_area_ = old_cmwa;
1764 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1765 on_currentWorkAreaChanged(wa);
1766 BufferView & bv = wa->bufferView();
1767 bv.cursor().fixIfBroken();
1769 wa->setUpdatesEnabled(true);
1770 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1774 void GuiView::removeWorkArea(GuiWorkArea * wa)
1776 LASSERT(wa, return);
1777 if (wa == d.current_work_area_) {
1779 disconnectBufferView();
1780 d.current_work_area_ = nullptr;
1781 d.current_main_work_area_ = nullptr;
1784 bool found_twa = false;
1785 for (int i = 0; i != d.splitter_->count(); ++i) {
1786 TabWorkArea * twa = d.tabWorkArea(i);
1787 if (twa->removeWorkArea(wa)) {
1788 // Found in this tab group, and deleted the GuiWorkArea.
1790 if (twa->count() != 0) {
1791 if (d.current_work_area_ == nullptr)
1792 // This means that we are closing the current GuiWorkArea, so
1793 // switch to the next GuiWorkArea in the found TabWorkArea.
1794 setCurrentWorkArea(twa->currentWorkArea());
1796 // No more WorkAreas in this tab group, so delete it.
1803 // It is not a tabbed work area (i.e., the search work area), so it
1804 // should be deleted by other means.
1805 LASSERT(found_twa, return);
1807 if (d.current_work_area_ == nullptr) {
1808 if (d.splitter_->count() != 0) {
1809 TabWorkArea * twa = d.currentTabWorkArea();
1810 setCurrentWorkArea(twa->currentWorkArea());
1812 // No more work areas, switch to the background widget.
1813 setCurrentWorkArea(nullptr);
1819 LayoutBox * GuiView::getLayoutDialog() const
1825 void GuiView::updateLayoutList()
1828 d.layout_->updateContents(false);
1832 void GuiView::updateToolbars()
1834 if (d.current_work_area_) {
1836 if (d.current_work_area_->bufferView().cursor().inMathed()
1837 && !d.current_work_area_->bufferView().cursor().inRegexped())
1838 context |= Toolbars::MATH;
1839 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1840 context |= Toolbars::TABLE;
1841 if (currentBufferView()->buffer().areChangesPresent()
1842 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1843 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1844 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1845 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1846 context |= Toolbars::REVIEW;
1847 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1848 context |= Toolbars::MATHMACROTEMPLATE;
1849 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1850 context |= Toolbars::IPA;
1851 if (command_execute_)
1852 context |= Toolbars::MINIBUFFER;
1853 if (minibuffer_focus_) {
1854 context |= Toolbars::MINIBUFFER_FOCUS;
1855 minibuffer_focus_ = false;
1858 for (auto const & tb_p : d.toolbars_)
1859 tb_p.second->update(context);
1861 for (auto const & tb_p : d.toolbars_)
1862 tb_p.second->update();
1866 void GuiView::refillToolbars()
1868 DynamicMenuButton::resetIconCache();
1869 for (auto const & tb_p : d.toolbars_)
1870 tb_p.second->refill();
1874 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1876 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1877 LASSERT(newBuffer, return);
1879 GuiWorkArea * wa = workArea(*newBuffer);
1880 if (wa == nullptr) {
1882 newBuffer->masterBuffer()->updateBuffer();
1884 wa = addWorkArea(*newBuffer);
1885 // scroll to the position when the BufferView was last closed
1886 if (lyxrc.use_lastfilepos) {
1887 LastFilePosSection::FilePos filepos =
1888 theSession().lastFilePos().load(newBuffer->fileName());
1889 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1892 //Disconnect the old buffer...there's no new one.
1895 connectBuffer(*newBuffer);
1896 connectBufferView(wa->bufferView());
1898 setCurrentWorkArea(wa);
1902 void GuiView::connectBuffer(Buffer & buf)
1904 buf.setGuiDelegate(this);
1908 void GuiView::disconnectBuffer()
1910 if (d.current_work_area_)
1911 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1915 void GuiView::connectBufferView(BufferView & bv)
1917 bv.setGuiDelegate(this);
1921 void GuiView::disconnectBufferView()
1923 if (d.current_work_area_)
1924 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1928 void GuiView::errors(string const & error_type, bool from_master)
1930 BufferView const * const bv = currentBufferView();
1934 ErrorList const & el = from_master ?
1935 bv->buffer().masterBuffer()->errorList(error_type) :
1936 bv->buffer().errorList(error_type);
1941 string err = error_type;
1943 err = "from_master|" + error_type;
1944 showDialog("errorlist", err);
1948 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1950 d.toc_models_.updateItem(toqstr(type), dit);
1954 void GuiView::structureChanged()
1956 // This is called from the Buffer, which has no way to ensure that cursors
1957 // in BufferView remain valid.
1958 if (documentBufferView())
1959 documentBufferView()->cursor().sanitize();
1960 // FIXME: This is slightly expensive, though less than the tocBackend update
1961 // (#9880). This also resets the view in the Toc Widget (#6675).
1962 d.toc_models_.reset(documentBufferView());
1963 // Navigator needs more than a simple update in this case. It needs to be
1965 updateDialog("toc", "");
1969 void GuiView::updateDialog(string const & name, string const & sdata)
1971 if (!isDialogVisible(name))
1974 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1975 if (it == d.dialogs_.end())
1978 Dialog * const dialog = it->second.get();
1979 if (dialog->isVisibleView())
1980 dialog->initialiseParams(sdata);
1984 BufferView * GuiView::documentBufferView()
1986 return currentMainWorkArea()
1987 ? ¤tMainWorkArea()->bufferView()
1992 BufferView const * GuiView::documentBufferView() const
1994 return currentMainWorkArea()
1995 ? ¤tMainWorkArea()->bufferView()
2000 BufferView * GuiView::currentBufferView()
2002 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2006 BufferView const * GuiView::currentBufferView() const
2008 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2012 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2013 Buffer const * orig, Buffer * clone)
2015 bool const success = clone->autoSave();
2017 busyBuffers.remove(orig);
2019 ? _("Automatic save done.")
2020 : _("Automatic save failed!");
2024 void GuiView::autoSave()
2026 LYXERR(Debug::INFO, "Running autoSave()");
2028 Buffer * buffer = documentBufferView()
2029 ? &documentBufferView()->buffer() : nullptr;
2031 resetAutosaveTimers();
2035 GuiViewPrivate::busyBuffers.insert(buffer);
2036 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2037 buffer, buffer->cloneBufferOnly());
2038 d.autosave_watcher_.setFuture(f);
2039 resetAutosaveTimers();
2043 void GuiView::resetAutosaveTimers()
2046 d.autosave_timeout_.restart();
2050 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2053 Buffer * buf = currentBufferView()
2054 ? ¤tBufferView()->buffer() : nullptr;
2055 Buffer * doc_buffer = documentBufferView()
2056 ? &(documentBufferView()->buffer()) : nullptr;
2059 /* In LyX/Mac, when a dialog is open, the menus of the
2060 application can still be accessed without giving focus to
2061 the main window. In this case, we want to disable the menu
2062 entries that are buffer-related.
2063 This code must not be used on Linux and Windows, since it
2064 would disable buffer-related entries when hovering over the
2065 menu (see bug #9574).
2067 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2073 // Check whether we need a buffer
2074 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2075 // no, exit directly
2076 flag.message(from_utf8(N_("Command not allowed with"
2077 "out any document open")));
2078 flag.setEnabled(false);
2082 if (cmd.origin() == FuncRequest::TOC) {
2083 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2084 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2085 flag.setEnabled(false);
2089 switch(cmd.action()) {
2090 case LFUN_BUFFER_IMPORT:
2093 case LFUN_MASTER_BUFFER_EXPORT:
2095 && (doc_buffer->parent() != nullptr
2096 || doc_buffer->hasChildren())
2097 && !d.processing_thread_watcher_.isRunning()
2098 // this launches a dialog, which would be in the wrong Buffer
2099 && !(::lyx::operator==(cmd.argument(), "custom"));
2102 case LFUN_MASTER_BUFFER_UPDATE:
2103 case LFUN_MASTER_BUFFER_VIEW:
2105 && (doc_buffer->parent() != nullptr
2106 || doc_buffer->hasChildren())
2107 && !d.processing_thread_watcher_.isRunning();
2110 case LFUN_BUFFER_UPDATE:
2111 case LFUN_BUFFER_VIEW: {
2112 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2116 string format = to_utf8(cmd.argument());
2117 if (cmd.argument().empty())
2118 format = doc_buffer->params().getDefaultOutputFormat();
2119 enable = doc_buffer->params().isExportable(format, true);
2123 case LFUN_BUFFER_RELOAD:
2124 enable = doc_buffer && !doc_buffer->isUnnamed()
2125 && doc_buffer->fileName().exists()
2126 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2129 case LFUN_BUFFER_RESET_EXPORT:
2130 enable = doc_buffer != nullptr;
2133 case LFUN_BUFFER_CHILD_OPEN:
2134 enable = doc_buffer != nullptr;
2137 case LFUN_MASTER_BUFFER_FORALL: {
2138 if (doc_buffer == nullptr) {
2139 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2143 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2144 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2145 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2150 for (Buffer * buf : doc_buffer->allRelatives()) {
2151 GuiWorkArea * wa = workArea(*buf);
2154 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2155 enable = flag.enabled();
2162 case LFUN_BUFFER_WRITE:
2163 enable = doc_buffer && (doc_buffer->isUnnamed()
2164 || (!doc_buffer->isClean()
2165 || cmd.argument() == "force"));
2168 //FIXME: This LFUN should be moved to GuiApplication.
2169 case LFUN_BUFFER_WRITE_ALL: {
2170 // We enable the command only if there are some modified buffers
2171 Buffer * first = theBufferList().first();
2176 // We cannot use a for loop as the buffer list is a cycle.
2178 if (!b->isClean()) {
2182 b = theBufferList().next(b);
2183 } while (b != first);
2187 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2188 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2191 case LFUN_BUFFER_EXPORT: {
2192 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2196 return doc_buffer->getStatus(cmd, flag);
2199 case LFUN_BUFFER_EXPORT_AS:
2200 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2205 case LFUN_BUFFER_WRITE_AS:
2206 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2207 enable = doc_buffer != nullptr;
2210 case LFUN_EXPORT_CANCEL:
2211 enable = d.processing_thread_watcher_.isRunning();
2214 case LFUN_BUFFER_CLOSE:
2215 case LFUN_VIEW_CLOSE:
2216 enable = doc_buffer != nullptr;
2219 case LFUN_BUFFER_CLOSE_ALL:
2220 enable = theBufferList().last() != theBufferList().first();
2223 case LFUN_BUFFER_CHKTEX: {
2224 // hide if we have no checktex command
2225 if (lyxrc.chktex_command.empty()) {
2226 flag.setUnknown(true);
2230 if (!doc_buffer || !doc_buffer->params().isLatex()
2231 || d.processing_thread_watcher_.isRunning()) {
2232 // grey out, don't hide
2240 case LFUN_VIEW_SPLIT:
2241 if (cmd.getArg(0) == "vertical")
2242 enable = doc_buffer && (d.splitter_->count() == 1 ||
2243 d.splitter_->orientation() == Qt::Vertical);
2245 enable = doc_buffer && (d.splitter_->count() == 1 ||
2246 d.splitter_->orientation() == Qt::Horizontal);
2249 case LFUN_TAB_GROUP_CLOSE:
2250 enable = d.tabWorkAreaCount() > 1;
2253 case LFUN_DEVEL_MODE_TOGGLE:
2254 flag.setOnOff(devel_mode_);
2257 case LFUN_TOOLBAR_SET: {
2258 string const name = cmd.getArg(0);
2259 string const state = cmd.getArg(1);
2260 if (name.empty() || state.empty()) {
2262 docstring const msg =
2263 _("Function toolbar-set requires two arguments!");
2267 if (state != "on" && state != "off" && state != "auto") {
2269 docstring const msg =
2270 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2275 if (GuiToolbar * t = toolbar(name)) {
2276 bool const autovis = t->visibility() & Toolbars::AUTO;
2278 flag.setOnOff(t->isVisible() && !autovis);
2279 else if (state == "off")
2280 flag.setOnOff(!t->isVisible() && !autovis);
2281 else if (state == "auto")
2282 flag.setOnOff(autovis);
2285 docstring const msg =
2286 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2292 case LFUN_TOOLBAR_TOGGLE: {
2293 string const name = cmd.getArg(0);
2294 if (GuiToolbar * t = toolbar(name))
2295 flag.setOnOff(t->isVisible());
2298 docstring const msg =
2299 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2305 case LFUN_TOOLBAR_MOVABLE: {
2306 string const name = cmd.getArg(0);
2307 // use negation since locked == !movable
2309 // toolbar name * locks all toolbars
2310 flag.setOnOff(!toolbarsMovable_);
2311 else if (GuiToolbar * t = toolbar(name))
2312 flag.setOnOff(!(t->isMovable()));
2315 docstring const msg =
2316 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2322 case LFUN_ICON_SIZE:
2323 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2326 case LFUN_DROP_LAYOUTS_CHOICE:
2327 enable = buf != nullptr;
2330 case LFUN_UI_TOGGLE:
2331 if (cmd.argument() == "zoomslider") {
2332 enable = doc_buffer;
2333 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2335 flag.setOnOff(isFullScreen());
2338 case LFUN_DIALOG_DISCONNECT_INSET:
2341 case LFUN_DIALOG_HIDE:
2342 // FIXME: should we check if the dialog is shown?
2345 case LFUN_DIALOG_TOGGLE:
2346 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2349 case LFUN_DIALOG_SHOW: {
2350 string const name = cmd.getArg(0);
2352 enable = name == "aboutlyx"
2353 || name == "file" //FIXME: should be removed.
2354 || name == "lyxfiles"
2356 || name == "texinfo"
2357 || name == "progress"
2358 || name == "compare";
2359 else if (name == "character" || name == "symbols"
2360 || name == "mathdelimiter" || name == "mathmatrix") {
2361 if (!buf || buf->isReadonly())
2364 Cursor const & cur = currentBufferView()->cursor();
2365 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2368 else if (name == "latexlog")
2369 enable = FileName(doc_buffer->logName()).isReadableFile();
2370 else if (name == "spellchecker")
2371 enable = theSpellChecker()
2372 && !doc_buffer->text().empty();
2373 else if (name == "vclog")
2374 enable = doc_buffer->lyxvc().inUse();
2378 case LFUN_DIALOG_UPDATE: {
2379 string const name = cmd.getArg(0);
2381 enable = name == "prefs";
2385 case LFUN_COMMAND_EXECUTE:
2387 case LFUN_MENU_OPEN:
2388 // Nothing to check.
2391 case LFUN_COMPLETION_INLINE:
2392 if (!d.current_work_area_
2393 || !d.current_work_area_->completer().inlinePossible(
2394 currentBufferView()->cursor()))
2398 case LFUN_COMPLETION_POPUP:
2399 if (!d.current_work_area_
2400 || !d.current_work_area_->completer().popupPossible(
2401 currentBufferView()->cursor()))
2406 if (!d.current_work_area_
2407 || !d.current_work_area_->completer().inlinePossible(
2408 currentBufferView()->cursor()))
2412 case LFUN_COMPLETION_ACCEPT:
2413 if (!d.current_work_area_
2414 || (!d.current_work_area_->completer().popupVisible()
2415 && !d.current_work_area_->completer().inlineVisible()
2416 && !d.current_work_area_->completer().completionAvailable()))
2420 case LFUN_COMPLETION_CANCEL:
2421 if (!d.current_work_area_
2422 || (!d.current_work_area_->completer().popupVisible()
2423 && !d.current_work_area_->completer().inlineVisible()))
2427 case LFUN_BUFFER_ZOOM_OUT:
2428 case LFUN_BUFFER_ZOOM_IN: {
2429 // only diff between these two is that the default for ZOOM_OUT
2431 bool const neg_zoom =
2432 convert<int>(cmd.argument()) < 0 ||
2433 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2434 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2435 docstring const msg =
2436 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2440 enable = doc_buffer;
2444 case LFUN_BUFFER_ZOOM: {
2445 bool const less_than_min_zoom =
2446 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2447 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2448 docstring const msg =
2449 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2452 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2455 enable = doc_buffer;
2459 case LFUN_BUFFER_MOVE_NEXT:
2460 case LFUN_BUFFER_MOVE_PREVIOUS:
2461 // we do not cycle when moving
2462 case LFUN_BUFFER_NEXT:
2463 case LFUN_BUFFER_PREVIOUS:
2464 // because we cycle, it doesn't matter whether on first or last
2465 enable = (d.currentTabWorkArea()->count() > 1);
2467 case LFUN_BUFFER_SWITCH:
2468 // toggle on the current buffer, but do not toggle off
2469 // the other ones (is that a good idea?)
2471 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2472 flag.setOnOff(true);
2475 case LFUN_VC_REGISTER:
2476 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2478 case LFUN_VC_RENAME:
2479 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2482 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2484 case LFUN_VC_CHECK_IN:
2485 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2487 case LFUN_VC_CHECK_OUT:
2488 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2490 case LFUN_VC_LOCKING_TOGGLE:
2491 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2492 && doc_buffer->lyxvc().lockingToggleEnabled();
2493 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2495 case LFUN_VC_REVERT:
2496 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2497 && !doc_buffer->hasReadonlyFlag();
2499 case LFUN_VC_UNDO_LAST:
2500 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2502 case LFUN_VC_REPO_UPDATE:
2503 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2505 case LFUN_VC_COMMAND: {
2506 if (cmd.argument().empty())
2508 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2512 case LFUN_VC_COMPARE:
2513 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2516 case LFUN_SERVER_GOTO_FILE_ROW:
2517 case LFUN_LYX_ACTIVATE:
2518 case LFUN_WINDOW_RAISE:
2520 case LFUN_FORWARD_SEARCH:
2521 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2524 case LFUN_FILE_INSERT_PLAINTEXT:
2525 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2526 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2529 case LFUN_SPELLING_CONTINUOUSLY:
2530 flag.setOnOff(lyxrc.spellcheck_continuously);
2533 case LFUN_CITATION_OPEN:
2542 flag.setEnabled(false);
2548 static FileName selectTemplateFile()
2550 FileDialog dlg(qt_("Select template file"));
2551 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2552 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2554 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2555 QStringList(qt_("LyX Documents (*.lyx)")));
2557 if (result.first == FileDialog::Later)
2559 if (result.second.isEmpty())
2561 return FileName(fromqstr(result.second));
2565 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2569 Buffer * newBuffer = nullptr;
2571 newBuffer = checkAndLoadLyXFile(filename);
2572 } catch (ExceptionMessage const &) {
2579 message(_("Document not loaded."));
2583 setBuffer(newBuffer);
2584 newBuffer->errors("Parse");
2587 theSession().lastFiles().add(filename);
2588 theSession().writeFile();
2595 void GuiView::openDocument(string const & fname)
2597 string initpath = lyxrc.document_path;
2599 if (documentBufferView()) {
2600 string const trypath = documentBufferView()->buffer().filePath();
2601 // If directory is writeable, use this as default.
2602 if (FileName(trypath).isDirWritable())
2608 if (fname.empty()) {
2609 FileDialog dlg(qt_("Select document to open"));
2610 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2611 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2613 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2614 FileDialog::Result result =
2615 dlg.open(toqstr(initpath), filter);
2617 if (result.first == FileDialog::Later)
2620 filename = fromqstr(result.second);
2622 // check selected filename
2623 if (filename.empty()) {
2624 message(_("Canceled."));
2630 // get absolute path of file and add ".lyx" to the filename if
2632 FileName const fullname =
2633 fileSearch(string(), filename, "lyx", support::may_not_exist);
2634 if (!fullname.empty())
2635 filename = fullname.absFileName();
2637 if (!fullname.onlyPath().isDirectory()) {
2638 Alert::warning(_("Invalid filename"),
2639 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2640 from_utf8(fullname.absFileName())));
2644 // if the file doesn't exist and isn't already open (bug 6645),
2645 // let the user create one
2646 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2647 !LyXVC::file_not_found_hook(fullname)) {
2648 // the user specifically chose this name. Believe him.
2649 Buffer * const b = newFile(filename, string(), true);
2655 docstring const disp_fn = makeDisplayPath(filename);
2656 message(bformat(_("Opening document %1$s..."), disp_fn));
2659 Buffer * buf = loadDocument(fullname);
2661 str2 = bformat(_("Document %1$s opened."), disp_fn);
2662 if (buf->lyxvc().inUse())
2663 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2664 " " + _("Version control detected.");
2666 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2671 // FIXME: clean that
2672 static bool import(GuiView * lv, FileName const & filename,
2673 string const & format, ErrorList & errorList)
2675 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2677 string loader_format;
2678 vector<string> loaders = theConverters().loaders();
2679 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2680 for (string const & loader : loaders) {
2681 if (!theConverters().isReachable(format, loader))
2684 string const tofile =
2685 support::changeExtension(filename.absFileName(),
2686 theFormats().extension(loader));
2687 if (theConverters().convert(nullptr, filename, FileName(tofile),
2688 filename, format, loader, errorList) != Converters::SUCCESS)
2690 loader_format = loader;
2693 if (loader_format.empty()) {
2694 frontend::Alert::error(_("Couldn't import file"),
2695 bformat(_("No information for importing the format %1$s."),
2696 translateIfPossible(theFormats().prettyName(format))));
2700 loader_format = format;
2702 if (loader_format == "lyx") {
2703 Buffer * buf = lv->loadDocument(lyxfile);
2707 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2711 bool as_paragraphs = loader_format == "textparagraph";
2712 string filename2 = (loader_format == format) ? filename.absFileName()
2713 : support::changeExtension(filename.absFileName(),
2714 theFormats().extension(loader_format));
2715 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2717 guiApp->setCurrentView(lv);
2718 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2725 void GuiView::importDocument(string const & argument)
2728 string filename = split(argument, format, ' ');
2730 LYXERR(Debug::INFO, format << " file: " << filename);
2732 // need user interaction
2733 if (filename.empty()) {
2734 string initpath = lyxrc.document_path;
2735 if (documentBufferView()) {
2736 string const trypath = documentBufferView()->buffer().filePath();
2737 // If directory is writeable, use this as default.
2738 if (FileName(trypath).isDirWritable())
2742 docstring const text = bformat(_("Select %1$s file to import"),
2743 translateIfPossible(theFormats().prettyName(format)));
2745 FileDialog dlg(toqstr(text));
2746 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2747 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2749 docstring filter = translateIfPossible(theFormats().prettyName(format));
2752 filter += from_utf8(theFormats().extensions(format));
2755 FileDialog::Result result =
2756 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2758 if (result.first == FileDialog::Later)
2761 filename = fromqstr(result.second);
2763 // check selected filename
2764 if (filename.empty())
2765 message(_("Canceled."));
2768 if (filename.empty())
2771 // get absolute path of file
2772 FileName const fullname(support::makeAbsPath(filename));
2774 // Can happen if the user entered a path into the dialog
2776 if (fullname.onlyFileName().empty()) {
2777 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2778 "Aborting import."),
2779 from_utf8(fullname.absFileName()));
2780 frontend::Alert::error(_("File name error"), msg);
2781 message(_("Canceled."));
2786 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2788 // Check if the document already is open
2789 Buffer * buf = theBufferList().getBuffer(lyxfile);
2792 if (!closeBuffer()) {
2793 message(_("Canceled."));
2798 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2800 // if the file exists already, and we didn't do
2801 // -i lyx thefile.lyx, warn
2802 if (lyxfile.exists() && fullname != lyxfile) {
2804 docstring text = bformat(_("The document %1$s already exists.\n\n"
2805 "Do you want to overwrite that document?"), displaypath);
2806 int const ret = Alert::prompt(_("Overwrite document?"),
2807 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2810 message(_("Canceled."));
2815 message(bformat(_("Importing %1$s..."), displaypath));
2816 ErrorList errorList;
2817 if (import(this, fullname, format, errorList))
2818 message(_("imported."));
2820 message(_("file not imported!"));
2822 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2826 void GuiView::newDocument(string const & filename, string templatefile,
2829 FileName initpath(lyxrc.document_path);
2830 if (documentBufferView()) {
2831 FileName const trypath(documentBufferView()->buffer().filePath());
2832 // If directory is writeable, use this as default.
2833 if (trypath.isDirWritable())
2837 if (from_template) {
2838 if (templatefile.empty())
2839 templatefile = selectTemplateFile().absFileName();
2840 if (templatefile.empty())
2845 if (filename.empty())
2846 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2848 b = newFile(filename, templatefile, true);
2853 // If no new document could be created, it is unsure
2854 // whether there is a valid BufferView.
2855 if (currentBufferView())
2856 // Ensure the cursor is correctly positioned on screen.
2857 currentBufferView()->showCursor();
2861 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2863 BufferView * bv = documentBufferView();
2868 FileName filename(to_utf8(fname));
2869 if (filename.empty()) {
2870 // Launch a file browser
2872 string initpath = lyxrc.document_path;
2873 string const trypath = bv->buffer().filePath();
2874 // If directory is writeable, use this as default.
2875 if (FileName(trypath).isDirWritable())
2879 FileDialog dlg(qt_("Select LyX document to insert"));
2880 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2881 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2883 FileDialog::Result result = dlg.open(toqstr(initpath),
2884 QStringList(qt_("LyX Documents (*.lyx)")));
2886 if (result.first == FileDialog::Later)
2890 filename.set(fromqstr(result.second));
2892 // check selected filename
2893 if (filename.empty()) {
2894 // emit message signal.
2895 message(_("Canceled."));
2900 bv->insertLyXFile(filename, ignorelang);
2901 bv->buffer().errors("Parse");
2906 string const GuiView::getTemplatesPath(Buffer & b)
2908 // We start off with the user's templates path
2909 string result = addPath(package().user_support().absFileName(), "templates");
2910 // Check for the document language
2911 string const langcode = b.params().language->code();
2912 string const shortcode = langcode.substr(0, 2);
2913 if (!langcode.empty() && shortcode != "en") {
2914 string subpath = addPath(result, shortcode);
2915 string subpath_long = addPath(result, langcode);
2916 // If we have a subdirectory for the language already,
2918 FileName sp = FileName(subpath);
2919 if (sp.isDirectory())
2921 else if (FileName(subpath_long).isDirectory())
2922 result = subpath_long;
2924 // Ask whether we should create such a subdirectory
2925 docstring const text =
2926 bformat(_("It is suggested to save the template in a subdirectory\n"
2927 "appropriate to the document language (%1$s).\n"
2928 "This subdirectory does not exists yet.\n"
2929 "Do you want to create it?"),
2930 _(b.params().language->display()));
2931 if (Alert::prompt(_("Create Language Directory?"),
2932 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2933 // If the user agreed, we try to create it and report if this failed.
2934 if (!sp.createDirectory(0777))
2935 Alert::error(_("Subdirectory creation failed!"),
2936 _("Could not create subdirectory.\n"
2937 "The template will be saved in the parent directory."));
2943 // Do we have a layout category?
2944 string const cat = b.params().baseClass() ?
2945 b.params().baseClass()->category()
2948 string subpath = addPath(result, cat);
2949 // If we have a subdirectory for the category already,
2951 FileName sp = FileName(subpath);
2952 if (sp.isDirectory())
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 layout category (%1$s).\n"
2959 "This subdirectory does not exists yet.\n"
2960 "Do you want to create it?"),
2962 if (Alert::prompt(_("Create Category 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."));
2978 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2980 FileName fname = b.fileName();
2981 FileName const oldname = fname;
2982 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2984 if (!newname.empty()) {
2987 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2989 fname = support::makeAbsPath(to_utf8(newname),
2990 oldname.onlyPath().absFileName());
2992 // Switch to this Buffer.
2995 // No argument? Ask user through dialog.
2997 QString const title = as_template ? qt_("Choose a filename to save template as")
2998 : qt_("Choose a filename to save document as");
2999 FileDialog dlg(title);
3000 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3001 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3003 if (!isLyXFileName(fname.absFileName()))
3004 fname.changeExtension(".lyx");
3006 string const path = as_template ?
3008 : fname.onlyPath().absFileName();
3009 FileDialog::Result result =
3010 dlg.save(toqstr(path),
3011 QStringList(qt_("LyX Documents (*.lyx)")),
3012 toqstr(fname.onlyFileName()));
3014 if (result.first == FileDialog::Later)
3017 fname.set(fromqstr(result.second));
3022 if (!isLyXFileName(fname.absFileName()))
3023 fname.changeExtension(".lyx");
3026 // fname is now the new Buffer location.
3028 // if there is already a Buffer open with this name, we do not want
3029 // to have another one. (the second test makes sure we're not just
3030 // trying to overwrite ourselves, which is fine.)
3031 if (theBufferList().exists(fname) && fname != oldname
3032 && theBufferList().getBuffer(fname) != &b) {
3033 docstring const text =
3034 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3035 "Please close it before attempting to overwrite it.\n"
3036 "Do you want to choose a new filename?"),
3037 from_utf8(fname.absFileName()));
3038 int const ret = Alert::prompt(_("Chosen File Already Open"),
3039 text, 0, 1, _("&Rename"), _("&Cancel"));
3041 case 0: return renameBuffer(b, docstring(), kind);
3042 case 1: return false;
3047 bool const existsLocal = fname.exists();
3048 bool const existsInVC = LyXVC::fileInVC(fname);
3049 if (existsLocal || existsInVC) {
3050 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3051 if (kind != LV_WRITE_AS && existsInVC) {
3052 // renaming to a name that is already in VC
3054 docstring text = bformat(_("The document %1$s "
3055 "is already registered.\n\n"
3056 "Do you want to choose a new name?"),
3058 docstring const title = (kind == LV_VC_RENAME) ?
3059 _("Rename document?") : _("Copy document?");
3060 docstring const button = (kind == LV_VC_RENAME) ?
3061 _("&Rename") : _("&Copy");
3062 int const ret = Alert::prompt(title, text, 0, 1,
3063 button, _("&Cancel"));
3065 case 0: return renameBuffer(b, docstring(), kind);
3066 case 1: return false;
3071 docstring text = bformat(_("The document %1$s "
3072 "already exists.\n\n"
3073 "Do you want to overwrite that document?"),
3075 int const ret = Alert::prompt(_("Overwrite document?"),
3076 text, 0, 2, _("&Overwrite"),
3077 _("&Rename"), _("&Cancel"));
3080 case 1: return renameBuffer(b, docstring(), kind);
3081 case 2: return false;
3087 case LV_VC_RENAME: {
3088 string msg = b.lyxvc().rename(fname);
3091 message(from_utf8(msg));
3095 string msg = b.lyxvc().copy(fname);
3098 message(from_utf8(msg));
3102 case LV_WRITE_AS_TEMPLATE:
3105 // LyXVC created the file already in case of LV_VC_RENAME or
3106 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3107 // relative paths of included stuff right if we moved e.g. from
3108 // /a/b.lyx to /a/c/b.lyx.
3110 bool const saved = saveBuffer(b, fname);
3117 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3119 FileName fname = b.fileName();
3121 FileDialog dlg(qt_("Choose a filename to export the document as"));
3122 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3125 QString const anyformat = qt_("Guess from extension (*.*)");
3128 vector<Format const *> export_formats;
3129 for (Format const & f : theFormats())
3130 if (f.documentFormat())
3131 export_formats.push_back(&f);
3132 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3133 map<QString, string> fmap;
3136 for (Format const * f : export_formats) {
3137 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3138 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3140 from_ascii(f->extension())));
3141 types << loc_filter;
3142 fmap[loc_filter] = f->name();
3143 if (from_ascii(f->name()) == iformat) {
3144 filter = loc_filter;
3145 ext = f->extension();
3148 string ofname = fname.onlyFileName();
3150 ofname = support::changeExtension(ofname, ext);
3151 FileDialog::Result result =
3152 dlg.save(toqstr(fname.onlyPath().absFileName()),
3156 if (result.first != FileDialog::Chosen)
3160 fname.set(fromqstr(result.second));
3161 if (filter == anyformat)
3162 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3164 fmt_name = fmap[filter];
3165 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3166 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3168 if (fmt_name.empty() || fname.empty())
3171 // fname is now the new Buffer location.
3172 if (FileName(fname).exists()) {
3173 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3174 docstring text = bformat(_("The document %1$s already "
3175 "exists.\n\nDo you want to "
3176 "overwrite that document?"),
3178 int const ret = Alert::prompt(_("Overwrite document?"),
3179 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3182 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3183 case 2: return false;
3187 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3190 return dr.dispatched();
3194 bool GuiView::saveBuffer(Buffer & b)
3196 return saveBuffer(b, FileName());
3200 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3202 if (workArea(b) && workArea(b)->inDialogMode())
3205 if (fn.empty() && b.isUnnamed())
3206 return renameBuffer(b, docstring());
3208 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3210 theSession().lastFiles().add(b.fileName());
3211 theSession().writeFile();
3215 // Switch to this Buffer.
3218 // FIXME: we don't tell the user *WHY* the save failed !!
3219 docstring const file = makeDisplayPath(b.absFileName(), 30);
3220 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3221 "Do you want to rename the document and "
3222 "try again?"), file);
3223 int const ret = Alert::prompt(_("Rename and save?"),
3224 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3227 if (!renameBuffer(b, docstring()))
3236 return saveBuffer(b, fn);
3240 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3242 return closeWorkArea(wa, false);
3246 // We only want to close the buffer if it is not visible in other workareas
3247 // of the same view, nor in other views, and if this is not a child
3248 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3250 Buffer & buf = wa->bufferView().buffer();
3252 bool last_wa = d.countWorkAreasOf(buf) == 1
3253 && !inOtherView(buf) && !buf.parent();
3255 bool close_buffer = last_wa;
3258 if (lyxrc.close_buffer_with_last_view == "yes")
3260 else if (lyxrc.close_buffer_with_last_view == "no")
3261 close_buffer = false;
3264 if (buf.isUnnamed())
3265 file = from_utf8(buf.fileName().onlyFileName());
3267 file = buf.fileName().displayName(30);
3268 docstring const text = bformat(
3269 _("Last view on document %1$s is being closed.\n"
3270 "Would you like to close or hide the document?\n"
3272 "Hidden documents can be displayed back through\n"
3273 "the menu: View->Hidden->...\n"
3275 "To remove this question, set your preference in:\n"
3276 " Tools->Preferences->Look&Feel->UserInterface\n"
3278 int ret = Alert::prompt(_("Close or hide document?"),
3279 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3282 close_buffer = (ret == 0);
3286 return closeWorkArea(wa, close_buffer);
3290 bool GuiView::closeBuffer()
3292 GuiWorkArea * wa = currentMainWorkArea();
3293 // coverity complained about this
3294 // it seems unnecessary, but perhaps is worth the check
3295 LASSERT(wa, return false);
3297 setCurrentWorkArea(wa);
3298 Buffer & buf = wa->bufferView().buffer();
3299 return closeWorkArea(wa, !buf.parent());
3303 void GuiView::writeSession() const {
3304 GuiWorkArea const * active_wa = currentMainWorkArea();
3305 for (int i = 0; i < d.splitter_->count(); ++i) {
3306 TabWorkArea * twa = d.tabWorkArea(i);
3307 for (int j = 0; j < twa->count(); ++j) {
3308 GuiWorkArea * wa = twa->workArea(j);
3309 Buffer & buf = wa->bufferView().buffer();
3310 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3316 bool GuiView::closeBufferAll()
3319 for (auto & buf : theBufferList()) {
3320 if (!saveBufferIfNeeded(*buf, false)) {
3321 // Closing has been cancelled, so abort.
3326 // Close the workareas in all other views
3327 QList<int> const ids = guiApp->viewIds();
3328 for (int i = 0; i != ids.size(); ++i) {
3329 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3333 // Close our own workareas
3334 if (!closeWorkAreaAll())
3341 bool GuiView::closeWorkAreaAll()
3343 setCurrentWorkArea(currentMainWorkArea());
3345 // We might be in a situation that there is still a tabWorkArea, but
3346 // there are no tabs anymore. This can happen when we get here after a
3347 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3348 // many TabWorkArea's have no documents anymore.
3351 // We have to call count() each time, because it can happen that
3352 // more than one splitter will disappear in one iteration (bug 5998).
3353 while (d.splitter_->count() > empty_twa) {
3354 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3356 if (twa->count() == 0)
3359 setCurrentWorkArea(twa->currentWorkArea());
3360 if (!closeTabWorkArea(twa))
3368 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3373 Buffer & buf = wa->bufferView().buffer();
3375 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3376 Alert::warning(_("Close document"),
3377 _("Document could not be closed because it is being processed by LyX."));
3382 return closeBuffer(buf);
3384 if (!inMultiTabs(wa))
3385 if (!saveBufferIfNeeded(buf, true))
3393 bool GuiView::closeBuffer(Buffer & buf)
3395 bool success = true;
3396 for (Buffer * child_buf : buf.getChildren()) {
3397 if (theBufferList().isOthersChild(&buf, child_buf)) {
3398 child_buf->setParent(nullptr);
3402 // FIXME: should we look in other tabworkareas?
3403 // ANSWER: I don't think so. I've tested, and if the child is
3404 // open in some other window, it closes without a problem.
3405 GuiWorkArea * child_wa = workArea(*child_buf);
3408 // If we are in a close_event all children will be closed in some time,
3409 // so no need to do it here. This will ensure that the children end up
3410 // in the session file in the correct order. If we close the master
3411 // buffer, we can close or release the child buffers here too.
3413 success = closeWorkArea(child_wa, true);
3417 // In this case the child buffer is open but hidden.
3418 // Even in this case, children can be dirty (e.g.,
3419 // after a label change in the master, see #11405).
3420 // Therefore, check this
3421 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3422 // If we are in a close_event all children will be closed in some time,
3423 // so no need to do it here. This will ensure that the children end up
3424 // in the session file in the correct order. If we close the master
3425 // buffer, we can close or release the child buffers here too.
3428 // Save dirty buffers also if closing_!
3429 if (saveBufferIfNeeded(*child_buf, false)) {
3430 child_buf->removeAutosaveFile();
3431 theBufferList().release(child_buf);
3433 // Saving of dirty children has been cancelled.
3434 // Cancel the whole process.
3441 // goto bookmark to update bookmark pit.
3442 // FIXME: we should update only the bookmarks related to this buffer!
3443 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3444 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3445 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3446 guiApp->gotoBookmark(i, false, false);
3448 if (saveBufferIfNeeded(buf, false)) {
3449 buf.removeAutosaveFile();
3450 theBufferList().release(&buf);
3454 // open all children again to avoid a crash because of dangling
3455 // pointers (bug 6603)
3461 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3463 while (twa == d.currentTabWorkArea()) {
3464 twa->setCurrentIndex(twa->count() - 1);
3466 GuiWorkArea * wa = twa->currentWorkArea();
3467 Buffer & b = wa->bufferView().buffer();
3469 // We only want to close the buffer if the same buffer is not visible
3470 // in another view, and if this is not a child and if we are closing
3471 // a view (not a tabgroup).
3472 bool const close_buffer =
3473 !inOtherView(b) && !b.parent() && closing_;
3475 if (!closeWorkArea(wa, close_buffer))
3482 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3484 if (buf.isClean() || buf.paragraphs().empty())
3487 // Switch to this Buffer.
3493 if (buf.isUnnamed()) {
3494 file = from_utf8(buf.fileName().onlyFileName());
3497 FileName filename = buf.fileName();
3499 file = filename.displayName(30);
3500 exists = filename.exists();
3503 // Bring this window to top before asking questions.
3508 if (hiding && buf.isUnnamed()) {
3509 docstring const text = bformat(_("The document %1$s has not been "
3510 "saved yet.\n\nDo you want to save "
3511 "the document?"), file);
3512 ret = Alert::prompt(_("Save new document?"),
3513 text, 0, 1, _("&Save"), _("&Cancel"));
3517 docstring const text = exists ?
3518 bformat(_("The document %1$s has unsaved changes."
3519 "\n\nDo you want to save the document or "
3520 "discard the changes?"), file) :
3521 bformat(_("The document %1$s has not been saved yet."
3522 "\n\nDo you want to save the document or "
3523 "discard it entirely?"), file);
3524 docstring const title = exists ?
3525 _("Save changed document?") : _("Save document?");
3526 ret = Alert::prompt(title, text, 0, 2,
3527 _("&Save"), _("&Discard"), _("&Cancel"));
3532 if (!saveBuffer(buf))
3536 // If we crash after this we could have no autosave file
3537 // but I guess this is really improbable (Jug).
3538 // Sometimes improbable things happen:
3539 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3540 // buf.removeAutosaveFile();
3542 // revert all changes
3553 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3555 Buffer & buf = wa->bufferView().buffer();
3557 for (int i = 0; i != d.splitter_->count(); ++i) {
3558 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3559 if (wa_ && wa_ != wa)
3562 return inOtherView(buf);
3566 bool GuiView::inOtherView(Buffer & buf)
3568 QList<int> const ids = guiApp->viewIds();
3570 for (int i = 0; i != ids.size(); ++i) {
3574 if (guiApp->view(ids[i]).workArea(buf))
3581 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3583 if (!documentBufferView())
3586 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3587 Buffer * const curbuf = &documentBufferView()->buffer();
3588 int nwa = twa->count();
3589 for (int i = 0; i < nwa; ++i) {
3590 if (&workArea(i)->bufferView().buffer() == curbuf) {
3592 if (np == NEXTBUFFER)
3593 next_index = (i == nwa - 1 ? 0 : i + 1);
3595 next_index = (i == 0 ? nwa - 1 : i - 1);
3597 twa->moveTab(i, next_index);
3599 setBuffer(&workArea(next_index)->bufferView().buffer());
3607 /// make sure the document is saved
3608 static bool ensureBufferClean(Buffer * buffer)
3610 LASSERT(buffer, return false);
3611 if (buffer->isClean() && !buffer->isUnnamed())
3614 docstring const file = buffer->fileName().displayName(30);
3617 if (!buffer->isUnnamed()) {
3618 text = bformat(_("The document %1$s has unsaved "
3619 "changes.\n\nDo you want to save "
3620 "the document?"), file);
3621 title = _("Save changed document?");
3624 text = bformat(_("The document %1$s has not been "
3625 "saved yet.\n\nDo you want to save "
3626 "the document?"), file);
3627 title = _("Save new document?");
3629 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3632 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3634 return buffer->isClean() && !buffer->isUnnamed();
3638 bool GuiView::reloadBuffer(Buffer & buf)
3640 currentBufferView()->cursor().reset();
3641 Buffer::ReadStatus status = buf.reload();
3642 return status == Buffer::ReadSuccess;
3646 void GuiView::checkExternallyModifiedBuffers()
3648 for (Buffer * buf : theBufferList()) {
3649 if (buf->fileName().exists() && buf->isChecksumModified()) {
3650 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3651 " Reload now? Any local changes will be lost."),
3652 from_utf8(buf->absFileName()));
3653 int const ret = Alert::prompt(_("Reload externally changed document?"),
3654 text, 0, 1, _("&Reload"), _("&Cancel"));
3662 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3664 Buffer * buffer = documentBufferView()
3665 ? &(documentBufferView()->buffer()) : nullptr;
3667 switch (cmd.action()) {
3668 case LFUN_VC_REGISTER:
3669 if (!buffer || !ensureBufferClean(buffer))
3671 if (!buffer->lyxvc().inUse()) {
3672 if (buffer->lyxvc().registrer()) {
3673 reloadBuffer(*buffer);
3674 dr.clearMessageUpdate();
3679 case LFUN_VC_RENAME:
3680 case LFUN_VC_COPY: {
3681 if (!buffer || !ensureBufferClean(buffer))
3683 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3684 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3685 // Some changes are not yet committed.
3686 // We test here and not in getStatus(), since
3687 // this test is expensive.
3689 LyXVC::CommandResult ret =
3690 buffer->lyxvc().checkIn(log);
3692 if (ret == LyXVC::ErrorCommand ||
3693 ret == LyXVC::VCSuccess)
3694 reloadBuffer(*buffer);
3695 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3696 frontend::Alert::error(
3697 _("Revision control error."),
3698 _("Document could not be checked in."));
3702 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3703 LV_VC_RENAME : LV_VC_COPY;
3704 renameBuffer(*buffer, cmd.argument(), kind);
3709 case LFUN_VC_CHECK_IN:
3710 if (!buffer || !ensureBufferClean(buffer))
3712 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3714 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3716 // Only skip reloading if the checkin was cancelled or
3717 // an error occurred before the real checkin VCS command
3718 // was executed, since the VCS might have changed the
3719 // file even if it could not checkin successfully.
3720 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3721 reloadBuffer(*buffer);
3725 case LFUN_VC_CHECK_OUT:
3726 if (!buffer || !ensureBufferClean(buffer))
3728 if (buffer->lyxvc().inUse()) {
3729 dr.setMessage(buffer->lyxvc().checkOut());
3730 reloadBuffer(*buffer);
3734 case LFUN_VC_LOCKING_TOGGLE:
3735 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3737 if (buffer->lyxvc().inUse()) {
3738 string res = buffer->lyxvc().lockingToggle();
3740 frontend::Alert::error(_("Revision control error."),
3741 _("Error when setting the locking property."));
3744 reloadBuffer(*buffer);
3749 case LFUN_VC_REVERT:
3752 if (buffer->lyxvc().revert()) {
3753 reloadBuffer(*buffer);
3754 dr.clearMessageUpdate();
3758 case LFUN_VC_UNDO_LAST:
3761 buffer->lyxvc().undoLast();
3762 reloadBuffer(*buffer);
3763 dr.clearMessageUpdate();
3766 case LFUN_VC_REPO_UPDATE:
3769 if (ensureBufferClean(buffer)) {
3770 dr.setMessage(buffer->lyxvc().repoUpdate());
3771 checkExternallyModifiedBuffers();
3775 case LFUN_VC_COMMAND: {
3776 string flag = cmd.getArg(0);
3777 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3780 if (contains(flag, 'M')) {
3781 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3784 string path = cmd.getArg(1);
3785 if (contains(path, "$$p") && buffer)
3786 path = subst(path, "$$p", buffer->filePath());
3787 LYXERR(Debug::LYXVC, "Directory: " << path);
3789 if (!pp.isReadableDirectory()) {
3790 lyxerr << _("Directory is not accessible.") << endl;
3793 support::PathChanger p(pp);
3795 string command = cmd.getArg(2);
3796 if (command.empty())
3799 command = subst(command, "$$i", buffer->absFileName());
3800 command = subst(command, "$$p", buffer->filePath());
3802 command = subst(command, "$$m", to_utf8(message));
3803 LYXERR(Debug::LYXVC, "Command: " << command);
3805 one.startscript(Systemcall::Wait, command);
3809 if (contains(flag, 'I'))
3810 buffer->markDirty();
3811 if (contains(flag, 'R'))
3812 reloadBuffer(*buffer);
3817 case LFUN_VC_COMPARE: {
3818 if (cmd.argument().empty()) {
3819 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3825 string rev1 = cmd.getArg(0);
3829 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3832 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3833 f2 = buffer->absFileName();
3835 string rev2 = cmd.getArg(1);
3839 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3843 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3844 f1 << "\n" << f2 << "\n" );
3845 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3846 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3856 void GuiView::openChildDocument(string const & fname)
3858 LASSERT(documentBufferView(), return);
3859 Buffer & buffer = documentBufferView()->buffer();
3860 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3861 documentBufferView()->saveBookmark(false);
3862 Buffer * child = nullptr;
3863 if (theBufferList().exists(filename)) {
3864 child = theBufferList().getBuffer(filename);
3867 message(bformat(_("Opening child document %1$s..."),
3868 makeDisplayPath(filename.absFileName())));
3869 child = loadDocument(filename, false);
3871 // Set the parent name of the child document.
3872 // This makes insertion of citations and references in the child work,
3873 // when the target is in the parent or another child document.
3875 child->setParent(&buffer);
3879 bool GuiView::goToFileRow(string const & argument)
3883 size_t i = argument.find_last_of(' ');
3884 if (i != string::npos) {
3885 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3886 istringstream is(argument.substr(i + 1));
3891 if (i == string::npos) {
3892 LYXERR0("Wrong argument: " << argument);
3895 Buffer * buf = nullptr;
3896 string const realtmp = package().temp_dir().realPath();
3897 // We have to use os::path_prefix_is() here, instead of
3898 // simply prefixIs(), because the file name comes from
3899 // an external application and may need case adjustment.
3900 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3901 buf = theBufferList().getBufferFromTmp(file_name, true);
3902 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3903 << (buf ? " success" : " failed"));
3905 // Must replace extension of the file to be .lyx
3906 // and get full path
3907 FileName const s = fileSearch(string(),
3908 support::changeExtension(file_name, ".lyx"), "lyx");
3909 // Either change buffer or load the file
3910 if (theBufferList().exists(s))
3911 buf = theBufferList().getBuffer(s);
3912 else if (s.exists()) {
3913 buf = loadDocument(s);
3918 _("File does not exist: %1$s"),
3919 makeDisplayPath(file_name)));
3925 _("No buffer for file: %1$s."),
3926 makeDisplayPath(file_name))
3931 bool success = documentBufferView()->setCursorFromRow(row);
3933 LYXERR(Debug::LATEX,
3934 "setCursorFromRow: invalid position for row " << row);
3935 frontend::Alert::error(_("Inverse Search Failed"),
3936 _("Invalid position requested by inverse search.\n"
3937 "You may need to update the viewed document."));
3943 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3945 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3946 menu->exec(QCursor::pos());
3951 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3952 Buffer const * orig, Buffer * clone, string const & format)
3954 Buffer::ExportStatus const status = func(format);
3956 // the cloning operation will have produced a clone of the entire set of
3957 // documents, starting from the master. so we must delete those.
3958 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3960 busyBuffers.remove(orig);
3965 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3966 Buffer const * orig, Buffer * clone, string const & format)
3968 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3970 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3974 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3975 Buffer const * orig, Buffer * clone, string const & format)
3977 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3979 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3983 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3984 Buffer const * orig, Buffer * clone, string const & format)
3986 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3988 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3992 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3993 Buffer const * used_buffer,
3994 docstring const & msg,
3995 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3996 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3997 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3998 bool allow_async, bool use_tmpdir)
4003 string format = argument;
4005 format = used_buffer->params().getDefaultOutputFormat();
4006 processing_format = format;
4008 progress_->clearMessages();
4011 #if EXPORT_in_THREAD
4013 GuiViewPrivate::busyBuffers.insert(used_buffer);
4014 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4015 if (!cloned_buffer) {
4016 Alert::error(_("Export Error"),
4017 _("Error cloning the Buffer."));
4020 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4025 setPreviewFuture(f);
4026 last_export_format = used_buffer->params().bufferFormat();
4029 // We are asynchronous, so we don't know here anything about the success
4032 Buffer::ExportStatus status;
4034 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4035 } else if (previewFunc) {
4036 status = (used_buffer->*previewFunc)(format);
4039 handleExportStatus(gv_, status, format);
4041 return (status == Buffer::ExportSuccess
4042 || status == Buffer::PreviewSuccess);
4046 Buffer::ExportStatus status;
4048 status = (used_buffer->*syncFunc)(format, true);
4049 } else if (previewFunc) {
4050 status = (used_buffer->*previewFunc)(format);
4053 handleExportStatus(gv_, status, format);
4055 return (status == Buffer::ExportSuccess
4056 || status == Buffer::PreviewSuccess);
4060 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4062 BufferView * bv = currentBufferView();
4063 LASSERT(bv, return);
4065 // Let the current BufferView dispatch its own actions.
4066 bv->dispatch(cmd, dr);
4067 if (dr.dispatched()) {
4068 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4069 updateDialog("document", "");
4073 // Try with the document BufferView dispatch if any.
4074 BufferView * doc_bv = documentBufferView();
4075 if (doc_bv && doc_bv != bv) {
4076 doc_bv->dispatch(cmd, dr);
4077 if (dr.dispatched()) {
4078 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4079 updateDialog("document", "");
4084 // Then let the current Cursor dispatch its own actions.
4085 bv->cursor().dispatch(cmd);
4087 // update completion. We do it here and not in
4088 // processKeySym to avoid another redraw just for a
4089 // changed inline completion
4090 if (cmd.origin() == FuncRequest::KEYBOARD) {
4091 if (cmd.action() == LFUN_SELF_INSERT
4092 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4093 updateCompletion(bv->cursor(), true, true);
4094 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4095 updateCompletion(bv->cursor(), false, true);
4097 updateCompletion(bv->cursor(), false, false);
4100 dr = bv->cursor().result();
4104 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4106 BufferView * bv = currentBufferView();
4107 // By default we won't need any update.
4108 dr.screenUpdate(Update::None);
4109 // assume cmd will be dispatched
4110 dr.dispatched(true);
4112 Buffer * doc_buffer = documentBufferView()
4113 ? &(documentBufferView()->buffer()) : nullptr;
4115 if (cmd.origin() == FuncRequest::TOC) {
4116 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4117 toc->doDispatch(bv->cursor(), cmd, dr);
4121 string const argument = to_utf8(cmd.argument());
4123 switch(cmd.action()) {
4124 case LFUN_BUFFER_CHILD_OPEN:
4125 openChildDocument(to_utf8(cmd.argument()));
4128 case LFUN_BUFFER_IMPORT:
4129 importDocument(to_utf8(cmd.argument()));
4132 case LFUN_MASTER_BUFFER_EXPORT:
4134 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4136 case LFUN_BUFFER_EXPORT: {
4139 // GCC only sees strfwd.h when building merged
4140 if (::lyx::operator==(cmd.argument(), "custom")) {
4141 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4142 // so the following test should not be needed.
4143 // In principle, we could try to switch to such a view...
4144 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4145 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4149 string const dest = cmd.getArg(1);
4150 FileName target_dir;
4151 if (!dest.empty() && FileName::isAbsolute(dest))
4152 target_dir = FileName(support::onlyPath(dest));
4154 target_dir = doc_buffer->fileName().onlyPath();
4156 string const format = (argument.empty() || argument == "default") ?
4157 doc_buffer->params().getDefaultOutputFormat() : argument;
4159 if ((dest.empty() && doc_buffer->isUnnamed())
4160 || !target_dir.isDirWritable()) {
4161 exportBufferAs(*doc_buffer, from_utf8(format));
4164 /* TODO/Review: Is it a problem to also export the children?
4165 See the update_unincluded flag */
4166 d.asyncBufferProcessing(format,
4169 &GuiViewPrivate::exportAndDestroy,
4171 nullptr, cmd.allowAsync());
4172 // TODO Inform user about success
4176 case LFUN_BUFFER_EXPORT_AS: {
4177 LASSERT(doc_buffer, break);
4178 docstring f = cmd.argument();
4180 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4181 exportBufferAs(*doc_buffer, f);
4185 case LFUN_BUFFER_UPDATE: {
4186 d.asyncBufferProcessing(argument,
4189 &GuiViewPrivate::compileAndDestroy,
4191 nullptr, cmd.allowAsync(), true);
4194 case LFUN_BUFFER_VIEW: {
4195 d.asyncBufferProcessing(argument,
4197 _("Previewing ..."),
4198 &GuiViewPrivate::previewAndDestroy,
4200 &Buffer::preview, cmd.allowAsync());
4203 case LFUN_MASTER_BUFFER_UPDATE: {
4204 d.asyncBufferProcessing(argument,
4205 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4207 &GuiViewPrivate::compileAndDestroy,
4209 nullptr, cmd.allowAsync(), true);
4212 case LFUN_MASTER_BUFFER_VIEW: {
4213 d.asyncBufferProcessing(argument,
4214 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4216 &GuiViewPrivate::previewAndDestroy,
4217 nullptr, &Buffer::preview, cmd.allowAsync());
4220 case LFUN_EXPORT_CANCEL: {
4221 Systemcall::killscript();
4224 case LFUN_BUFFER_SWITCH: {
4225 string const file_name = to_utf8(cmd.argument());
4226 if (!FileName::isAbsolute(file_name)) {
4228 dr.setMessage(_("Absolute filename expected."));
4232 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4235 dr.setMessage(_("Document not loaded"));
4239 // Do we open or switch to the buffer in this view ?
4240 if (workArea(*buffer)
4241 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4246 // Look for the buffer in other views
4247 QList<int> const ids = guiApp->viewIds();
4249 for (; i != ids.size(); ++i) {
4250 GuiView & gv = guiApp->view(ids[i]);
4251 if (gv.workArea(*buffer)) {
4253 gv.activateWindow();
4255 gv.setBuffer(buffer);
4260 // If necessary, open a new window as a last resort
4261 if (i == ids.size()) {
4262 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4268 case LFUN_BUFFER_NEXT:
4269 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4272 case LFUN_BUFFER_MOVE_NEXT:
4273 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4276 case LFUN_BUFFER_PREVIOUS:
4277 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4280 case LFUN_BUFFER_MOVE_PREVIOUS:
4281 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4284 case LFUN_BUFFER_CHKTEX:
4285 LASSERT(doc_buffer, break);
4286 doc_buffer->runChktex();
4289 case LFUN_COMMAND_EXECUTE: {
4290 command_execute_ = true;
4291 minibuffer_focus_ = true;
4294 case LFUN_DROP_LAYOUTS_CHOICE:
4295 d.layout_->showPopup();
4298 case LFUN_MENU_OPEN:
4299 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4300 menu->exec(QCursor::pos());
4303 case LFUN_FILE_INSERT: {
4304 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4305 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4306 dr.forceBufferUpdate();
4307 dr.screenUpdate(Update::Force);
4312 case LFUN_FILE_INSERT_PLAINTEXT:
4313 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4314 string const fname = to_utf8(cmd.argument());
4315 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4316 dr.setMessage(_("Absolute filename expected."));
4320 FileName filename(fname);
4321 if (fname.empty()) {
4322 FileDialog dlg(qt_("Select file to insert"));
4324 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4325 QStringList(qt_("All Files (*)")));
4327 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4328 dr.setMessage(_("Canceled."));
4332 filename.set(fromqstr(result.second));
4336 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4337 bv->dispatch(new_cmd, dr);
4342 case LFUN_BUFFER_RELOAD: {
4343 LASSERT(doc_buffer, break);
4346 bool drop = (cmd.argument() == "dump");
4349 if (!drop && !doc_buffer->isClean()) {
4350 docstring const file =
4351 makeDisplayPath(doc_buffer->absFileName(), 20);
4352 if (doc_buffer->notifiesExternalModification()) {
4353 docstring text = _("The current version will be lost. "
4354 "Are you sure you want to load the version on disk "
4355 "of the document %1$s?");
4356 ret = Alert::prompt(_("Reload saved document?"),
4357 bformat(text, file), 1, 1,
4358 _("&Reload"), _("&Cancel"));
4360 docstring text = _("Any changes will be lost. "
4361 "Are you sure you want to revert to the saved version "
4362 "of the document %1$s?");
4363 ret = Alert::prompt(_("Revert to saved document?"),
4364 bformat(text, file), 1, 1,
4365 _("&Revert"), _("&Cancel"));
4370 doc_buffer->markClean();
4371 reloadBuffer(*doc_buffer);
4372 dr.forceBufferUpdate();
4377 case LFUN_BUFFER_RESET_EXPORT:
4378 LASSERT(doc_buffer, break);
4379 doc_buffer->requireFreshStart(true);
4380 dr.setMessage(_("Buffer export reset."));
4383 case LFUN_BUFFER_WRITE:
4384 LASSERT(doc_buffer, break);
4385 saveBuffer(*doc_buffer);
4388 case LFUN_BUFFER_WRITE_AS:
4389 LASSERT(doc_buffer, break);
4390 renameBuffer(*doc_buffer, cmd.argument());
4393 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4394 LASSERT(doc_buffer, break);
4395 renameBuffer(*doc_buffer, cmd.argument(),
4396 LV_WRITE_AS_TEMPLATE);
4399 case LFUN_BUFFER_WRITE_ALL: {
4400 Buffer * first = theBufferList().first();
4403 message(_("Saving all documents..."));
4404 // We cannot use a for loop as the buffer list cycles.
4407 if (!b->isClean()) {
4409 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4411 b = theBufferList().next(b);
4412 } while (b != first);
4413 dr.setMessage(_("All documents saved."));
4417 case LFUN_MASTER_BUFFER_FORALL: {
4421 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4422 funcToRun.allowAsync(false);
4424 for (Buffer const * buf : doc_buffer->allRelatives()) {
4425 // Switch to other buffer view and resend cmd
4426 lyx::dispatch(FuncRequest(
4427 LFUN_BUFFER_SWITCH, buf->absFileName()));
4428 lyx::dispatch(funcToRun);
4431 lyx::dispatch(FuncRequest(
4432 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4436 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4437 LASSERT(doc_buffer, break);
4438 doc_buffer->clearExternalModification();
4441 case LFUN_BUFFER_CLOSE:
4445 case LFUN_BUFFER_CLOSE_ALL:
4449 case LFUN_DEVEL_MODE_TOGGLE:
4450 devel_mode_ = !devel_mode_;
4452 dr.setMessage(_("Developer mode is now enabled."));
4454 dr.setMessage(_("Developer mode is now disabled."));
4457 case LFUN_TOOLBAR_SET: {
4458 string const name = cmd.getArg(0);
4459 string const state = cmd.getArg(1);
4460 if (GuiToolbar * t = toolbar(name))
4465 case LFUN_TOOLBAR_TOGGLE: {
4466 string const name = cmd.getArg(0);
4467 if (GuiToolbar * t = toolbar(name))
4472 case LFUN_TOOLBAR_MOVABLE: {
4473 string const name = cmd.getArg(0);
4475 // toggle (all) toolbars movablility
4476 toolbarsMovable_ = !toolbarsMovable_;
4477 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4478 GuiToolbar * tb = toolbar(ti.name);
4479 if (tb && tb->isMovable() != toolbarsMovable_)
4480 // toggle toolbar movablity if it does not fit lock
4481 // (all) toolbars positions state silent = true, since
4482 // status bar notifications are slow
4485 if (toolbarsMovable_)
4486 dr.setMessage(_("Toolbars unlocked."));
4488 dr.setMessage(_("Toolbars locked."));
4489 } else if (GuiToolbar * t = toolbar(name)) {
4490 // toggle current toolbar movablity
4492 // update lock (all) toolbars positions
4493 updateLockToolbars();
4498 case LFUN_ICON_SIZE: {
4499 QSize size = d.iconSize(cmd.argument());
4501 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4502 size.width(), size.height()));
4506 case LFUN_DIALOG_UPDATE: {
4507 string const name = to_utf8(cmd.argument());
4508 if (name == "prefs" || name == "document")
4509 updateDialog(name, string());
4510 else if (name == "paragraph")
4511 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4512 else if (currentBufferView()) {
4513 Inset * inset = currentBufferView()->editedInset(name);
4514 // Can only update a dialog connected to an existing inset
4516 // FIXME: get rid of this indirection; GuiView ask the inset
4517 // if he is kind enough to update itself...
4518 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4519 //FIXME: pass DispatchResult here?
4520 inset->dispatch(currentBufferView()->cursor(), fr);
4526 case LFUN_DIALOG_TOGGLE: {
4527 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4528 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4529 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4533 case LFUN_DIALOG_DISCONNECT_INSET:
4534 disconnectDialog(to_utf8(cmd.argument()));
4537 case LFUN_DIALOG_HIDE: {
4538 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4542 case LFUN_DIALOG_SHOW: {
4543 string const name = cmd.getArg(0);
4544 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4546 if (name == "latexlog") {
4547 // getStatus checks that
4548 LASSERT(doc_buffer, break);
4549 Buffer::LogType type;
4550 string const logfile = doc_buffer->logName(&type);
4552 case Buffer::latexlog:
4555 case Buffer::buildlog:
4556 sdata = "literate ";
4559 sdata += Lexer::quoteString(logfile);
4560 showDialog("log", sdata);
4561 } else if (name == "vclog") {
4562 // getStatus checks that
4563 LASSERT(doc_buffer, break);
4564 string const sdata2 = "vc " +
4565 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4566 showDialog("log", sdata2);
4567 } else if (name == "symbols") {
4568 sdata = bv->cursor().getEncoding()->name();
4570 showDialog("symbols", sdata);
4571 } else if (name == "findreplace") {
4572 sdata = to_utf8(bv->cursor().selectionAsString(false));
4573 showDialog(name, sdata);
4575 } else if (name == "prefs" && isFullScreen()) {
4576 lfunUiToggle("fullscreen");
4577 showDialog("prefs", sdata);
4579 showDialog(name, sdata);
4584 dr.setMessage(cmd.argument());
4587 case LFUN_UI_TOGGLE: {
4588 string arg = cmd.getArg(0);
4589 if (!lfunUiToggle(arg)) {
4590 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4591 dr.setMessage(bformat(msg, from_utf8(arg)));
4593 // Make sure the keyboard focus stays in the work area.
4598 case LFUN_VIEW_SPLIT: {
4599 LASSERT(doc_buffer, break);
4600 string const orientation = cmd.getArg(0);
4601 d.splitter_->setOrientation(orientation == "vertical"
4602 ? Qt::Vertical : Qt::Horizontal);
4603 TabWorkArea * twa = addTabWorkArea();
4604 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4605 setCurrentWorkArea(wa);
4608 case LFUN_TAB_GROUP_CLOSE:
4609 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4610 closeTabWorkArea(twa);
4611 d.current_work_area_ = nullptr;
4612 twa = d.currentTabWorkArea();
4613 // Switch to the next GuiWorkArea in the found TabWorkArea.
4615 // Make sure the work area is up to date.
4616 setCurrentWorkArea(twa->currentWorkArea());
4618 setCurrentWorkArea(nullptr);
4623 case LFUN_VIEW_CLOSE:
4624 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4625 closeWorkArea(twa->currentWorkArea());
4626 d.current_work_area_ = nullptr;
4627 twa = d.currentTabWorkArea();
4628 // Switch to the next GuiWorkArea in the found TabWorkArea.
4630 // Make sure the work area is up to date.
4631 setCurrentWorkArea(twa->currentWorkArea());
4633 setCurrentWorkArea(nullptr);
4638 case LFUN_COMPLETION_INLINE:
4639 if (d.current_work_area_)
4640 d.current_work_area_->completer().showInline();
4643 case LFUN_COMPLETION_POPUP:
4644 if (d.current_work_area_)
4645 d.current_work_area_->completer().showPopup();
4650 if (d.current_work_area_)
4651 d.current_work_area_->completer().tab();
4654 case LFUN_COMPLETION_CANCEL:
4655 if (d.current_work_area_) {
4656 if (d.current_work_area_->completer().popupVisible())
4657 d.current_work_area_->completer().hidePopup();
4659 d.current_work_area_->completer().hideInline();
4663 case LFUN_COMPLETION_ACCEPT:
4664 if (d.current_work_area_)
4665 d.current_work_area_->completer().activate();
4668 case LFUN_BUFFER_ZOOM_IN:
4669 case LFUN_BUFFER_ZOOM_OUT:
4670 case LFUN_BUFFER_ZOOM: {
4671 if (cmd.argument().empty()) {
4672 if (cmd.action() == LFUN_BUFFER_ZOOM)
4674 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4679 if (cmd.action() == LFUN_BUFFER_ZOOM)
4680 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4681 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4682 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4684 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4687 // Actual zoom value: default zoom + fractional extra value
4688 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4689 if (zoom < static_cast<int>(zoom_min_))
4692 setCurrentZoom(zoom);
4694 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4695 lyxrc.currentZoom, lyxrc.defaultZoom));
4697 guiApp->fontLoader().update();
4698 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4702 case LFUN_VC_REGISTER:
4703 case LFUN_VC_RENAME:
4705 case LFUN_VC_CHECK_IN:
4706 case LFUN_VC_CHECK_OUT:
4707 case LFUN_VC_REPO_UPDATE:
4708 case LFUN_VC_LOCKING_TOGGLE:
4709 case LFUN_VC_REVERT:
4710 case LFUN_VC_UNDO_LAST:
4711 case LFUN_VC_COMMAND:
4712 case LFUN_VC_COMPARE:
4713 dispatchVC(cmd, dr);
4716 case LFUN_SERVER_GOTO_FILE_ROW:
4717 if(goToFileRow(to_utf8(cmd.argument())))
4718 dr.screenUpdate(Update::Force | Update::FitCursor);
4721 case LFUN_LYX_ACTIVATE:
4725 case LFUN_WINDOW_RAISE:
4731 case LFUN_FORWARD_SEARCH: {
4732 // it seems safe to assume we have a document buffer, since
4733 // getStatus wants one.
4734 LASSERT(doc_buffer, break);
4735 Buffer const * doc_master = doc_buffer->masterBuffer();
4736 FileName const path(doc_master->temppath());
4737 string const texname = doc_master->isChild(doc_buffer)
4738 ? DocFileName(changeExtension(
4739 doc_buffer->absFileName(),
4740 "tex")).mangledFileName()
4741 : doc_buffer->latexName();
4742 string const fulltexname =
4743 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4744 string const mastername =
4745 removeExtension(doc_master->latexName());
4746 FileName const dviname(addName(path.absFileName(),
4747 addExtension(mastername, "dvi")));
4748 FileName const pdfname(addName(path.absFileName(),
4749 addExtension(mastername, "pdf")));
4750 bool const have_dvi = dviname.exists();
4751 bool const have_pdf = pdfname.exists();
4752 if (!have_dvi && !have_pdf) {
4753 dr.setMessage(_("Please, preview the document first."));
4756 string outname = dviname.onlyFileName();
4757 string command = lyxrc.forward_search_dvi;
4758 if (!have_dvi || (have_pdf &&
4759 pdfname.lastModified() > dviname.lastModified())) {
4760 outname = pdfname.onlyFileName();
4761 command = lyxrc.forward_search_pdf;
4764 DocIterator cur = bv->cursor();
4765 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4766 LYXERR(Debug::ACTION, "Forward search: row:" << row
4768 if (row == -1 || command.empty()) {
4769 dr.setMessage(_("Couldn't proceed."));
4772 string texrow = convert<string>(row);
4774 command = subst(command, "$$n", texrow);
4775 command = subst(command, "$$f", fulltexname);
4776 command = subst(command, "$$t", texname);
4777 command = subst(command, "$$o", outname);
4779 volatile PathChanger p(path);
4781 one.startscript(Systemcall::DontWait, command);
4785 case LFUN_SPELLING_CONTINUOUSLY:
4786 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4787 dr.screenUpdate(Update::Force);
4790 case LFUN_CITATION_OPEN: {
4792 if (theFormats().getFormat("pdf"))
4793 pdfv = theFormats().getFormat("pdf")->viewer();
4794 if (theFormats().getFormat("ps"))
4795 psv = theFormats().getFormat("ps")->viewer();
4796 frontend::showTarget(argument, pdfv, psv);
4801 // The LFUN must be for one of BufferView, Buffer or Cursor;
4803 dispatchToBufferView(cmd, dr);
4807 // Need to update bv because many LFUNs here might have destroyed it
4808 bv = currentBufferView();
4810 // Clear non-empty selections
4811 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4813 Cursor & cur = bv->cursor();
4814 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4815 cur.clearSelection();
4821 bool GuiView::lfunUiToggle(string const & ui_component)
4823 if (ui_component == "scrollbar") {
4824 // hide() is of no help
4825 if (d.current_work_area_->verticalScrollBarPolicy() ==
4826 Qt::ScrollBarAlwaysOff)
4828 d.current_work_area_->setVerticalScrollBarPolicy(
4829 Qt::ScrollBarAsNeeded);
4831 d.current_work_area_->setVerticalScrollBarPolicy(
4832 Qt::ScrollBarAlwaysOff);
4833 } else if (ui_component == "statusbar") {
4834 statusBar()->setVisible(!statusBar()->isVisible());
4835 } else if (ui_component == "menubar") {
4836 menuBar()->setVisible(!menuBar()->isVisible());
4837 } else if (ui_component == "zoomslider") {
4838 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4839 zoom_in_->setVisible(zoom_slider_->isVisible());
4840 zoom_out_->setVisible(zoom_slider_->isVisible());
4841 } else if (ui_component == "frame") {
4842 int const l = contentsMargins().left();
4844 //are the frames in default state?
4845 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4847 #if QT_VERSION > 0x050903
4848 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4850 setContentsMargins(-2, -2, -2, -2);
4852 #if QT_VERSION > 0x050903
4853 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4855 setContentsMargins(0, 0, 0, 0);
4858 if (ui_component == "fullscreen") {
4866 void GuiView::toggleFullScreen()
4868 setWindowState(windowState() ^ Qt::WindowFullScreen);
4872 Buffer const * GuiView::updateInset(Inset const * inset)
4877 Buffer const * inset_buffer = &(inset->buffer());
4879 for (int i = 0; i != d.splitter_->count(); ++i) {
4880 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4883 Buffer const * buffer = &(wa->bufferView().buffer());
4884 if (inset_buffer == buffer)
4885 wa->scheduleRedraw(true);
4887 return inset_buffer;
4891 void GuiView::restartCaret()
4893 /* When we move around, or type, it's nice to be able to see
4894 * the caret immediately after the keypress.
4896 if (d.current_work_area_)
4897 d.current_work_area_->startBlinkingCaret();
4899 // Take this occasion to update the other GUI elements.
4905 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4907 if (d.current_work_area_)
4908 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4913 // This list should be kept in sync with the list of insets in
4914 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4915 // dialog should have the same name as the inset.
4916 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4917 // docs in LyXAction.cpp.
4919 char const * const dialognames[] = {
4921 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4922 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4923 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4924 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4925 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4926 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4927 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4928 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4930 char const * const * const end_dialognames =
4931 dialognames + (sizeof(dialognames) / sizeof(char *));
4935 cmpCStr(char const * name) : name_(name) {}
4936 bool operator()(char const * other) {
4937 return strcmp(other, name_) == 0;
4944 bool isValidName(string const & name)
4946 return find_if(dialognames, end_dialognames,
4947 cmpCStr(name.c_str())) != end_dialognames;
4953 void GuiView::resetDialogs()
4955 // Make sure that no LFUN uses any GuiView.
4956 guiApp->setCurrentView(nullptr);
4960 constructToolbars();
4961 guiApp->menus().fillMenuBar(menuBar(), this, false);
4962 d.layout_->updateContents(true);
4963 // Now update controls with current buffer.
4964 guiApp->setCurrentView(this);
4970 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4972 for (QObject * child: widget->children()) {
4973 if (child->inherits("QGroupBox")) {
4974 QGroupBox * box = (QGroupBox*) child;
4977 flatGroupBoxes(child, flag);
4983 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4985 if (!isValidName(name))
4988 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4990 if (it != d.dialogs_.end()) {
4992 it->second->hideView();
4993 return it->second.get();
4996 Dialog * dialog = build(name);
4997 d.dialogs_[name].reset(dialog);
4998 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4999 // Force a uniform style for group boxes
5000 // On Mac non-flat works better, on Linux flat is standard
5001 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5003 if (lyxrc.allow_geometry_session)
5004 dialog->restoreSession();
5011 void GuiView::showDialog(string const & name, string const & sdata,
5014 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5018 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5024 const string name = fromqstr(qname);
5025 const string sdata = fromqstr(qdata);
5029 Dialog * dialog = findOrBuild(name, false);
5031 bool const visible = dialog->isVisibleView();
5032 dialog->showData(sdata);
5033 if (currentBufferView())
5034 currentBufferView()->editInset(name, inset);
5035 // We only set the focus to the new dialog if it was not yet
5036 // visible in order not to change the existing previous behaviour
5038 // activateWindow is needed for floating dockviews
5039 dialog->asQWidget()->raise();
5040 dialog->asQWidget()->activateWindow();
5041 if (dialog->wantInitialFocus())
5042 dialog->asQWidget()->setFocus();
5046 catch (ExceptionMessage const &) {
5054 bool GuiView::isDialogVisible(string const & name) const
5056 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5057 if (it == d.dialogs_.end())
5059 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5063 void GuiView::hideDialog(string const & name, Inset * inset)
5065 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5066 if (it == d.dialogs_.end())
5070 if (!currentBufferView())
5072 if (inset != currentBufferView()->editedInset(name))
5076 Dialog * const dialog = it->second.get();
5077 if (dialog->isVisibleView())
5079 if (currentBufferView())
5080 currentBufferView()->editInset(name, nullptr);
5084 void GuiView::disconnectDialog(string const & name)
5086 if (!isValidName(name))
5088 if (currentBufferView())
5089 currentBufferView()->editInset(name, nullptr);
5093 void GuiView::hideAll() const
5095 for(auto const & dlg_p : d.dialogs_)
5096 dlg_p.second->hideView();
5100 void GuiView::updateDialogs()
5102 for(auto const & dlg_p : d.dialogs_) {
5103 Dialog * dialog = dlg_p.second.get();
5105 if (dialog->needBufferOpen() && !documentBufferView())
5106 hideDialog(fromqstr(dialog->name()), nullptr);
5107 else if (dialog->isVisibleView())
5108 dialog->checkStatus();
5116 Dialog * GuiView::build(string const & name)
5118 return createDialog(*this, name);
5122 SEMenu::SEMenu(QWidget * parent)
5124 QAction * action = addAction(qt_("Disable Shell Escape"));
5125 connect(action, SIGNAL(triggered()),
5126 parent, SLOT(disableShellEscape()));
5129 } // namespace frontend
5132 #include "moc_GuiView.cpp"