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({
2614 qt_("LyX Documents (*.lyx)"),
2615 qt_("LyX Document Backups (*.lyx~)"),
2616 qt_("All Files (*.*)")
2618 FileDialog::Result result =
2619 dlg.open(toqstr(initpath), filter);
2621 if (result.first == FileDialog::Later)
2624 filename = fromqstr(result.second);
2626 // check selected filename
2627 if (filename.empty()) {
2628 message(_("Canceled."));
2634 // get absolute path of file and add ".lyx" to the filename if
2636 FileName const fullname =
2637 fileSearch(string(), filename, "lyx", support::may_not_exist);
2638 if (!fullname.empty())
2639 filename = fullname.absFileName();
2641 if (!fullname.onlyPath().isDirectory()) {
2642 Alert::warning(_("Invalid filename"),
2643 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2644 from_utf8(fullname.absFileName())));
2648 // if the file doesn't exist and isn't already open (bug 6645),
2649 // let the user create one
2650 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2651 !LyXVC::file_not_found_hook(fullname)) {
2652 // the user specifically chose this name. Believe him.
2653 Buffer * const b = newFile(filename, string(), true);
2659 docstring const disp_fn = makeDisplayPath(filename);
2660 message(bformat(_("Opening document %1$s..."), disp_fn));
2663 Buffer * buf = loadDocument(fullname);
2665 str2 = bformat(_("Document %1$s opened."), disp_fn);
2666 if (buf->lyxvc().inUse())
2667 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2668 " " + _("Version control detected.");
2670 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2675 // FIXME: clean that
2676 static bool import(GuiView * lv, FileName const & filename,
2677 string const & format, ErrorList & errorList)
2679 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2681 string loader_format;
2682 vector<string> loaders = theConverters().loaders();
2683 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2684 for (string const & loader : loaders) {
2685 if (!theConverters().isReachable(format, loader))
2688 string const tofile =
2689 support::changeExtension(filename.absFileName(),
2690 theFormats().extension(loader));
2691 if (theConverters().convert(nullptr, filename, FileName(tofile),
2692 filename, format, loader, errorList) != Converters::SUCCESS)
2694 loader_format = loader;
2697 if (loader_format.empty()) {
2698 frontend::Alert::error(_("Couldn't import file"),
2699 bformat(_("No information for importing the format %1$s."),
2700 translateIfPossible(theFormats().prettyName(format))));
2704 loader_format = format;
2706 if (loader_format == "lyx") {
2707 Buffer * buf = lv->loadDocument(lyxfile);
2711 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2715 bool as_paragraphs = loader_format == "textparagraph";
2716 string filename2 = (loader_format == format) ? filename.absFileName()
2717 : support::changeExtension(filename.absFileName(),
2718 theFormats().extension(loader_format));
2719 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2721 guiApp->setCurrentView(lv);
2722 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2729 void GuiView::importDocument(string const & argument)
2732 string filename = split(argument, format, ' ');
2734 LYXERR(Debug::INFO, format << " file: " << filename);
2736 // need user interaction
2737 if (filename.empty()) {
2738 string initpath = lyxrc.document_path;
2739 if (documentBufferView()) {
2740 string const trypath = documentBufferView()->buffer().filePath();
2741 // If directory is writeable, use this as default.
2742 if (FileName(trypath).isDirWritable())
2746 docstring const text = bformat(_("Select %1$s file to import"),
2747 translateIfPossible(theFormats().prettyName(format)));
2749 FileDialog dlg(toqstr(text));
2750 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2751 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2753 docstring filter = translateIfPossible(theFormats().prettyName(format));
2756 filter += from_utf8(theFormats().extensions(format));
2759 FileDialog::Result result =
2760 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2762 if (result.first == FileDialog::Later)
2765 filename = fromqstr(result.second);
2767 // check selected filename
2768 if (filename.empty())
2769 message(_("Canceled."));
2772 if (filename.empty())
2775 // get absolute path of file
2776 FileName const fullname(support::makeAbsPath(filename));
2778 // Can happen if the user entered a path into the dialog
2780 if (fullname.onlyFileName().empty()) {
2781 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2782 "Aborting import."),
2783 from_utf8(fullname.absFileName()));
2784 frontend::Alert::error(_("File name error"), msg);
2785 message(_("Canceled."));
2790 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2792 // Check if the document already is open
2793 Buffer * buf = theBufferList().getBuffer(lyxfile);
2796 if (!closeBuffer()) {
2797 message(_("Canceled."));
2802 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2804 // if the file exists already, and we didn't do
2805 // -i lyx thefile.lyx, warn
2806 if (lyxfile.exists() && fullname != lyxfile) {
2808 docstring text = bformat(_("The document %1$s already exists.\n\n"
2809 "Do you want to overwrite that document?"), displaypath);
2810 int const ret = Alert::prompt(_("Overwrite document?"),
2811 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2814 message(_("Canceled."));
2819 message(bformat(_("Importing %1$s..."), displaypath));
2820 ErrorList errorList;
2821 if (import(this, fullname, format, errorList))
2822 message(_("imported."));
2824 message(_("file not imported!"));
2826 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2830 void GuiView::newDocument(string const & filename, string templatefile,
2833 FileName initpath(lyxrc.document_path);
2834 if (documentBufferView()) {
2835 FileName const trypath(documentBufferView()->buffer().filePath());
2836 // If directory is writeable, use this as default.
2837 if (trypath.isDirWritable())
2841 if (from_template) {
2842 if (templatefile.empty())
2843 templatefile = selectTemplateFile().absFileName();
2844 if (templatefile.empty())
2849 if (filename.empty())
2850 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2852 b = newFile(filename, templatefile, true);
2857 // If no new document could be created, it is unsure
2858 // whether there is a valid BufferView.
2859 if (currentBufferView())
2860 // Ensure the cursor is correctly positioned on screen.
2861 currentBufferView()->showCursor();
2865 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2867 BufferView * bv = documentBufferView();
2872 FileName filename(to_utf8(fname));
2873 if (filename.empty()) {
2874 // Launch a file browser
2876 string initpath = lyxrc.document_path;
2877 string const trypath = bv->buffer().filePath();
2878 // If directory is writeable, use this as default.
2879 if (FileName(trypath).isDirWritable())
2883 FileDialog dlg(qt_("Select LyX document to insert"));
2884 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2885 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2887 FileDialog::Result result = dlg.open(toqstr(initpath),
2888 QStringList(qt_("LyX Documents (*.lyx)")));
2890 if (result.first == FileDialog::Later)
2894 filename.set(fromqstr(result.second));
2896 // check selected filename
2897 if (filename.empty()) {
2898 // emit message signal.
2899 message(_("Canceled."));
2904 bv->insertLyXFile(filename, ignorelang);
2905 bv->buffer().errors("Parse");
2910 string const GuiView::getTemplatesPath(Buffer & b)
2912 // We start off with the user's templates path
2913 string result = addPath(package().user_support().absFileName(), "templates");
2914 // Check for the document language
2915 string const langcode = b.params().language->code();
2916 string const shortcode = langcode.substr(0, 2);
2917 if (!langcode.empty() && shortcode != "en") {
2918 string subpath = addPath(result, shortcode);
2919 string subpath_long = addPath(result, langcode);
2920 // If we have a subdirectory for the language already,
2922 FileName sp = FileName(subpath);
2923 if (sp.isDirectory())
2925 else if (FileName(subpath_long).isDirectory())
2926 result = subpath_long;
2928 // Ask whether we should create such a subdirectory
2929 docstring const text =
2930 bformat(_("It is suggested to save the template in a subdirectory\n"
2931 "appropriate to the document language (%1$s).\n"
2932 "This subdirectory does not exists yet.\n"
2933 "Do you want to create it?"),
2934 _(b.params().language->display()));
2935 if (Alert::prompt(_("Create Language Directory?"),
2936 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2937 // If the user agreed, we try to create it and report if this failed.
2938 if (!sp.createDirectory(0777))
2939 Alert::error(_("Subdirectory creation failed!"),
2940 _("Could not create subdirectory.\n"
2941 "The template will be saved in the parent directory."));
2947 // Do we have a layout category?
2948 string const cat = b.params().baseClass() ?
2949 b.params().baseClass()->category()
2952 string subpath = addPath(result, cat);
2953 // If we have a subdirectory for the category already,
2955 FileName sp = FileName(subpath);
2956 if (sp.isDirectory())
2959 // Ask whether we should create such a subdirectory
2960 docstring const text =
2961 bformat(_("It is suggested to save the template in a subdirectory\n"
2962 "appropriate to the layout category (%1$s).\n"
2963 "This subdirectory does not exists yet.\n"
2964 "Do you want to create it?"),
2966 if (Alert::prompt(_("Create Category Directory?"),
2967 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2968 // If the user agreed, we try to create it and report if this failed.
2969 if (!sp.createDirectory(0777))
2970 Alert::error(_("Subdirectory creation failed!"),
2971 _("Could not create subdirectory.\n"
2972 "The template will be saved in the parent directory."));
2982 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2984 FileName fname = b.fileName();
2985 FileName const oldname = fname;
2986 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2988 if (!newname.empty()) {
2991 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2993 fname = support::makeAbsPath(to_utf8(newname),
2994 oldname.onlyPath().absFileName());
2996 // Switch to this Buffer.
2999 // No argument? Ask user through dialog.
3001 QString const title = as_template ? qt_("Choose a filename to save template as")
3002 : qt_("Choose a filename to save document as");
3003 FileDialog dlg(title);
3004 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3005 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3007 if (!isLyXFileName(fname.absFileName()))
3008 fname.changeExtension(".lyx");
3010 string const path = as_template ?
3012 : fname.onlyPath().absFileName();
3013 FileDialog::Result result =
3014 dlg.save(toqstr(path),
3015 QStringList(qt_("LyX Documents (*.lyx)")),
3016 toqstr(fname.onlyFileName()));
3018 if (result.first == FileDialog::Later)
3021 fname.set(fromqstr(result.second));
3026 if (!isLyXFileName(fname.absFileName()))
3027 fname.changeExtension(".lyx");
3030 // fname is now the new Buffer location.
3032 // if there is already a Buffer open with this name, we do not want
3033 // to have another one. (the second test makes sure we're not just
3034 // trying to overwrite ourselves, which is fine.)
3035 if (theBufferList().exists(fname) && fname != oldname
3036 && theBufferList().getBuffer(fname) != &b) {
3037 docstring const text =
3038 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3039 "Please close it before attempting to overwrite it.\n"
3040 "Do you want to choose a new filename?"),
3041 from_utf8(fname.absFileName()));
3042 int const ret = Alert::prompt(_("Chosen File Already Open"),
3043 text, 0, 1, _("&Rename"), _("&Cancel"));
3045 case 0: return renameBuffer(b, docstring(), kind);
3046 case 1: return false;
3051 bool const existsLocal = fname.exists();
3052 bool const existsInVC = LyXVC::fileInVC(fname);
3053 if (existsLocal || existsInVC) {
3054 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3055 if (kind != LV_WRITE_AS && existsInVC) {
3056 // renaming to a name that is already in VC
3058 docstring text = bformat(_("The document %1$s "
3059 "is already registered.\n\n"
3060 "Do you want to choose a new name?"),
3062 docstring const title = (kind == LV_VC_RENAME) ?
3063 _("Rename document?") : _("Copy document?");
3064 docstring const button = (kind == LV_VC_RENAME) ?
3065 _("&Rename") : _("&Copy");
3066 int const ret = Alert::prompt(title, text, 0, 1,
3067 button, _("&Cancel"));
3069 case 0: return renameBuffer(b, docstring(), kind);
3070 case 1: return false;
3075 docstring text = bformat(_("The document %1$s "
3076 "already exists.\n\n"
3077 "Do you want to overwrite that document?"),
3079 int const ret = Alert::prompt(_("Overwrite document?"),
3080 text, 0, 2, _("&Overwrite"),
3081 _("&Rename"), _("&Cancel"));
3084 case 1: return renameBuffer(b, docstring(), kind);
3085 case 2: return false;
3091 case LV_VC_RENAME: {
3092 string msg = b.lyxvc().rename(fname);
3095 message(from_utf8(msg));
3099 string msg = b.lyxvc().copy(fname);
3102 message(from_utf8(msg));
3106 case LV_WRITE_AS_TEMPLATE:
3109 // LyXVC created the file already in case of LV_VC_RENAME or
3110 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3111 // relative paths of included stuff right if we moved e.g. from
3112 // /a/b.lyx to /a/c/b.lyx.
3114 bool const saved = saveBuffer(b, fname);
3121 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3123 FileName fname = b.fileName();
3125 FileDialog dlg(qt_("Choose a filename to export the document as"));
3126 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3129 QString const anyformat = qt_("Guess from extension (*.*)");
3132 vector<Format const *> export_formats;
3133 for (Format const & f : theFormats())
3134 if (f.documentFormat())
3135 export_formats.push_back(&f);
3136 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3137 map<QString, string> fmap;
3140 for (Format const * f : export_formats) {
3141 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3142 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3144 from_ascii(f->extension())));
3145 types << loc_filter;
3146 fmap[loc_filter] = f->name();
3147 if (from_ascii(f->name()) == iformat) {
3148 filter = loc_filter;
3149 ext = f->extension();
3152 string ofname = fname.onlyFileName();
3154 ofname = support::changeExtension(ofname, ext);
3155 FileDialog::Result result =
3156 dlg.save(toqstr(fname.onlyPath().absFileName()),
3160 if (result.first != FileDialog::Chosen)
3164 fname.set(fromqstr(result.second));
3165 if (filter == anyformat)
3166 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3168 fmt_name = fmap[filter];
3169 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3170 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3172 if (fmt_name.empty() || fname.empty())
3175 // fname is now the new Buffer location.
3176 if (FileName(fname).exists()) {
3177 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3178 docstring text = bformat(_("The document %1$s already "
3179 "exists.\n\nDo you want to "
3180 "overwrite that document?"),
3182 int const ret = Alert::prompt(_("Overwrite document?"),
3183 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3186 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3187 case 2: return false;
3191 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3194 return dr.dispatched();
3198 bool GuiView::saveBuffer(Buffer & b)
3200 return saveBuffer(b, FileName());
3204 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3206 if (workArea(b) && workArea(b)->inDialogMode())
3209 if (fn.empty() && b.isUnnamed())
3210 return renameBuffer(b, docstring());
3212 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3214 theSession().lastFiles().add(b.fileName());
3215 theSession().writeFile();
3219 // Switch to this Buffer.
3222 // FIXME: we don't tell the user *WHY* the save failed !!
3223 docstring const file = makeDisplayPath(b.absFileName(), 30);
3224 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3225 "Do you want to rename the document and "
3226 "try again?"), file);
3227 int const ret = Alert::prompt(_("Rename and save?"),
3228 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3231 if (!renameBuffer(b, docstring()))
3240 return saveBuffer(b, fn);
3244 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3246 return closeWorkArea(wa, false);
3250 // We only want to close the buffer if it is not visible in other workareas
3251 // of the same view, nor in other views, and if this is not a child
3252 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3254 Buffer & buf = wa->bufferView().buffer();
3256 bool last_wa = d.countWorkAreasOf(buf) == 1
3257 && !inOtherView(buf) && !buf.parent();
3259 bool close_buffer = last_wa;
3262 if (lyxrc.close_buffer_with_last_view == "yes")
3264 else if (lyxrc.close_buffer_with_last_view == "no")
3265 close_buffer = false;
3268 if (buf.isUnnamed())
3269 file = from_utf8(buf.fileName().onlyFileName());
3271 file = buf.fileName().displayName(30);
3272 docstring const text = bformat(
3273 _("Last view on document %1$s is being closed.\n"
3274 "Would you like to close or hide the document?\n"
3276 "Hidden documents can be displayed back through\n"
3277 "the menu: View->Hidden->...\n"
3279 "To remove this question, set your preference in:\n"
3280 " Tools->Preferences->Look&Feel->UserInterface\n"
3282 int ret = Alert::prompt(_("Close or hide document?"),
3283 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3286 close_buffer = (ret == 0);
3290 return closeWorkArea(wa, close_buffer);
3294 bool GuiView::closeBuffer()
3296 GuiWorkArea * wa = currentMainWorkArea();
3297 // coverity complained about this
3298 // it seems unnecessary, but perhaps is worth the check
3299 LASSERT(wa, return false);
3301 setCurrentWorkArea(wa);
3302 Buffer & buf = wa->bufferView().buffer();
3303 return closeWorkArea(wa, !buf.parent());
3307 void GuiView::writeSession() const {
3308 GuiWorkArea const * active_wa = currentMainWorkArea();
3309 for (int i = 0; i < d.splitter_->count(); ++i) {
3310 TabWorkArea * twa = d.tabWorkArea(i);
3311 for (int j = 0; j < twa->count(); ++j) {
3312 GuiWorkArea * wa = twa->workArea(j);
3313 Buffer & buf = wa->bufferView().buffer();
3314 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3320 bool GuiView::closeBufferAll()
3323 for (auto & buf : theBufferList()) {
3324 if (!saveBufferIfNeeded(*buf, false)) {
3325 // Closing has been cancelled, so abort.
3330 // Close the workareas in all other views
3331 QList<int> const ids = guiApp->viewIds();
3332 for (int i = 0; i != ids.size(); ++i) {
3333 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3337 // Close our own workareas
3338 if (!closeWorkAreaAll())
3345 bool GuiView::closeWorkAreaAll()
3347 setCurrentWorkArea(currentMainWorkArea());
3349 // We might be in a situation that there is still a tabWorkArea, but
3350 // there are no tabs anymore. This can happen when we get here after a
3351 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3352 // many TabWorkArea's have no documents anymore.
3355 // We have to call count() each time, because it can happen that
3356 // more than one splitter will disappear in one iteration (bug 5998).
3357 while (d.splitter_->count() > empty_twa) {
3358 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3360 if (twa->count() == 0)
3363 setCurrentWorkArea(twa->currentWorkArea());
3364 if (!closeTabWorkArea(twa))
3372 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3377 Buffer & buf = wa->bufferView().buffer();
3379 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3380 Alert::warning(_("Close document"),
3381 _("Document could not be closed because it is being processed by LyX."));
3386 return closeBuffer(buf);
3388 if (!inMultiTabs(wa))
3389 if (!saveBufferIfNeeded(buf, true))
3397 bool GuiView::closeBuffer(Buffer & buf)
3399 bool success = true;
3400 for (Buffer * child_buf : buf.getChildren()) {
3401 if (theBufferList().isOthersChild(&buf, child_buf)) {
3402 child_buf->setParent(nullptr);
3406 // FIXME: should we look in other tabworkareas?
3407 // ANSWER: I don't think so. I've tested, and if the child is
3408 // open in some other window, it closes without a problem.
3409 GuiWorkArea * child_wa = workArea(*child_buf);
3412 // If we are in a close_event all children will be closed in some time,
3413 // so no need to do it here. This will ensure that the children end up
3414 // in the session file in the correct order. If we close the master
3415 // buffer, we can close or release the child buffers here too.
3417 success = closeWorkArea(child_wa, true);
3421 // In this case the child buffer is open but hidden.
3422 // Even in this case, children can be dirty (e.g.,
3423 // after a label change in the master, see #11405).
3424 // Therefore, check this
3425 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3426 // If we are in a close_event all children will be closed in some time,
3427 // so no need to do it here. This will ensure that the children end up
3428 // in the session file in the correct order. If we close the master
3429 // buffer, we can close or release the child buffers here too.
3432 // Save dirty buffers also if closing_!
3433 if (saveBufferIfNeeded(*child_buf, false)) {
3434 child_buf->removeAutosaveFile();
3435 theBufferList().release(child_buf);
3437 // Saving of dirty children has been cancelled.
3438 // Cancel the whole process.
3445 // goto bookmark to update bookmark pit.
3446 // FIXME: we should update only the bookmarks related to this buffer!
3447 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3448 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3449 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3450 guiApp->gotoBookmark(i, false, false);
3452 if (saveBufferIfNeeded(buf, false)) {
3453 buf.removeAutosaveFile();
3454 theBufferList().release(&buf);
3458 // open all children again to avoid a crash because of dangling
3459 // pointers (bug 6603)
3465 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3467 while (twa == d.currentTabWorkArea()) {
3468 twa->setCurrentIndex(twa->count() - 1);
3470 GuiWorkArea * wa = twa->currentWorkArea();
3471 Buffer & b = wa->bufferView().buffer();
3473 // We only want to close the buffer if the same buffer is not visible
3474 // in another view, and if this is not a child and if we are closing
3475 // a view (not a tabgroup).
3476 bool const close_buffer =
3477 !inOtherView(b) && !b.parent() && closing_;
3479 if (!closeWorkArea(wa, close_buffer))
3486 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3488 if (buf.isClean() || buf.paragraphs().empty())
3491 // Switch to this Buffer.
3497 if (buf.isUnnamed()) {
3498 file = from_utf8(buf.fileName().onlyFileName());
3501 FileName filename = buf.fileName();
3503 file = filename.displayName(30);
3504 exists = filename.exists();
3507 // Bring this window to top before asking questions.
3512 if (hiding && buf.isUnnamed()) {
3513 docstring const text = bformat(_("The document %1$s has not been "
3514 "saved yet.\n\nDo you want to save "
3515 "the document?"), file);
3516 ret = Alert::prompt(_("Save new document?"),
3517 text, 0, 1, _("&Save"), _("&Cancel"));
3521 docstring const text = exists ?
3522 bformat(_("The document %1$s has unsaved changes."
3523 "\n\nDo you want to save the document or "
3524 "discard the changes?"), file) :
3525 bformat(_("The document %1$s has not been saved yet."
3526 "\n\nDo you want to save the document or "
3527 "discard it entirely?"), file);
3528 docstring const title = exists ?
3529 _("Save changed document?") : _("Save document?");
3530 ret = Alert::prompt(title, text, 0, 2,
3531 _("&Save"), _("&Discard"), _("&Cancel"));
3536 if (!saveBuffer(buf))
3540 // If we crash after this we could have no autosave file
3541 // but I guess this is really improbable (Jug).
3542 // Sometimes improbable things happen:
3543 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3544 // buf.removeAutosaveFile();
3546 // revert all changes
3557 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3559 Buffer & buf = wa->bufferView().buffer();
3561 for (int i = 0; i != d.splitter_->count(); ++i) {
3562 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3563 if (wa_ && wa_ != wa)
3566 return inOtherView(buf);
3570 bool GuiView::inOtherView(Buffer & buf)
3572 QList<int> const ids = guiApp->viewIds();
3574 for (int i = 0; i != ids.size(); ++i) {
3578 if (guiApp->view(ids[i]).workArea(buf))
3585 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3587 if (!documentBufferView())
3590 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3591 Buffer * const curbuf = &documentBufferView()->buffer();
3592 int nwa = twa->count();
3593 for (int i = 0; i < nwa; ++i) {
3594 if (&workArea(i)->bufferView().buffer() == curbuf) {
3596 if (np == NEXTBUFFER)
3597 next_index = (i == nwa - 1 ? 0 : i + 1);
3599 next_index = (i == 0 ? nwa - 1 : i - 1);
3601 twa->moveTab(i, next_index);
3603 setBuffer(&workArea(next_index)->bufferView().buffer());
3611 /// make sure the document is saved
3612 static bool ensureBufferClean(Buffer * buffer)
3614 LASSERT(buffer, return false);
3615 if (buffer->isClean() && !buffer->isUnnamed())
3618 docstring const file = buffer->fileName().displayName(30);
3621 if (!buffer->isUnnamed()) {
3622 text = bformat(_("The document %1$s has unsaved "
3623 "changes.\n\nDo you want to save "
3624 "the document?"), file);
3625 title = _("Save changed document?");
3628 text = bformat(_("The document %1$s has not been "
3629 "saved yet.\n\nDo you want to save "
3630 "the document?"), file);
3631 title = _("Save new document?");
3633 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3636 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3638 return buffer->isClean() && !buffer->isUnnamed();
3642 bool GuiView::reloadBuffer(Buffer & buf)
3644 currentBufferView()->cursor().reset();
3645 Buffer::ReadStatus status = buf.reload();
3646 return status == Buffer::ReadSuccess;
3650 void GuiView::checkExternallyModifiedBuffers()
3652 for (Buffer * buf : theBufferList()) {
3653 if (buf->fileName().exists() && buf->isChecksumModified()) {
3654 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3655 " Reload now? Any local changes will be lost."),
3656 from_utf8(buf->absFileName()));
3657 int const ret = Alert::prompt(_("Reload externally changed document?"),
3658 text, 0, 1, _("&Reload"), _("&Cancel"));
3666 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3668 Buffer * buffer = documentBufferView()
3669 ? &(documentBufferView()->buffer()) : nullptr;
3671 switch (cmd.action()) {
3672 case LFUN_VC_REGISTER:
3673 if (!buffer || !ensureBufferClean(buffer))
3675 if (!buffer->lyxvc().inUse()) {
3676 if (buffer->lyxvc().registrer()) {
3677 reloadBuffer(*buffer);
3678 dr.clearMessageUpdate();
3683 case LFUN_VC_RENAME:
3684 case LFUN_VC_COPY: {
3685 if (!buffer || !ensureBufferClean(buffer))
3687 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3688 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3689 // Some changes are not yet committed.
3690 // We test here and not in getStatus(), since
3691 // this test is expensive.
3693 LyXVC::CommandResult ret =
3694 buffer->lyxvc().checkIn(log);
3696 if (ret == LyXVC::ErrorCommand ||
3697 ret == LyXVC::VCSuccess)
3698 reloadBuffer(*buffer);
3699 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3700 frontend::Alert::error(
3701 _("Revision control error."),
3702 _("Document could not be checked in."));
3706 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3707 LV_VC_RENAME : LV_VC_COPY;
3708 renameBuffer(*buffer, cmd.argument(), kind);
3713 case LFUN_VC_CHECK_IN:
3714 if (!buffer || !ensureBufferClean(buffer))
3716 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3718 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3720 // Only skip reloading if the checkin was cancelled or
3721 // an error occurred before the real checkin VCS command
3722 // was executed, since the VCS might have changed the
3723 // file even if it could not checkin successfully.
3724 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3725 reloadBuffer(*buffer);
3729 case LFUN_VC_CHECK_OUT:
3730 if (!buffer || !ensureBufferClean(buffer))
3732 if (buffer->lyxvc().inUse()) {
3733 dr.setMessage(buffer->lyxvc().checkOut());
3734 reloadBuffer(*buffer);
3738 case LFUN_VC_LOCKING_TOGGLE:
3739 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3741 if (buffer->lyxvc().inUse()) {
3742 string res = buffer->lyxvc().lockingToggle();
3744 frontend::Alert::error(_("Revision control error."),
3745 _("Error when setting the locking property."));
3748 reloadBuffer(*buffer);
3753 case LFUN_VC_REVERT:
3756 if (buffer->lyxvc().revert()) {
3757 reloadBuffer(*buffer);
3758 dr.clearMessageUpdate();
3762 case LFUN_VC_UNDO_LAST:
3765 buffer->lyxvc().undoLast();
3766 reloadBuffer(*buffer);
3767 dr.clearMessageUpdate();
3770 case LFUN_VC_REPO_UPDATE:
3773 if (ensureBufferClean(buffer)) {
3774 dr.setMessage(buffer->lyxvc().repoUpdate());
3775 checkExternallyModifiedBuffers();
3779 case LFUN_VC_COMMAND: {
3780 string flag = cmd.getArg(0);
3781 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3784 if (contains(flag, 'M')) {
3785 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3788 string path = cmd.getArg(1);
3789 if (contains(path, "$$p") && buffer)
3790 path = subst(path, "$$p", buffer->filePath());
3791 LYXERR(Debug::LYXVC, "Directory: " << path);
3793 if (!pp.isReadableDirectory()) {
3794 lyxerr << _("Directory is not accessible.") << endl;
3797 support::PathChanger p(pp);
3799 string command = cmd.getArg(2);
3800 if (command.empty())
3803 command = subst(command, "$$i", buffer->absFileName());
3804 command = subst(command, "$$p", buffer->filePath());
3806 command = subst(command, "$$m", to_utf8(message));
3807 LYXERR(Debug::LYXVC, "Command: " << command);
3809 one.startscript(Systemcall::Wait, command);
3813 if (contains(flag, 'I'))
3814 buffer->markDirty();
3815 if (contains(flag, 'R'))
3816 reloadBuffer(*buffer);
3821 case LFUN_VC_COMPARE: {
3822 if (cmd.argument().empty()) {
3823 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3829 string rev1 = cmd.getArg(0);
3833 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3836 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3837 f2 = buffer->absFileName();
3839 string rev2 = cmd.getArg(1);
3843 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3847 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3848 f1 << "\n" << f2 << "\n" );
3849 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3850 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3860 void GuiView::openChildDocument(string const & fname)
3862 LASSERT(documentBufferView(), return);
3863 Buffer & buffer = documentBufferView()->buffer();
3864 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3865 documentBufferView()->saveBookmark(false);
3866 Buffer * child = nullptr;
3867 if (theBufferList().exists(filename)) {
3868 child = theBufferList().getBuffer(filename);
3871 message(bformat(_("Opening child document %1$s..."),
3872 makeDisplayPath(filename.absFileName())));
3873 child = loadDocument(filename, false);
3875 // Set the parent name of the child document.
3876 // This makes insertion of citations and references in the child work,
3877 // when the target is in the parent or another child document.
3879 child->setParent(&buffer);
3883 bool GuiView::goToFileRow(string const & argument)
3887 size_t i = argument.find_last_of(' ');
3888 if (i != string::npos) {
3889 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3890 istringstream is(argument.substr(i + 1));
3895 if (i == string::npos) {
3896 LYXERR0("Wrong argument: " << argument);
3899 Buffer * buf = nullptr;
3900 string const realtmp = package().temp_dir().realPath();
3901 // We have to use os::path_prefix_is() here, instead of
3902 // simply prefixIs(), because the file name comes from
3903 // an external application and may need case adjustment.
3904 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3905 buf = theBufferList().getBufferFromTmp(file_name, true);
3906 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3907 << (buf ? " success" : " failed"));
3909 // Must replace extension of the file to be .lyx
3910 // and get full path
3911 FileName const s = fileSearch(string(),
3912 support::changeExtension(file_name, ".lyx"), "lyx");
3913 // Either change buffer or load the file
3914 if (theBufferList().exists(s))
3915 buf = theBufferList().getBuffer(s);
3916 else if (s.exists()) {
3917 buf = loadDocument(s);
3922 _("File does not exist: %1$s"),
3923 makeDisplayPath(file_name)));
3929 _("No buffer for file: %1$s."),
3930 makeDisplayPath(file_name))
3935 bool success = documentBufferView()->setCursorFromRow(row);
3937 LYXERR(Debug::LATEX,
3938 "setCursorFromRow: invalid position for row " << row);
3939 frontend::Alert::error(_("Inverse Search Failed"),
3940 _("Invalid position requested by inverse search.\n"
3941 "You may need to update the viewed document."));
3947 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3949 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3950 menu->exec(QCursor::pos());
3955 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3956 Buffer const * orig, Buffer * clone, string const & format)
3958 Buffer::ExportStatus const status = func(format);
3960 // the cloning operation will have produced a clone of the entire set of
3961 // documents, starting from the master. so we must delete those.
3962 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3964 busyBuffers.remove(orig);
3969 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3970 Buffer const * orig, Buffer * clone, string const & format)
3972 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3974 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3978 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3979 Buffer const * orig, Buffer * clone, string const & format)
3981 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3983 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3987 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3988 Buffer const * orig, Buffer * clone, string const & format)
3990 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3992 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3996 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3997 Buffer const * used_buffer,
3998 docstring const & msg,
3999 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4000 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4001 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4002 bool allow_async, bool use_tmpdir)
4007 string format = argument;
4009 format = used_buffer->params().getDefaultOutputFormat();
4010 processing_format = format;
4012 progress_->clearMessages();
4015 #if EXPORT_in_THREAD
4017 GuiViewPrivate::busyBuffers.insert(used_buffer);
4018 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4019 if (!cloned_buffer) {
4020 Alert::error(_("Export Error"),
4021 _("Error cloning the Buffer."));
4024 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4029 setPreviewFuture(f);
4030 last_export_format = used_buffer->params().bufferFormat();
4033 // We are asynchronous, so we don't know here anything about the success
4036 Buffer::ExportStatus status;
4038 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4039 } else if (previewFunc) {
4040 status = (used_buffer->*previewFunc)(format);
4043 handleExportStatus(gv_, status, format);
4045 return (status == Buffer::ExportSuccess
4046 || status == Buffer::PreviewSuccess);
4050 Buffer::ExportStatus status;
4052 status = (used_buffer->*syncFunc)(format, true);
4053 } else if (previewFunc) {
4054 status = (used_buffer->*previewFunc)(format);
4057 handleExportStatus(gv_, status, format);
4059 return (status == Buffer::ExportSuccess
4060 || status == Buffer::PreviewSuccess);
4064 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4066 BufferView * bv = currentBufferView();
4067 LASSERT(bv, return);
4069 // Let the current BufferView dispatch its own actions.
4070 bv->dispatch(cmd, dr);
4071 if (dr.dispatched()) {
4072 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4073 updateDialog("document", "");
4077 // Try with the document BufferView dispatch if any.
4078 BufferView * doc_bv = documentBufferView();
4079 if (doc_bv && doc_bv != bv) {
4080 doc_bv->dispatch(cmd, dr);
4081 if (dr.dispatched()) {
4082 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4083 updateDialog("document", "");
4088 // Then let the current Cursor dispatch its own actions.
4089 bv->cursor().dispatch(cmd);
4091 // update completion. We do it here and not in
4092 // processKeySym to avoid another redraw just for a
4093 // changed inline completion
4094 if (cmd.origin() == FuncRequest::KEYBOARD) {
4095 if (cmd.action() == LFUN_SELF_INSERT
4096 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4097 updateCompletion(bv->cursor(), true, true);
4098 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4099 updateCompletion(bv->cursor(), false, true);
4101 updateCompletion(bv->cursor(), false, false);
4104 dr = bv->cursor().result();
4108 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4110 BufferView * bv = currentBufferView();
4111 // By default we won't need any update.
4112 dr.screenUpdate(Update::None);
4113 // assume cmd will be dispatched
4114 dr.dispatched(true);
4116 Buffer * doc_buffer = documentBufferView()
4117 ? &(documentBufferView()->buffer()) : nullptr;
4119 if (cmd.origin() == FuncRequest::TOC) {
4120 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4121 toc->doDispatch(bv->cursor(), cmd, dr);
4125 string const argument = to_utf8(cmd.argument());
4127 switch(cmd.action()) {
4128 case LFUN_BUFFER_CHILD_OPEN:
4129 openChildDocument(to_utf8(cmd.argument()));
4132 case LFUN_BUFFER_IMPORT:
4133 importDocument(to_utf8(cmd.argument()));
4136 case LFUN_MASTER_BUFFER_EXPORT:
4138 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4140 case LFUN_BUFFER_EXPORT: {
4143 // GCC only sees strfwd.h when building merged
4144 if (::lyx::operator==(cmd.argument(), "custom")) {
4145 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4146 // so the following test should not be needed.
4147 // In principle, we could try to switch to such a view...
4148 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4149 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4153 string const dest = cmd.getArg(1);
4154 FileName target_dir;
4155 if (!dest.empty() && FileName::isAbsolute(dest))
4156 target_dir = FileName(support::onlyPath(dest));
4158 target_dir = doc_buffer->fileName().onlyPath();
4160 string const format = (argument.empty() || argument == "default") ?
4161 doc_buffer->params().getDefaultOutputFormat() : argument;
4163 if ((dest.empty() && doc_buffer->isUnnamed())
4164 || !target_dir.isDirWritable()) {
4165 exportBufferAs(*doc_buffer, from_utf8(format));
4168 /* TODO/Review: Is it a problem to also export the children?
4169 See the update_unincluded flag */
4170 d.asyncBufferProcessing(format,
4173 &GuiViewPrivate::exportAndDestroy,
4175 nullptr, cmd.allowAsync());
4176 // TODO Inform user about success
4180 case LFUN_BUFFER_EXPORT_AS: {
4181 LASSERT(doc_buffer, break);
4182 docstring f = cmd.argument();
4184 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4185 exportBufferAs(*doc_buffer, f);
4189 case LFUN_BUFFER_UPDATE: {
4190 d.asyncBufferProcessing(argument,
4193 &GuiViewPrivate::compileAndDestroy,
4195 nullptr, cmd.allowAsync(), true);
4198 case LFUN_BUFFER_VIEW: {
4199 d.asyncBufferProcessing(argument,
4201 _("Previewing ..."),
4202 &GuiViewPrivate::previewAndDestroy,
4204 &Buffer::preview, cmd.allowAsync());
4207 case LFUN_MASTER_BUFFER_UPDATE: {
4208 d.asyncBufferProcessing(argument,
4209 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4211 &GuiViewPrivate::compileAndDestroy,
4213 nullptr, cmd.allowAsync(), true);
4216 case LFUN_MASTER_BUFFER_VIEW: {
4217 d.asyncBufferProcessing(argument,
4218 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4220 &GuiViewPrivate::previewAndDestroy,
4221 nullptr, &Buffer::preview, cmd.allowAsync());
4224 case LFUN_EXPORT_CANCEL: {
4225 Systemcall::killscript();
4228 case LFUN_BUFFER_SWITCH: {
4229 string const file_name = to_utf8(cmd.argument());
4230 if (!FileName::isAbsolute(file_name)) {
4232 dr.setMessage(_("Absolute filename expected."));
4236 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4239 dr.setMessage(_("Document not loaded"));
4243 // Do we open or switch to the buffer in this view ?
4244 if (workArea(*buffer)
4245 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4250 // Look for the buffer in other views
4251 QList<int> const ids = guiApp->viewIds();
4253 for (; i != ids.size(); ++i) {
4254 GuiView & gv = guiApp->view(ids[i]);
4255 if (gv.workArea(*buffer)) {
4257 gv.activateWindow();
4259 gv.setBuffer(buffer);
4264 // If necessary, open a new window as a last resort
4265 if (i == ids.size()) {
4266 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4272 case LFUN_BUFFER_NEXT:
4273 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4276 case LFUN_BUFFER_MOVE_NEXT:
4277 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4280 case LFUN_BUFFER_PREVIOUS:
4281 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4284 case LFUN_BUFFER_MOVE_PREVIOUS:
4285 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4288 case LFUN_BUFFER_CHKTEX:
4289 LASSERT(doc_buffer, break);
4290 doc_buffer->runChktex();
4293 case LFUN_COMMAND_EXECUTE: {
4294 command_execute_ = true;
4295 minibuffer_focus_ = true;
4298 case LFUN_DROP_LAYOUTS_CHOICE:
4299 d.layout_->showPopup();
4302 case LFUN_MENU_OPEN:
4303 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4304 menu->exec(QCursor::pos());
4307 case LFUN_FILE_INSERT: {
4308 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4309 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4310 dr.forceBufferUpdate();
4311 dr.screenUpdate(Update::Force);
4316 case LFUN_FILE_INSERT_PLAINTEXT:
4317 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4318 string const fname = to_utf8(cmd.argument());
4319 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4320 dr.setMessage(_("Absolute filename expected."));
4324 FileName filename(fname);
4325 if (fname.empty()) {
4326 FileDialog dlg(qt_("Select file to insert"));
4328 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4329 QStringList(qt_("All Files (*)")));
4331 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4332 dr.setMessage(_("Canceled."));
4336 filename.set(fromqstr(result.second));
4340 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4341 bv->dispatch(new_cmd, dr);
4346 case LFUN_BUFFER_RELOAD: {
4347 LASSERT(doc_buffer, break);
4350 bool drop = (cmd.argument() == "dump");
4353 if (!drop && !doc_buffer->isClean()) {
4354 docstring const file =
4355 makeDisplayPath(doc_buffer->absFileName(), 20);
4356 if (doc_buffer->notifiesExternalModification()) {
4357 docstring text = _("The current version will be lost. "
4358 "Are you sure you want to load the version on disk "
4359 "of the document %1$s?");
4360 ret = Alert::prompt(_("Reload saved document?"),
4361 bformat(text, file), 1, 1,
4362 _("&Reload"), _("&Cancel"));
4364 docstring text = _("Any changes will be lost. "
4365 "Are you sure you want to revert to the saved version "
4366 "of the document %1$s?");
4367 ret = Alert::prompt(_("Revert to saved document?"),
4368 bformat(text, file), 1, 1,
4369 _("&Revert"), _("&Cancel"));
4374 doc_buffer->markClean();
4375 reloadBuffer(*doc_buffer);
4376 dr.forceBufferUpdate();
4381 case LFUN_BUFFER_RESET_EXPORT:
4382 LASSERT(doc_buffer, break);
4383 doc_buffer->requireFreshStart(true);
4384 dr.setMessage(_("Buffer export reset."));
4387 case LFUN_BUFFER_WRITE:
4388 LASSERT(doc_buffer, break);
4389 saveBuffer(*doc_buffer);
4392 case LFUN_BUFFER_WRITE_AS:
4393 LASSERT(doc_buffer, break);
4394 renameBuffer(*doc_buffer, cmd.argument());
4397 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4398 LASSERT(doc_buffer, break);
4399 renameBuffer(*doc_buffer, cmd.argument(),
4400 LV_WRITE_AS_TEMPLATE);
4403 case LFUN_BUFFER_WRITE_ALL: {
4404 Buffer * first = theBufferList().first();
4407 message(_("Saving all documents..."));
4408 // We cannot use a for loop as the buffer list cycles.
4411 if (!b->isClean()) {
4413 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4415 b = theBufferList().next(b);
4416 } while (b != first);
4417 dr.setMessage(_("All documents saved."));
4421 case LFUN_MASTER_BUFFER_FORALL: {
4425 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4426 funcToRun.allowAsync(false);
4428 for (Buffer const * buf : doc_buffer->allRelatives()) {
4429 // Switch to other buffer view and resend cmd
4430 lyx::dispatch(FuncRequest(
4431 LFUN_BUFFER_SWITCH, buf->absFileName()));
4432 lyx::dispatch(funcToRun);
4435 lyx::dispatch(FuncRequest(
4436 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4440 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4441 LASSERT(doc_buffer, break);
4442 doc_buffer->clearExternalModification();
4445 case LFUN_BUFFER_CLOSE:
4449 case LFUN_BUFFER_CLOSE_ALL:
4453 case LFUN_DEVEL_MODE_TOGGLE:
4454 devel_mode_ = !devel_mode_;
4456 dr.setMessage(_("Developer mode is now enabled."));
4458 dr.setMessage(_("Developer mode is now disabled."));
4461 case LFUN_TOOLBAR_SET: {
4462 string const name = cmd.getArg(0);
4463 string const state = cmd.getArg(1);
4464 if (GuiToolbar * t = toolbar(name))
4469 case LFUN_TOOLBAR_TOGGLE: {
4470 string const name = cmd.getArg(0);
4471 if (GuiToolbar * t = toolbar(name))
4476 case LFUN_TOOLBAR_MOVABLE: {
4477 string const name = cmd.getArg(0);
4479 // toggle (all) toolbars movablility
4480 toolbarsMovable_ = !toolbarsMovable_;
4481 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4482 GuiToolbar * tb = toolbar(ti.name);
4483 if (tb && tb->isMovable() != toolbarsMovable_)
4484 // toggle toolbar movablity if it does not fit lock
4485 // (all) toolbars positions state silent = true, since
4486 // status bar notifications are slow
4489 if (toolbarsMovable_)
4490 dr.setMessage(_("Toolbars unlocked."));
4492 dr.setMessage(_("Toolbars locked."));
4493 } else if (GuiToolbar * t = toolbar(name)) {
4494 // toggle current toolbar movablity
4496 // update lock (all) toolbars positions
4497 updateLockToolbars();
4502 case LFUN_ICON_SIZE: {
4503 QSize size = d.iconSize(cmd.argument());
4505 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4506 size.width(), size.height()));
4510 case LFUN_DIALOG_UPDATE: {
4511 string const name = to_utf8(cmd.argument());
4512 if (name == "prefs" || name == "document")
4513 updateDialog(name, string());
4514 else if (name == "paragraph")
4515 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4516 else if (currentBufferView()) {
4517 Inset * inset = currentBufferView()->editedInset(name);
4518 // Can only update a dialog connected to an existing inset
4520 // FIXME: get rid of this indirection; GuiView ask the inset
4521 // if he is kind enough to update itself...
4522 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4523 //FIXME: pass DispatchResult here?
4524 inset->dispatch(currentBufferView()->cursor(), fr);
4530 case LFUN_DIALOG_TOGGLE: {
4531 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4532 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4533 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4537 case LFUN_DIALOG_DISCONNECT_INSET:
4538 disconnectDialog(to_utf8(cmd.argument()));
4541 case LFUN_DIALOG_HIDE: {
4542 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4546 case LFUN_DIALOG_SHOW: {
4547 string const name = cmd.getArg(0);
4548 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4550 if (name == "latexlog") {
4551 // getStatus checks that
4552 LASSERT(doc_buffer, break);
4553 Buffer::LogType type;
4554 string const logfile = doc_buffer->logName(&type);
4556 case Buffer::latexlog:
4559 case Buffer::buildlog:
4560 sdata = "literate ";
4563 sdata += Lexer::quoteString(logfile);
4564 showDialog("log", sdata);
4565 } else if (name == "vclog") {
4566 // getStatus checks that
4567 LASSERT(doc_buffer, break);
4568 string const sdata2 = "vc " +
4569 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4570 showDialog("log", sdata2);
4571 } else if (name == "symbols") {
4572 sdata = bv->cursor().getEncoding()->name();
4574 showDialog("symbols", sdata);
4575 } else if (name == "findreplace") {
4576 sdata = to_utf8(bv->cursor().selectionAsString(false));
4577 showDialog(name, sdata);
4579 } else if (name == "prefs" && isFullScreen()) {
4580 lfunUiToggle("fullscreen");
4581 showDialog("prefs", sdata);
4583 showDialog(name, sdata);
4588 dr.setMessage(cmd.argument());
4591 case LFUN_UI_TOGGLE: {
4592 string arg = cmd.getArg(0);
4593 if (!lfunUiToggle(arg)) {
4594 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4595 dr.setMessage(bformat(msg, from_utf8(arg)));
4597 // Make sure the keyboard focus stays in the work area.
4602 case LFUN_VIEW_SPLIT: {
4603 LASSERT(doc_buffer, break);
4604 string const orientation = cmd.getArg(0);
4605 d.splitter_->setOrientation(orientation == "vertical"
4606 ? Qt::Vertical : Qt::Horizontal);
4607 TabWorkArea * twa = addTabWorkArea();
4608 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4609 setCurrentWorkArea(wa);
4612 case LFUN_TAB_GROUP_CLOSE:
4613 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4614 closeTabWorkArea(twa);
4615 d.current_work_area_ = nullptr;
4616 twa = d.currentTabWorkArea();
4617 // Switch to the next GuiWorkArea in the found TabWorkArea.
4619 // Make sure the work area is up to date.
4620 setCurrentWorkArea(twa->currentWorkArea());
4622 setCurrentWorkArea(nullptr);
4627 case LFUN_VIEW_CLOSE:
4628 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4629 closeWorkArea(twa->currentWorkArea());
4630 d.current_work_area_ = nullptr;
4631 twa = d.currentTabWorkArea();
4632 // Switch to the next GuiWorkArea in the found TabWorkArea.
4634 // Make sure the work area is up to date.
4635 setCurrentWorkArea(twa->currentWorkArea());
4637 setCurrentWorkArea(nullptr);
4642 case LFUN_COMPLETION_INLINE:
4643 if (d.current_work_area_)
4644 d.current_work_area_->completer().showInline();
4647 case LFUN_COMPLETION_POPUP:
4648 if (d.current_work_area_)
4649 d.current_work_area_->completer().showPopup();
4654 if (d.current_work_area_)
4655 d.current_work_area_->completer().tab();
4658 case LFUN_COMPLETION_CANCEL:
4659 if (d.current_work_area_) {
4660 if (d.current_work_area_->completer().popupVisible())
4661 d.current_work_area_->completer().hidePopup();
4663 d.current_work_area_->completer().hideInline();
4667 case LFUN_COMPLETION_ACCEPT:
4668 if (d.current_work_area_)
4669 d.current_work_area_->completer().activate();
4672 case LFUN_BUFFER_ZOOM_IN:
4673 case LFUN_BUFFER_ZOOM_OUT:
4674 case LFUN_BUFFER_ZOOM: {
4675 if (cmd.argument().empty()) {
4676 if (cmd.action() == LFUN_BUFFER_ZOOM)
4678 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4683 if (cmd.action() == LFUN_BUFFER_ZOOM)
4684 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4685 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4686 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4688 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4691 // Actual zoom value: default zoom + fractional extra value
4692 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4693 if (zoom < static_cast<int>(zoom_min_))
4696 setCurrentZoom(zoom);
4698 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4699 lyxrc.currentZoom, lyxrc.defaultZoom));
4701 guiApp->fontLoader().update();
4702 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4706 case LFUN_VC_REGISTER:
4707 case LFUN_VC_RENAME:
4709 case LFUN_VC_CHECK_IN:
4710 case LFUN_VC_CHECK_OUT:
4711 case LFUN_VC_REPO_UPDATE:
4712 case LFUN_VC_LOCKING_TOGGLE:
4713 case LFUN_VC_REVERT:
4714 case LFUN_VC_UNDO_LAST:
4715 case LFUN_VC_COMMAND:
4716 case LFUN_VC_COMPARE:
4717 dispatchVC(cmd, dr);
4720 case LFUN_SERVER_GOTO_FILE_ROW:
4721 if(goToFileRow(to_utf8(cmd.argument())))
4722 dr.screenUpdate(Update::Force | Update::FitCursor);
4725 case LFUN_LYX_ACTIVATE:
4729 case LFUN_WINDOW_RAISE:
4735 case LFUN_FORWARD_SEARCH: {
4736 // it seems safe to assume we have a document buffer, since
4737 // getStatus wants one.
4738 LASSERT(doc_buffer, break);
4739 Buffer const * doc_master = doc_buffer->masterBuffer();
4740 FileName const path(doc_master->temppath());
4741 string const texname = doc_master->isChild(doc_buffer)
4742 ? DocFileName(changeExtension(
4743 doc_buffer->absFileName(),
4744 "tex")).mangledFileName()
4745 : doc_buffer->latexName();
4746 string const fulltexname =
4747 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4748 string const mastername =
4749 removeExtension(doc_master->latexName());
4750 FileName const dviname(addName(path.absFileName(),
4751 addExtension(mastername, "dvi")));
4752 FileName const pdfname(addName(path.absFileName(),
4753 addExtension(mastername, "pdf")));
4754 bool const have_dvi = dviname.exists();
4755 bool const have_pdf = pdfname.exists();
4756 if (!have_dvi && !have_pdf) {
4757 dr.setMessage(_("Please, preview the document first."));
4760 string outname = dviname.onlyFileName();
4761 string command = lyxrc.forward_search_dvi;
4762 if (!have_dvi || (have_pdf &&
4763 pdfname.lastModified() > dviname.lastModified())) {
4764 outname = pdfname.onlyFileName();
4765 command = lyxrc.forward_search_pdf;
4768 DocIterator cur = bv->cursor();
4769 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4770 LYXERR(Debug::ACTION, "Forward search: row:" << row
4772 if (row == -1 || command.empty()) {
4773 dr.setMessage(_("Couldn't proceed."));
4776 string texrow = convert<string>(row);
4778 command = subst(command, "$$n", texrow);
4779 command = subst(command, "$$f", fulltexname);
4780 command = subst(command, "$$t", texname);
4781 command = subst(command, "$$o", outname);
4783 volatile PathChanger p(path);
4785 one.startscript(Systemcall::DontWait, command);
4789 case LFUN_SPELLING_CONTINUOUSLY:
4790 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4791 dr.screenUpdate(Update::Force);
4794 case LFUN_CITATION_OPEN: {
4796 if (theFormats().getFormat("pdf"))
4797 pdfv = theFormats().getFormat("pdf")->viewer();
4798 if (theFormats().getFormat("ps"))
4799 psv = theFormats().getFormat("ps")->viewer();
4800 frontend::showTarget(argument, pdfv, psv);
4805 // The LFUN must be for one of BufferView, Buffer or Cursor;
4807 dispatchToBufferView(cmd, dr);
4811 // Need to update bv because many LFUNs here might have destroyed it
4812 bv = currentBufferView();
4814 // Clear non-empty selections
4815 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4817 Cursor & cur = bv->cursor();
4818 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4819 cur.clearSelection();
4825 bool GuiView::lfunUiToggle(string const & ui_component)
4827 if (ui_component == "scrollbar") {
4828 // hide() is of no help
4829 if (d.current_work_area_->verticalScrollBarPolicy() ==
4830 Qt::ScrollBarAlwaysOff)
4832 d.current_work_area_->setVerticalScrollBarPolicy(
4833 Qt::ScrollBarAsNeeded);
4835 d.current_work_area_->setVerticalScrollBarPolicy(
4836 Qt::ScrollBarAlwaysOff);
4837 } else if (ui_component == "statusbar") {
4838 statusBar()->setVisible(!statusBar()->isVisible());
4839 } else if (ui_component == "menubar") {
4840 menuBar()->setVisible(!menuBar()->isVisible());
4841 } else if (ui_component == "zoomslider") {
4842 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4843 zoom_in_->setVisible(zoom_slider_->isVisible());
4844 zoom_out_->setVisible(zoom_slider_->isVisible());
4845 } else if (ui_component == "frame") {
4846 int const l = contentsMargins().left();
4848 //are the frames in default state?
4849 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4851 #if QT_VERSION > 0x050903
4852 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4854 setContentsMargins(-2, -2, -2, -2);
4856 #if QT_VERSION > 0x050903
4857 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4859 setContentsMargins(0, 0, 0, 0);
4862 if (ui_component == "fullscreen") {
4870 void GuiView::toggleFullScreen()
4872 setWindowState(windowState() ^ Qt::WindowFullScreen);
4876 Buffer const * GuiView::updateInset(Inset const * inset)
4881 Buffer const * inset_buffer = &(inset->buffer());
4883 for (int i = 0; i != d.splitter_->count(); ++i) {
4884 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4887 Buffer const * buffer = &(wa->bufferView().buffer());
4888 if (inset_buffer == buffer)
4889 wa->scheduleRedraw(true);
4891 return inset_buffer;
4895 void GuiView::restartCaret()
4897 /* When we move around, or type, it's nice to be able to see
4898 * the caret immediately after the keypress.
4900 if (d.current_work_area_)
4901 d.current_work_area_->startBlinkingCaret();
4903 // Take this occasion to update the other GUI elements.
4909 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4911 if (d.current_work_area_)
4912 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4917 // This list should be kept in sync with the list of insets in
4918 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4919 // dialog should have the same name as the inset.
4920 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4921 // docs in LyXAction.cpp.
4923 char const * const dialognames[] = {
4925 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4926 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4927 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4928 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4929 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4930 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4931 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4932 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4934 char const * const * const end_dialognames =
4935 dialognames + (sizeof(dialognames) / sizeof(char *));
4939 cmpCStr(char const * name) : name_(name) {}
4940 bool operator()(char const * other) {
4941 return strcmp(other, name_) == 0;
4948 bool isValidName(string const & name)
4950 return find_if(dialognames, end_dialognames,
4951 cmpCStr(name.c_str())) != end_dialognames;
4957 void GuiView::resetDialogs()
4959 // Make sure that no LFUN uses any GuiView.
4960 guiApp->setCurrentView(nullptr);
4964 constructToolbars();
4965 guiApp->menus().fillMenuBar(menuBar(), this, false);
4966 d.layout_->updateContents(true);
4967 // Now update controls with current buffer.
4968 guiApp->setCurrentView(this);
4974 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4976 for (QObject * child: widget->children()) {
4977 if (child->inherits("QGroupBox")) {
4978 QGroupBox * box = (QGroupBox*) child;
4981 flatGroupBoxes(child, flag);
4987 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4989 if (!isValidName(name))
4992 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4994 if (it != d.dialogs_.end()) {
4996 it->second->hideView();
4997 return it->second.get();
5000 Dialog * dialog = build(name);
5001 d.dialogs_[name].reset(dialog);
5002 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5003 // Force a uniform style for group boxes
5004 // On Mac non-flat works better, on Linux flat is standard
5005 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5007 if (lyxrc.allow_geometry_session)
5008 dialog->restoreSession();
5015 void GuiView::showDialog(string const & name, string const & sdata,
5018 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5022 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5028 const string name = fromqstr(qname);
5029 const string sdata = fromqstr(qdata);
5033 Dialog * dialog = findOrBuild(name, false);
5035 bool const visible = dialog->isVisibleView();
5036 dialog->showData(sdata);
5037 if (currentBufferView())
5038 currentBufferView()->editInset(name, inset);
5039 // We only set the focus to the new dialog if it was not yet
5040 // visible in order not to change the existing previous behaviour
5042 // activateWindow is needed for floating dockviews
5043 dialog->asQWidget()->raise();
5044 dialog->asQWidget()->activateWindow();
5045 if (dialog->wantInitialFocus())
5046 dialog->asQWidget()->setFocus();
5050 catch (ExceptionMessage const &) {
5058 bool GuiView::isDialogVisible(string const & name) const
5060 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5061 if (it == d.dialogs_.end())
5063 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5067 void GuiView::hideDialog(string const & name, Inset * inset)
5069 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5070 if (it == d.dialogs_.end())
5074 if (!currentBufferView())
5076 if (inset != currentBufferView()->editedInset(name))
5080 Dialog * const dialog = it->second.get();
5081 if (dialog->isVisibleView())
5083 if (currentBufferView())
5084 currentBufferView()->editInset(name, nullptr);
5088 void GuiView::disconnectDialog(string const & name)
5090 if (!isValidName(name))
5092 if (currentBufferView())
5093 currentBufferView()->editInset(name, nullptr);
5097 void GuiView::hideAll() const
5099 for(auto const & dlg_p : d.dialogs_)
5100 dlg_p.second->hideView();
5104 void GuiView::updateDialogs()
5106 for(auto const & dlg_p : d.dialogs_) {
5107 Dialog * dialog = dlg_p.second.get();
5109 if (dialog->needBufferOpen() && !documentBufferView())
5110 hideDialog(fromqstr(dialog->name()), nullptr);
5111 else if (dialog->isVisibleView())
5112 dialog->checkStatus();
5120 Dialog * GuiView::build(string const & name)
5122 return createDialog(*this, name);
5126 SEMenu::SEMenu(QWidget * parent)
5128 QAction * action = addAction(qt_("Disable Shell Escape"));
5129 connect(action, SIGNAL(triggered()),
5130 parent, SLOT(disableShellEscape()));
5133 } // namespace frontend
5136 #include "moc_GuiView.cpp"