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(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 QToolButton(statusBar());
676 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
677 zoom_value_->setToolButtonStyle(Qt::ToolButtonTextOnly);
678 zoom_value_->setAutoRaise(true);
679 zoom_value_->setPopupMode(QToolButton::InstantPopup);
680 zoom_value_->setFixedHeight(fm.height());
681 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
682 zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
684 zoom_value_->setMinimumWidth(fm.width("000%"));
686 statusBar()->addPermanentWidget(zoom_value_);
687 zoom_value_->setEnabled(currentBufferView());
689 act_zoom_default_ = new QAction(toqstr(bformat(_("&Reset to default (%1$d%)"),
690 lyxrc.defaultZoom)), this);
691 act_zoom_in_ = new QAction(qt_("Zoom &in"), this);
692 act_zoom_out_ = new QAction(qt_("Zoom &out"), this);
693 act_zoom_show_ = new QAction(qt_("Show zoom slider"));
694 act_zoom_show_->setCheckable(true);
695 zoom_value_->addAction(act_zoom_default_);
696 zoom_value_->addAction(act_zoom_in_);
697 zoom_value_->addAction(act_zoom_out_);
698 zoom_value_->addAction(act_zoom_show_);
700 connect(act_zoom_default_, SIGNAL(triggered()),
701 this, SLOT(resetDefaultZoom()));
702 connect(act_zoom_in_, SIGNAL(triggered()),
703 this, SLOT(zoomInPressed()));
704 connect(act_zoom_out_, SIGNAL(triggered()),
705 this, SLOT(zoomOutPressed()));
706 connect(act_zoom_show_, SIGNAL(triggered()),
707 this, SLOT(toogleZoomSlider()));
709 int const iconheight = max(int(d.normalIconSize), fm.height());
710 QSize const iconsize(iconheight, iconheight);
712 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
713 shell_escape_ = new QLabel(statusBar());
714 shell_escape_->setPixmap(shellescape);
715 shell_escape_->setAlignment(Qt::AlignCenter);
716 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
717 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
718 "external commands for this document. "
719 "Right click to change."));
720 SEMenu * menu = new SEMenu(this);
721 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
722 menu, SLOT(showMenu(QPoint)));
723 shell_escape_->hide();
724 statusBar()->addPermanentWidget(shell_escape_);
726 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
727 read_only_ = new QLabel(statusBar());
728 read_only_->setPixmap(readonly);
729 read_only_->setAlignment(Qt::AlignCenter);
731 statusBar()->addPermanentWidget(read_only_);
733 version_control_ = new QLabel(statusBar());
734 version_control_->setAlignment(Qt::AlignCenter);
735 version_control_->setFrameStyle(QFrame::StyledPanel);
736 version_control_->hide();
737 statusBar()->addPermanentWidget(version_control_);
739 statusBar()->setSizeGripEnabled(true);
742 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
743 SLOT(autoSaveThreadFinished()));
745 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
746 SLOT(processingThreadStarted()));
747 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
748 SLOT(processingThreadFinished()));
750 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
751 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
753 // set custom application bars context menu, e.g. tool bar and menu bar
754 setContextMenuPolicy(Qt::CustomContextMenu);
755 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
756 SLOT(toolBarPopup(const QPoint &)));
758 // Forbid too small unresizable window because it can happen
759 // with some window manager under X11.
760 setMinimumSize(300, 200);
762 if (lyxrc.allow_geometry_session) {
763 // Now take care of session management.
768 // no session handling, default to a sane size.
769 setGeometry(50, 50, 690, 510);
772 // clear session data if any.
773 settings.remove("views");
783 void GuiView::disableShellEscape()
785 BufferView * bv = documentBufferView();
788 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
789 bv->buffer().params().shell_escape = false;
790 bv->processUpdateFlags(Update::Force);
794 void GuiView::checkCancelBackground()
796 docstring const ttl = _("Cancel Export?");
797 docstring const msg = _("Do you want to cancel the background export process?");
799 Alert::prompt(ttl, msg, 1, 1,
800 _("&Cancel export"), _("Co&ntinue"));
802 Systemcall::killscript();
806 void GuiView::enableZoomOptions()
808 act_zoom_default_->setEnabled(zoom_slider_->value() != lyxrc.defaultZoom);
810 act_zoom_in_->setEnabled(getStatus(FuncRequest(LFUN_BUFFER_ZOOM_IN), status));
811 act_zoom_out_->setEnabled(getStatus(FuncRequest(LFUN_BUFFER_ZOOM_OUT), status));
815 void GuiView::zoomSliderMoved(int value)
818 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
819 currentWorkArea()->scheduleRedraw(true);
820 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
824 void GuiView::zoomValueChanged(int value)
826 if (value != lyxrc.currentZoom)
827 zoomSliderMoved(value);
832 void GuiView::zoomInPressed()
835 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
836 currentWorkArea()->scheduleRedraw(true);
840 void GuiView::zoomOutPressed()
843 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
844 currentWorkArea()->scheduleRedraw(true);
848 void GuiView::toogleZoomSlider()
851 dispatch(FuncRequest(LFUN_UI_TOGGLE, "zoomslider"), dr);
855 void GuiView::resetDefaultZoom()
857 zoomValueChanged(lyxrc.defaultZoom);
862 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
864 QVector<GuiWorkArea*> areas;
865 for (int i = 0; i < tabWorkAreaCount(); i++) {
866 TabWorkArea* ta = tabWorkArea(i);
867 for (int u = 0; u < ta->count(); u++) {
868 areas << ta->workArea(u);
874 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
875 string const & format)
877 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
880 case Buffer::ExportSuccess:
881 msg = bformat(_("Successful export to format: %1$s"), fmt);
883 case Buffer::ExportCancel:
884 msg = _("Document export cancelled.");
886 case Buffer::ExportError:
887 case Buffer::ExportNoPathToFormat:
888 case Buffer::ExportTexPathHasSpaces:
889 case Buffer::ExportConverterError:
890 msg = bformat(_("Error while exporting format: %1$s"), fmt);
892 case Buffer::PreviewSuccess:
893 msg = bformat(_("Successful preview of format: %1$s"), fmt);
895 case Buffer::PreviewError:
896 msg = bformat(_("Error while previewing format: %1$s"), fmt);
898 case Buffer::ExportKilled:
899 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
906 void GuiView::processingThreadStarted()
911 void GuiView::processingThreadFinished()
913 QFutureWatcher<Buffer::ExportStatus> const * watcher =
914 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
916 Buffer::ExportStatus const status = watcher->result();
917 handleExportStatus(this, status, d.processing_format);
920 BufferView const * const bv = currentBufferView();
921 if (bv && !bv->buffer().errorList("Export").empty()) {
926 bool const error = (status != Buffer::ExportSuccess &&
927 status != Buffer::PreviewSuccess &&
928 status != Buffer::ExportCancel);
930 ErrorList & el = bv->buffer().errorList(d.last_export_format);
931 // at this point, we do not know if buffer-view or
932 // master-buffer-view was called. If there was an export error,
933 // and the current buffer's error log is empty, we guess that
934 // it must be master-buffer-view that was called so we set
936 errors(d.last_export_format, el.empty());
941 void GuiView::autoSaveThreadFinished()
943 QFutureWatcher<docstring> const * watcher =
944 static_cast<QFutureWatcher<docstring> const *>(sender());
945 message(watcher->result());
950 void GuiView::saveLayout() const
953 settings.setValue("zoom_ratio", zoom_ratio_);
954 settings.setValue("devel_mode", devel_mode_);
955 settings.beginGroup("views");
956 settings.beginGroup(QString::number(id_));
957 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
958 settings.setValue("pos", pos());
959 settings.setValue("size", size());
961 settings.setValue("geometry", saveGeometry());
962 settings.setValue("layout", saveState(0));
963 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
964 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
968 void GuiView::saveUISettings() const
972 // Save the toolbar private states
973 for (auto const & tb_p : d.toolbars_)
974 tb_p.second->saveSession(settings);
975 // Now take care of all other dialogs
976 for (auto const & dlg_p : d.dialogs_)
977 dlg_p.second->saveSession(settings);
981 void GuiView::setCurrentZoom(const int v)
983 lyxrc.currentZoom = v;
984 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
985 Q_EMIT currentZoomChanged(v);
989 bool GuiView::restoreLayout()
992 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
993 // Actual zoom value: default zoom + fractional offset
994 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
995 if (zoom < static_cast<int>(zoom_min_))
997 setCurrentZoom(zoom);
998 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
999 settings.beginGroup("views");
1000 settings.beginGroup(QString::number(id_));
1001 QString const icon_key = "icon_size";
1002 if (!settings.contains(icon_key))
1005 //code below is skipped when when ~/.config/LyX is (re)created
1006 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1008 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1009 zoom_slider_->setVisible(show_zoom_slider);
1010 act_zoom_show_->setChecked(show_zoom_slider);
1011 zoom_in_->setVisible(show_zoom_slider);
1012 zoom_out_->setVisible(show_zoom_slider);
1014 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1015 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1016 QSize size = settings.value("size", QSize(690, 510)).toSize();
1020 // Work-around for bug #6034: the window ends up in an undetermined
1021 // state when trying to restore a maximized window when it is
1022 // already maximized.
1023 if (!(windowState() & Qt::WindowMaximized))
1024 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1025 setGeometry(50, 50, 690, 510);
1028 // Make sure layout is correctly oriented.
1029 setLayoutDirection(qApp->layoutDirection());
1031 // Allow the toc and view-source dock widget to be restored if needed.
1033 if ((dialog = findOrBuild("toc", true)))
1034 // see bug 5082. At least setup title and enabled state.
1035 // Visibility will be adjusted by restoreState below.
1036 dialog->prepareView();
1037 if ((dialog = findOrBuild("view-source", true)))
1038 dialog->prepareView();
1039 if ((dialog = findOrBuild("progress", true)))
1040 dialog->prepareView();
1042 if (!restoreState(settings.value("layout").toByteArray(), 0))
1045 // init the toolbars that have not been restored
1046 for (auto const & tb_p : guiApp->toolbars()) {
1047 GuiToolbar * tb = toolbar(tb_p.name);
1048 if (tb && !tb->isRestored())
1049 initToolbar(tb_p.name);
1052 // update lock (all) toolbars positions
1053 updateLockToolbars();
1060 GuiToolbar * GuiView::toolbar(string const & name)
1062 ToolbarMap::iterator it = d.toolbars_.find(name);
1063 if (it != d.toolbars_.end())
1066 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1071 void GuiView::updateLockToolbars()
1073 toolbarsMovable_ = false;
1074 for (ToolbarInfo const & info : guiApp->toolbars()) {
1075 GuiToolbar * tb = toolbar(info.name);
1076 if (tb && tb->isMovable())
1077 toolbarsMovable_ = true;
1082 void GuiView::constructToolbars()
1084 for (auto const & tb_p : d.toolbars_)
1086 d.toolbars_.clear();
1088 // I don't like doing this here, but the standard toolbar
1089 // destroys this object when it's destroyed itself (vfr)
1090 d.layout_ = new LayoutBox(*this);
1091 d.stack_widget_->addWidget(d.layout_);
1092 d.layout_->move(0,0);
1094 // extracts the toolbars from the backend
1095 for (ToolbarInfo const & inf : guiApp->toolbars())
1096 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1100 void GuiView::initToolbars()
1102 // extracts the toolbars from the backend
1103 for (ToolbarInfo const & inf : guiApp->toolbars())
1104 initToolbar(inf.name);
1108 void GuiView::initToolbar(string const & name)
1110 GuiToolbar * tb = toolbar(name);
1113 int const visibility = guiApp->toolbars().defaultVisibility(name);
1114 bool newline = !(visibility & Toolbars::SAMEROW);
1115 tb->setVisible(false);
1116 tb->setVisibility(visibility);
1118 if (visibility & Toolbars::TOP) {
1120 addToolBarBreak(Qt::TopToolBarArea);
1121 addToolBar(Qt::TopToolBarArea, tb);
1124 if (visibility & Toolbars::BOTTOM) {
1126 addToolBarBreak(Qt::BottomToolBarArea);
1127 addToolBar(Qt::BottomToolBarArea, tb);
1130 if (visibility & Toolbars::LEFT) {
1132 addToolBarBreak(Qt::LeftToolBarArea);
1133 addToolBar(Qt::LeftToolBarArea, tb);
1136 if (visibility & Toolbars::RIGHT) {
1138 addToolBarBreak(Qt::RightToolBarArea);
1139 addToolBar(Qt::RightToolBarArea, tb);
1142 if (visibility & Toolbars::ON)
1143 tb->setVisible(true);
1145 tb->setMovable(true);
1149 TocModels & GuiView::tocModels()
1151 return d.toc_models_;
1155 void GuiView::setFocus()
1157 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1158 QMainWindow::setFocus();
1162 bool GuiView::hasFocus() const
1164 if (currentWorkArea())
1165 return currentWorkArea()->hasFocus();
1166 if (currentMainWorkArea())
1167 return currentMainWorkArea()->hasFocus();
1168 return d.bg_widget_->hasFocus();
1172 void GuiView::focusInEvent(QFocusEvent * e)
1174 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1175 QMainWindow::focusInEvent(e);
1176 // Make sure guiApp points to the correct view.
1177 guiApp->setCurrentView(this);
1178 if (currentWorkArea())
1179 currentWorkArea()->setFocus();
1180 else if (currentMainWorkArea())
1181 currentMainWorkArea()->setFocus();
1183 d.bg_widget_->setFocus();
1187 void GuiView::showEvent(QShowEvent * e)
1189 LYXERR(Debug::GUI, "Passed Geometry "
1190 << size().height() << "x" << size().width()
1191 << "+" << pos().x() << "+" << pos().y());
1193 if (d.splitter_->count() == 0)
1194 // No work area, switch to the background widget.
1198 QMainWindow::showEvent(e);
1202 bool GuiView::closeScheduled()
1209 bool GuiView::prepareAllBuffersForLogout()
1211 Buffer * first = theBufferList().first();
1215 // First, iterate over all buffers and ask the users if unsaved
1216 // changes should be saved.
1217 // We cannot use a for loop as the buffer list cycles.
1220 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1222 b = theBufferList().next(b);
1223 } while (b != first);
1225 // Next, save session state
1226 // When a view/window was closed before without quitting LyX, there
1227 // are already entries in the lastOpened list.
1228 theSession().lastOpened().clear();
1235 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1236 ** is responsibility of the container (e.g., dialog)
1238 void GuiView::closeEvent(QCloseEvent * close_event)
1240 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1242 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1243 Alert::warning(_("Exit LyX"),
1244 _("LyX could not be closed because documents are being processed by LyX."));
1245 close_event->setAccepted(false);
1249 // If the user pressed the x (so we didn't call closeView
1250 // programmatically), we want to clear all existing entries.
1252 theSession().lastOpened().clear();
1257 // it can happen that this event arrives without selecting the view,
1258 // e.g. when clicking the close button on a background window.
1260 if (!closeWorkAreaAll()) {
1262 close_event->ignore();
1266 // Make sure that nothing will use this to be closed View.
1267 guiApp->unregisterView(this);
1269 if (isFullScreen()) {
1270 // Switch off fullscreen before closing.
1275 // Make sure the timer time out will not trigger a statusbar update.
1276 d.statusbar_timer_.stop();
1278 // Saving fullscreen requires additional tweaks in the toolbar code.
1279 // It wouldn't also work under linux natively.
1280 if (lyxrc.allow_geometry_session) {
1285 close_event->accept();
1289 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1291 if (event->mimeData()->hasUrls())
1293 /// \todo Ask lyx-devel is this is enough:
1294 /// if (event->mimeData()->hasFormat("text/plain"))
1295 /// event->acceptProposedAction();
1299 void GuiView::dropEvent(QDropEvent * event)
1301 QList<QUrl> files = event->mimeData()->urls();
1302 if (files.isEmpty())
1305 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1306 for (int i = 0; i != files.size(); ++i) {
1307 string const file = os::internal_path(fromqstr(
1308 files.at(i).toLocalFile()));
1312 string const ext = support::getExtension(file);
1313 vector<const Format *> found_formats;
1315 // Find all formats that have the correct extension.
1316 for (const Format * fmt : theConverters().importableFormats())
1317 if (fmt->hasExtension(ext))
1318 found_formats.push_back(fmt);
1321 if (!found_formats.empty()) {
1322 if (found_formats.size() > 1) {
1323 //FIXME: show a dialog to choose the correct importable format
1324 LYXERR(Debug::FILES,
1325 "Multiple importable formats found, selecting first");
1327 string const arg = found_formats[0]->name() + " " + file;
1328 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1331 //FIXME: do we have to explicitly check whether it's a lyx file?
1332 LYXERR(Debug::FILES,
1333 "No formats found, trying to open it as a lyx file");
1334 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1336 // add the functions to the queue
1337 guiApp->addToFuncRequestQueue(cmd);
1340 // now process the collected functions. We perform the events
1341 // asynchronously. This prevents potential problems in case the
1342 // BufferView is closed within an event.
1343 guiApp->processFuncRequestQueueAsync();
1347 void GuiView::message(docstring const & str)
1349 if (ForkedProcess::iAmAChild())
1352 // call is moved to GUI-thread by GuiProgress
1353 d.progress_->appendMessage(toqstr(str));
1357 void GuiView::clearMessageText()
1359 message(docstring());
1363 void GuiView::updateStatusBarMessage(QString const & str)
1365 statusBar()->showMessage(str);
1366 d.statusbar_timer_.stop();
1367 d.statusbar_timer_.start(3000);
1371 void GuiView::clearMessage()
1373 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1374 // the hasFocus function mostly returns false, even if the focus is on
1375 // a workarea in this view.
1379 d.statusbar_timer_.stop();
1383 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1385 if (wa != d.current_work_area_
1386 || wa->bufferView().buffer().isInternal())
1388 Buffer const & buf = wa->bufferView().buffer();
1389 // Set the windows title
1390 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1391 if (buf.notifiesExternalModification()) {
1392 title = bformat(_("%1$s (modified externally)"), title);
1393 // If the external modification status has changed, then maybe the status of
1394 // buffer-save has changed too.
1398 title += from_ascii(" - LyX");
1400 setWindowTitle(toqstr(title));
1401 // Sets the path for the window: this is used by OSX to
1402 // allow a context click on the title bar showing a menu
1403 // with the path up to the file
1404 setWindowFilePath(toqstr(buf.absFileName()));
1405 // Tell Qt whether the current document is changed
1406 setWindowModified(!buf.isClean());
1408 if (buf.params().shell_escape)
1409 shell_escape_->show();
1411 shell_escape_->hide();
1413 if (buf.hasReadonlyFlag())
1418 if (buf.lyxvc().inUse()) {
1419 version_control_->show();
1420 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1422 version_control_->hide();
1426 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1428 if (d.current_work_area_)
1429 // disconnect the current work area from all slots
1430 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1432 disconnectBufferView();
1433 connectBufferView(wa->bufferView());
1434 connectBuffer(wa->bufferView().buffer());
1435 d.current_work_area_ = wa;
1436 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1437 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1438 QObject::connect(wa, SIGNAL(busy(bool)),
1439 this, SLOT(setBusy(bool)));
1440 // connection of a signal to a signal
1441 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1442 this, SIGNAL(bufferViewChanged()));
1443 Q_EMIT updateWindowTitle(wa);
1444 Q_EMIT bufferViewChanged();
1448 void GuiView::onBufferViewChanged()
1451 // Buffer-dependent dialogs must be updated. This is done here because
1452 // some dialogs require buffer()->text.
1454 zoom_slider_->setEnabled(currentBufferView());
1455 zoom_value_->setEnabled(currentBufferView());
1456 zoom_in_->setEnabled(currentBufferView());
1457 zoom_out_->setEnabled(currentBufferView());
1461 void GuiView::on_lastWorkAreaRemoved()
1464 // We already are in a close event. Nothing more to do.
1467 if (d.splitter_->count() > 1)
1468 // We have a splitter so don't close anything.
1471 // Reset and updates the dialogs.
1472 Q_EMIT bufferViewChanged();
1477 if (lyxrc.open_buffers_in_tabs)
1478 // Nothing more to do, the window should stay open.
1481 if (guiApp->viewIds().size() > 1) {
1487 // On Mac we also close the last window because the application stay
1488 // resident in memory. On other platforms we don't close the last
1489 // window because this would quit the application.
1495 void GuiView::updateStatusBar()
1497 // let the user see the explicit message
1498 if (d.statusbar_timer_.isActive())
1505 void GuiView::showMessage()
1509 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1510 if (msg.isEmpty()) {
1511 BufferView const * bv = currentBufferView();
1513 msg = toqstr(bv->cursor().currentState(devel_mode_));
1515 msg = qt_("Welcome to LyX!");
1517 statusBar()->showMessage(msg);
1521 bool GuiView::event(QEvent * e)
1525 // Useful debug code:
1526 //case QEvent::ActivationChange:
1527 //case QEvent::WindowDeactivate:
1528 //case QEvent::Paint:
1529 //case QEvent::Enter:
1530 //case QEvent::Leave:
1531 //case QEvent::HoverEnter:
1532 //case QEvent::HoverLeave:
1533 //case QEvent::HoverMove:
1534 //case QEvent::StatusTip:
1535 //case QEvent::DragEnter:
1536 //case QEvent::DragLeave:
1537 //case QEvent::Drop:
1540 case QEvent::WindowStateChange: {
1541 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1542 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1543 bool result = QMainWindow::event(e);
1544 bool nfstate = (windowState() & Qt::WindowFullScreen);
1545 if (!ofstate && nfstate) {
1546 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1547 // switch to full-screen state
1548 if (lyxrc.full_screen_statusbar)
1549 statusBar()->hide();
1550 if (lyxrc.full_screen_menubar)
1552 if (lyxrc.full_screen_toolbars) {
1553 for (auto const & tb_p : d.toolbars_)
1554 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1555 tb_p.second->hide();
1557 for (int i = 0; i != d.splitter_->count(); ++i)
1558 d.tabWorkArea(i)->setFullScreen(true);
1559 #if QT_VERSION > 0x050903
1560 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1561 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1563 setContentsMargins(-2, -2, -2, -2);
1565 hideDialogs("prefs", nullptr);
1566 } else if (ofstate && !nfstate) {
1567 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1568 // switch back from full-screen state
1569 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1570 statusBar()->show();
1571 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1573 if (lyxrc.full_screen_toolbars) {
1574 for (auto const & tb_p : d.toolbars_)
1575 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1576 tb_p.second->show();
1579 for (int i = 0; i != d.splitter_->count(); ++i)
1580 d.tabWorkArea(i)->setFullScreen(false);
1581 #if QT_VERSION > 0x050903
1582 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1584 setContentsMargins(0, 0, 0, 0);
1588 case QEvent::WindowActivate: {
1589 GuiView * old_view = guiApp->currentView();
1590 if (this == old_view) {
1592 return QMainWindow::event(e);
1594 if (old_view && old_view->currentBufferView()) {
1595 // save current selection to the selection buffer to allow
1596 // middle-button paste in this window.
1597 cap::saveSelection(old_view->currentBufferView()->cursor());
1599 guiApp->setCurrentView(this);
1600 if (d.current_work_area_)
1601 on_currentWorkAreaChanged(d.current_work_area_);
1605 return QMainWindow::event(e);
1608 case QEvent::ShortcutOverride: {
1610 if (isFullScreen() && menuBar()->isHidden()) {
1611 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1612 // FIXME: we should also try to detect special LyX shortcut such as
1613 // Alt-P and Alt-M. Right now there is a hack in
1614 // GuiWorkArea::processKeySym() that hides again the menubar for
1616 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1618 return QMainWindow::event(e);
1621 return QMainWindow::event(e);
1624 case QEvent::ApplicationPaletteChange: {
1625 // runtime switch from/to dark mode
1627 return QMainWindow::event(e);
1631 return QMainWindow::event(e);
1635 void GuiView::resetWindowTitle()
1637 setWindowTitle(qt_("LyX"));
1640 bool GuiView::focusNextPrevChild(bool /*next*/)
1647 bool GuiView::busy() const
1653 void GuiView::setBusy(bool busy)
1655 bool const busy_before = busy_ > 0;
1656 busy ? ++busy_ : --busy_;
1657 if ((busy_ > 0) == busy_before)
1658 // busy state didn't change
1662 QApplication::setOverrideCursor(Qt::WaitCursor);
1665 QApplication::restoreOverrideCursor();
1670 void GuiView::resetCommandExecute()
1672 command_execute_ = false;
1677 double GuiView::pixelRatio() const
1679 #if QT_VERSION >= 0x050000
1680 return qt_scale_factor * devicePixelRatio();
1687 GuiWorkArea * GuiView::workArea(int index)
1689 if (TabWorkArea * twa = d.currentTabWorkArea())
1690 if (index < twa->count())
1691 return twa->workArea(index);
1696 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1698 if (currentWorkArea()
1699 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1700 return currentWorkArea();
1701 if (TabWorkArea * twa = d.currentTabWorkArea())
1702 return twa->workArea(buffer);
1707 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1709 // Automatically create a TabWorkArea if there are none yet.
1710 TabWorkArea * tab_widget = d.splitter_->count()
1711 ? d.currentTabWorkArea() : addTabWorkArea();
1712 return tab_widget->addWorkArea(buffer, *this);
1716 TabWorkArea * GuiView::addTabWorkArea()
1718 TabWorkArea * twa = new TabWorkArea;
1719 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1720 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1721 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1722 this, SLOT(on_lastWorkAreaRemoved()));
1724 d.splitter_->addWidget(twa);
1725 d.stack_widget_->setCurrentWidget(d.splitter_);
1730 GuiWorkArea const * GuiView::currentWorkArea() const
1732 return d.current_work_area_;
1736 GuiWorkArea * GuiView::currentWorkArea()
1738 return d.current_work_area_;
1742 GuiWorkArea const * GuiView::currentMainWorkArea() const
1744 if (!d.currentTabWorkArea())
1746 return d.currentTabWorkArea()->currentWorkArea();
1750 GuiWorkArea * GuiView::currentMainWorkArea()
1752 if (!d.currentTabWorkArea())
1754 return d.currentTabWorkArea()->currentWorkArea();
1758 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1760 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1762 d.current_work_area_ = nullptr;
1764 Q_EMIT bufferViewChanged();
1768 // FIXME: I've no clue why this is here and why it accesses
1769 // theGuiApp()->currentView, which might be 0 (bug 6464).
1770 // See also 27525 (vfr).
1771 if (theGuiApp()->currentView() == this
1772 && theGuiApp()->currentView()->currentWorkArea() == wa)
1775 if (currentBufferView())
1776 cap::saveSelection(currentBufferView()->cursor());
1778 theGuiApp()->setCurrentView(this);
1779 d.current_work_area_ = wa;
1781 // We need to reset this now, because it will need to be
1782 // right if the tabWorkArea gets reset in the for loop. We
1783 // will change it back if we aren't in that case.
1784 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1785 d.current_main_work_area_ = wa;
1787 for (int i = 0; i != d.splitter_->count(); ++i) {
1788 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1789 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1790 << ", Current main wa: " << currentMainWorkArea());
1795 d.current_main_work_area_ = old_cmwa;
1797 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1798 on_currentWorkAreaChanged(wa);
1799 BufferView & bv = wa->bufferView();
1800 bv.cursor().fixIfBroken();
1802 wa->setUpdatesEnabled(true);
1803 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1807 void GuiView::removeWorkArea(GuiWorkArea * wa)
1809 LASSERT(wa, return);
1810 if (wa == d.current_work_area_) {
1812 disconnectBufferView();
1813 d.current_work_area_ = nullptr;
1814 d.current_main_work_area_ = nullptr;
1817 bool found_twa = false;
1818 for (int i = 0; i != d.splitter_->count(); ++i) {
1819 TabWorkArea * twa = d.tabWorkArea(i);
1820 if (twa->removeWorkArea(wa)) {
1821 // Found in this tab group, and deleted the GuiWorkArea.
1823 if (twa->count() != 0) {
1824 if (d.current_work_area_ == nullptr)
1825 // This means that we are closing the current GuiWorkArea, so
1826 // switch to the next GuiWorkArea in the found TabWorkArea.
1827 setCurrentWorkArea(twa->currentWorkArea());
1829 // No more WorkAreas in this tab group, so delete it.
1836 // It is not a tabbed work area (i.e., the search work area), so it
1837 // should be deleted by other means.
1838 LASSERT(found_twa, return);
1840 if (d.current_work_area_ == nullptr) {
1841 if (d.splitter_->count() != 0) {
1842 TabWorkArea * twa = d.currentTabWorkArea();
1843 setCurrentWorkArea(twa->currentWorkArea());
1845 // No more work areas, switch to the background widget.
1846 setCurrentWorkArea(nullptr);
1852 LayoutBox * GuiView::getLayoutDialog() const
1858 void GuiView::updateLayoutList()
1861 d.layout_->updateContents(false);
1865 void GuiView::updateToolbars()
1867 if (d.current_work_area_) {
1869 if (d.current_work_area_->bufferView().cursor().inMathed()
1870 && !d.current_work_area_->bufferView().cursor().inRegexped())
1871 context |= Toolbars::MATH;
1872 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1873 context |= Toolbars::TABLE;
1874 if (currentBufferView()->buffer().areChangesPresent()
1875 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1876 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1877 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1878 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1879 context |= Toolbars::REVIEW;
1880 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1881 context |= Toolbars::MATHMACROTEMPLATE;
1882 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1883 context |= Toolbars::IPA;
1884 if (command_execute_)
1885 context |= Toolbars::MINIBUFFER;
1886 if (minibuffer_focus_) {
1887 context |= Toolbars::MINIBUFFER_FOCUS;
1888 minibuffer_focus_ = false;
1891 for (auto const & tb_p : d.toolbars_)
1892 tb_p.second->update(context);
1894 for (auto const & tb_p : d.toolbars_)
1895 tb_p.second->update();
1899 void GuiView::refillToolbars()
1901 for (auto const & tb_p : d.toolbars_)
1902 tb_p.second->refill();
1906 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1908 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1909 LASSERT(newBuffer, return);
1911 GuiWorkArea * wa = workArea(*newBuffer);
1912 if (wa == nullptr) {
1914 newBuffer->masterBuffer()->updateBuffer();
1916 wa = addWorkArea(*newBuffer);
1917 // scroll to the position when the BufferView was last closed
1918 if (lyxrc.use_lastfilepos) {
1919 LastFilePosSection::FilePos filepos =
1920 theSession().lastFilePos().load(newBuffer->fileName());
1921 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1924 //Disconnect the old buffer...there's no new one.
1927 connectBuffer(*newBuffer);
1928 connectBufferView(wa->bufferView());
1930 setCurrentWorkArea(wa);
1934 void GuiView::connectBuffer(Buffer & buf)
1936 buf.setGuiDelegate(this);
1940 void GuiView::disconnectBuffer()
1942 if (d.current_work_area_)
1943 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1947 void GuiView::connectBufferView(BufferView & bv)
1949 bv.setGuiDelegate(this);
1953 void GuiView::disconnectBufferView()
1955 if (d.current_work_area_)
1956 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1960 void GuiView::errors(string const & error_type, bool from_master)
1962 BufferView const * const bv = currentBufferView();
1966 ErrorList const & el = from_master ?
1967 bv->buffer().masterBuffer()->errorList(error_type) :
1968 bv->buffer().errorList(error_type);
1973 string err = error_type;
1975 err = "from_master|" + error_type;
1976 showDialog("errorlist", err);
1980 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1982 d.toc_models_.updateItem(toqstr(type), dit);
1986 void GuiView::structureChanged()
1988 // This is called from the Buffer, which has no way to ensure that cursors
1989 // in BufferView remain valid.
1990 if (documentBufferView())
1991 documentBufferView()->cursor().sanitize();
1992 // FIXME: This is slightly expensive, though less than the tocBackend update
1993 // (#9880). This also resets the view in the Toc Widget (#6675).
1994 d.toc_models_.reset(documentBufferView());
1995 // Navigator needs more than a simple update in this case. It needs to be
1997 updateDialog("toc", "");
2001 void GuiView::updateDialog(string const & name, string const & sdata)
2003 if (!isDialogVisible(name))
2006 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2007 if (it == d.dialogs_.end())
2010 Dialog * const dialog = it->second.get();
2011 if (dialog->isVisibleView())
2012 dialog->initialiseParams(sdata);
2016 BufferView * GuiView::documentBufferView()
2018 return currentMainWorkArea()
2019 ? ¤tMainWorkArea()->bufferView()
2024 BufferView const * GuiView::documentBufferView() const
2026 return currentMainWorkArea()
2027 ? ¤tMainWorkArea()->bufferView()
2032 BufferView * GuiView::currentBufferView()
2034 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2038 BufferView const * GuiView::currentBufferView() const
2040 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2044 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2045 Buffer const * orig, Buffer * clone)
2047 bool const success = clone->autoSave();
2049 busyBuffers.remove(orig);
2051 ? _("Automatic save done.")
2052 : _("Automatic save failed!");
2056 void GuiView::autoSave()
2058 LYXERR(Debug::INFO, "Running autoSave()");
2060 Buffer * buffer = documentBufferView()
2061 ? &documentBufferView()->buffer() : nullptr;
2063 resetAutosaveTimers();
2067 GuiViewPrivate::busyBuffers.insert(buffer);
2068 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2069 buffer, buffer->cloneBufferOnly());
2070 d.autosave_watcher_.setFuture(f);
2071 resetAutosaveTimers();
2075 void GuiView::resetAutosaveTimers()
2078 d.autosave_timeout_.restart();
2082 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2085 Buffer * buf = currentBufferView()
2086 ? ¤tBufferView()->buffer() : nullptr;
2087 Buffer * doc_buffer = documentBufferView()
2088 ? &(documentBufferView()->buffer()) : nullptr;
2091 /* In LyX/Mac, when a dialog is open, the menus of the
2092 application can still be accessed without giving focus to
2093 the main window. In this case, we want to disable the menu
2094 entries that are buffer-related.
2095 This code must not be used on Linux and Windows, since it
2096 would disable buffer-related entries when hovering over the
2097 menu (see bug #9574).
2099 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2105 // Check whether we need a buffer
2106 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2107 // no, exit directly
2108 flag.message(from_utf8(N_("Command not allowed with"
2109 "out any document open")));
2110 flag.setEnabled(false);
2114 if (cmd.origin() == FuncRequest::TOC) {
2115 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2116 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2117 flag.setEnabled(false);
2121 switch(cmd.action()) {
2122 case LFUN_BUFFER_IMPORT:
2125 case LFUN_MASTER_BUFFER_EXPORT:
2127 && (doc_buffer->parent() != nullptr
2128 || doc_buffer->hasChildren())
2129 && !d.processing_thread_watcher_.isRunning()
2130 // this launches a dialog, which would be in the wrong Buffer
2131 && !(::lyx::operator==(cmd.argument(), "custom"));
2134 case LFUN_MASTER_BUFFER_UPDATE:
2135 case LFUN_MASTER_BUFFER_VIEW:
2137 && (doc_buffer->parent() != nullptr
2138 || doc_buffer->hasChildren())
2139 && !d.processing_thread_watcher_.isRunning();
2142 case LFUN_BUFFER_UPDATE:
2143 case LFUN_BUFFER_VIEW: {
2144 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2148 string format = to_utf8(cmd.argument());
2149 if (cmd.argument().empty())
2150 format = doc_buffer->params().getDefaultOutputFormat();
2151 enable = doc_buffer->params().isExportable(format, true);
2155 case LFUN_BUFFER_RELOAD:
2156 enable = doc_buffer && !doc_buffer->isUnnamed()
2157 && doc_buffer->fileName().exists()
2158 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2161 case LFUN_BUFFER_RESET_EXPORT:
2162 enable = doc_buffer != nullptr;
2165 case LFUN_BUFFER_CHILD_OPEN:
2166 enable = doc_buffer != nullptr;
2169 case LFUN_MASTER_BUFFER_FORALL: {
2170 if (doc_buffer == nullptr) {
2171 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2175 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2176 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2177 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2182 for (Buffer * buf : doc_buffer->allRelatives()) {
2183 GuiWorkArea * wa = workArea(*buf);
2186 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2187 enable = flag.enabled();
2194 case LFUN_BUFFER_WRITE:
2195 enable = doc_buffer && (doc_buffer->isUnnamed()
2196 || (!doc_buffer->isClean()
2197 || cmd.argument() == "force"));
2200 //FIXME: This LFUN should be moved to GuiApplication.
2201 case LFUN_BUFFER_WRITE_ALL: {
2202 // We enable the command only if there are some modified buffers
2203 Buffer * first = theBufferList().first();
2208 // We cannot use a for loop as the buffer list is a cycle.
2210 if (!b->isClean()) {
2214 b = theBufferList().next(b);
2215 } while (b != first);
2219 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2220 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2223 case LFUN_BUFFER_EXPORT: {
2224 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2228 return doc_buffer->getStatus(cmd, flag);
2231 case LFUN_BUFFER_EXPORT_AS:
2232 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2237 case LFUN_BUFFER_WRITE_AS:
2238 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2239 enable = doc_buffer != nullptr;
2242 case LFUN_EXPORT_CANCEL:
2243 enable = d.processing_thread_watcher_.isRunning();
2246 case LFUN_BUFFER_CLOSE:
2247 case LFUN_VIEW_CLOSE:
2248 enable = doc_buffer != nullptr;
2251 case LFUN_BUFFER_CLOSE_ALL:
2252 enable = theBufferList().last() != theBufferList().first();
2255 case LFUN_BUFFER_CHKTEX: {
2256 // hide if we have no checktex command
2257 if (lyxrc.chktex_command.empty()) {
2258 flag.setUnknown(true);
2262 if (!doc_buffer || !doc_buffer->params().isLatex()
2263 || d.processing_thread_watcher_.isRunning()) {
2264 // grey out, don't hide
2272 case LFUN_VIEW_SPLIT:
2273 if (cmd.getArg(0) == "vertical")
2274 enable = doc_buffer && (d.splitter_->count() == 1 ||
2275 d.splitter_->orientation() == Qt::Vertical);
2277 enable = doc_buffer && (d.splitter_->count() == 1 ||
2278 d.splitter_->orientation() == Qt::Horizontal);
2281 case LFUN_TAB_GROUP_CLOSE:
2282 enable = d.tabWorkAreaCount() > 1;
2285 case LFUN_DEVEL_MODE_TOGGLE:
2286 flag.setOnOff(devel_mode_);
2289 case LFUN_TOOLBAR_SET: {
2290 string const name = cmd.getArg(0);
2291 string const state = cmd.getArg(1);
2292 if (name.empty() || state.empty()) {
2294 docstring const msg =
2295 _("Function toolbar-set requires two arguments!");
2299 if (state != "on" && state != "off" && state != "auto") {
2301 docstring const msg =
2302 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2307 if (GuiToolbar * t = toolbar(name)) {
2308 bool const autovis = t->visibility() & Toolbars::AUTO;
2310 flag.setOnOff(t->isVisible() && !autovis);
2311 else if (state == "off")
2312 flag.setOnOff(!t->isVisible() && !autovis);
2313 else if (state == "auto")
2314 flag.setOnOff(autovis);
2317 docstring const msg =
2318 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2324 case LFUN_TOOLBAR_TOGGLE: {
2325 string const name = cmd.getArg(0);
2326 if (GuiToolbar * t = toolbar(name))
2327 flag.setOnOff(t->isVisible());
2330 docstring const msg =
2331 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2337 case LFUN_TOOLBAR_MOVABLE: {
2338 string const name = cmd.getArg(0);
2339 // use negation since locked == !movable
2341 // toolbar name * locks all toolbars
2342 flag.setOnOff(!toolbarsMovable_);
2343 else if (GuiToolbar * t = toolbar(name))
2344 flag.setOnOff(!(t->isMovable()));
2347 docstring const msg =
2348 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2354 case LFUN_ICON_SIZE:
2355 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2358 case LFUN_DROP_LAYOUTS_CHOICE:
2359 enable = buf != nullptr;
2362 case LFUN_UI_TOGGLE:
2363 if (cmd.argument() == "zoomslider") {
2364 enable = doc_buffer;
2365 flag.setOnOff(zoom_slider_->isVisible());
2367 flag.setOnOff(isFullScreen());
2370 case LFUN_DIALOG_DISCONNECT_INSET:
2373 case LFUN_DIALOG_HIDE:
2374 // FIXME: should we check if the dialog is shown?
2377 case LFUN_DIALOG_TOGGLE:
2378 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2381 case LFUN_DIALOG_SHOW: {
2382 string const name = cmd.getArg(0);
2384 enable = name == "aboutlyx"
2385 || name == "file" //FIXME: should be removed.
2386 || name == "lyxfiles"
2388 || name == "texinfo"
2389 || name == "progress"
2390 || name == "compare";
2391 else if (name == "character" || name == "symbols"
2392 || name == "mathdelimiter" || name == "mathmatrix") {
2393 if (!buf || buf->isReadonly())
2396 Cursor const & cur = currentBufferView()->cursor();
2397 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2400 else if (name == "latexlog")
2401 enable = FileName(doc_buffer->logName()).isReadableFile();
2402 else if (name == "spellchecker")
2403 enable = theSpellChecker()
2404 && !doc_buffer->text().empty();
2405 else if (name == "vclog")
2406 enable = doc_buffer->lyxvc().inUse();
2410 case LFUN_DIALOG_UPDATE: {
2411 string const name = cmd.getArg(0);
2413 enable = name == "prefs";
2417 case LFUN_COMMAND_EXECUTE:
2419 case LFUN_MENU_OPEN:
2420 // Nothing to check.
2423 case LFUN_COMPLETION_INLINE:
2424 if (!d.current_work_area_
2425 || !d.current_work_area_->completer().inlinePossible(
2426 currentBufferView()->cursor()))
2430 case LFUN_COMPLETION_POPUP:
2431 if (!d.current_work_area_
2432 || !d.current_work_area_->completer().popupPossible(
2433 currentBufferView()->cursor()))
2438 if (!d.current_work_area_
2439 || !d.current_work_area_->completer().inlinePossible(
2440 currentBufferView()->cursor()))
2444 case LFUN_COMPLETION_ACCEPT:
2445 if (!d.current_work_area_
2446 || (!d.current_work_area_->completer().popupVisible()
2447 && !d.current_work_area_->completer().inlineVisible()
2448 && !d.current_work_area_->completer().completionAvailable()))
2452 case LFUN_COMPLETION_CANCEL:
2453 if (!d.current_work_area_
2454 || (!d.current_work_area_->completer().popupVisible()
2455 && !d.current_work_area_->completer().inlineVisible()))
2459 case LFUN_BUFFER_ZOOM_OUT:
2460 case LFUN_BUFFER_ZOOM_IN: {
2461 // only diff between these two is that the default for ZOOM_OUT
2463 bool const neg_zoom =
2464 convert<int>(cmd.argument()) < 0 ||
2465 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2466 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2467 docstring const msg =
2468 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2472 enable = doc_buffer;
2476 case LFUN_BUFFER_ZOOM: {
2477 bool const less_than_min_zoom =
2478 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2479 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2480 docstring const msg =
2481 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2486 enable = doc_buffer;
2490 case LFUN_BUFFER_MOVE_NEXT:
2491 case LFUN_BUFFER_MOVE_PREVIOUS:
2492 // we do not cycle when moving
2493 case LFUN_BUFFER_NEXT:
2494 case LFUN_BUFFER_PREVIOUS:
2495 // because we cycle, it doesn't matter whether on first or last
2496 enable = (d.currentTabWorkArea()->count() > 1);
2498 case LFUN_BUFFER_SWITCH:
2499 // toggle on the current buffer, but do not toggle off
2500 // the other ones (is that a good idea?)
2502 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2503 flag.setOnOff(true);
2506 case LFUN_VC_REGISTER:
2507 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2509 case LFUN_VC_RENAME:
2510 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2513 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2515 case LFUN_VC_CHECK_IN:
2516 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2518 case LFUN_VC_CHECK_OUT:
2519 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2521 case LFUN_VC_LOCKING_TOGGLE:
2522 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2523 && doc_buffer->lyxvc().lockingToggleEnabled();
2524 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2526 case LFUN_VC_REVERT:
2527 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2528 && !doc_buffer->hasReadonlyFlag();
2530 case LFUN_VC_UNDO_LAST:
2531 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2533 case LFUN_VC_REPO_UPDATE:
2534 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2536 case LFUN_VC_COMMAND: {
2537 if (cmd.argument().empty())
2539 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2543 case LFUN_VC_COMPARE:
2544 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2547 case LFUN_SERVER_GOTO_FILE_ROW:
2548 case LFUN_LYX_ACTIVATE:
2549 case LFUN_WINDOW_RAISE:
2551 case LFUN_FORWARD_SEARCH:
2552 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2555 case LFUN_FILE_INSERT_PLAINTEXT:
2556 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2557 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2560 case LFUN_SPELLING_CONTINUOUSLY:
2561 flag.setOnOff(lyxrc.spellcheck_continuously);
2564 case LFUN_CITATION_OPEN:
2573 flag.setEnabled(false);
2579 static FileName selectTemplateFile()
2581 FileDialog dlg(qt_("Select template file"));
2582 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2583 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2585 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2586 QStringList(qt_("LyX Documents (*.lyx)")));
2588 if (result.first == FileDialog::Later)
2590 if (result.second.isEmpty())
2592 return FileName(fromqstr(result.second));
2596 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2600 Buffer * newBuffer = nullptr;
2602 newBuffer = checkAndLoadLyXFile(filename);
2603 } catch (ExceptionMessage const &) {
2610 message(_("Document not loaded."));
2614 setBuffer(newBuffer);
2615 newBuffer->errors("Parse");
2618 theSession().lastFiles().add(filename);
2619 theSession().writeFile();
2626 void GuiView::openDocument(string const & fname)
2628 string initpath = lyxrc.document_path;
2630 if (documentBufferView()) {
2631 string const trypath = documentBufferView()->buffer().filePath();
2632 // If directory is writeable, use this as default.
2633 if (FileName(trypath).isDirWritable())
2639 if (fname.empty()) {
2640 FileDialog dlg(qt_("Select document to open"));
2641 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2642 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2644 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2645 FileDialog::Result result =
2646 dlg.open(toqstr(initpath), filter);
2648 if (result.first == FileDialog::Later)
2651 filename = fromqstr(result.second);
2653 // check selected filename
2654 if (filename.empty()) {
2655 message(_("Canceled."));
2661 // get absolute path of file and add ".lyx" to the filename if
2663 FileName const fullname =
2664 fileSearch(string(), filename, "lyx", support::may_not_exist);
2665 if (!fullname.empty())
2666 filename = fullname.absFileName();
2668 if (!fullname.onlyPath().isDirectory()) {
2669 Alert::warning(_("Invalid filename"),
2670 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2671 from_utf8(fullname.absFileName())));
2675 // if the file doesn't exist and isn't already open (bug 6645),
2676 // let the user create one
2677 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2678 !LyXVC::file_not_found_hook(fullname)) {
2679 // the user specifically chose this name. Believe him.
2680 Buffer * const b = newFile(filename, string(), true);
2686 docstring const disp_fn = makeDisplayPath(filename);
2687 message(bformat(_("Opening document %1$s..."), disp_fn));
2690 Buffer * buf = loadDocument(fullname);
2692 str2 = bformat(_("Document %1$s opened."), disp_fn);
2693 if (buf->lyxvc().inUse())
2694 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2695 " " + _("Version control detected.");
2697 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2702 // FIXME: clean that
2703 static bool import(GuiView * lv, FileName const & filename,
2704 string const & format, ErrorList & errorList)
2706 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2708 string loader_format;
2709 vector<string> loaders = theConverters().loaders();
2710 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2711 for (string const & loader : loaders) {
2712 if (!theConverters().isReachable(format, loader))
2715 string const tofile =
2716 support::changeExtension(filename.absFileName(),
2717 theFormats().extension(loader));
2718 if (theConverters().convert(nullptr, filename, FileName(tofile),
2719 filename, format, loader, errorList) != Converters::SUCCESS)
2721 loader_format = loader;
2724 if (loader_format.empty()) {
2725 frontend::Alert::error(_("Couldn't import file"),
2726 bformat(_("No information for importing the format %1$s."),
2727 translateIfPossible(theFormats().prettyName(format))));
2731 loader_format = format;
2733 if (loader_format == "lyx") {
2734 Buffer * buf = lv->loadDocument(lyxfile);
2738 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2742 bool as_paragraphs = loader_format == "textparagraph";
2743 string filename2 = (loader_format == format) ? filename.absFileName()
2744 : support::changeExtension(filename.absFileName(),
2745 theFormats().extension(loader_format));
2746 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2748 guiApp->setCurrentView(lv);
2749 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2756 void GuiView::importDocument(string const & argument)
2759 string filename = split(argument, format, ' ');
2761 LYXERR(Debug::INFO, format << " file: " << filename);
2763 // need user interaction
2764 if (filename.empty()) {
2765 string initpath = lyxrc.document_path;
2766 if (documentBufferView()) {
2767 string const trypath = documentBufferView()->buffer().filePath();
2768 // If directory is writeable, use this as default.
2769 if (FileName(trypath).isDirWritable())
2773 docstring const text = bformat(_("Select %1$s file to import"),
2774 translateIfPossible(theFormats().prettyName(format)));
2776 FileDialog dlg(toqstr(text));
2777 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2778 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2780 docstring filter = translateIfPossible(theFormats().prettyName(format));
2783 filter += from_utf8(theFormats().extensions(format));
2786 FileDialog::Result result =
2787 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2789 if (result.first == FileDialog::Later)
2792 filename = fromqstr(result.second);
2794 // check selected filename
2795 if (filename.empty())
2796 message(_("Canceled."));
2799 if (filename.empty())
2802 // get absolute path of file
2803 FileName const fullname(support::makeAbsPath(filename));
2805 // Can happen if the user entered a path into the dialog
2807 if (fullname.onlyFileName().empty()) {
2808 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2809 "Aborting import."),
2810 from_utf8(fullname.absFileName()));
2811 frontend::Alert::error(_("File name error"), msg);
2812 message(_("Canceled."));
2817 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2819 // Check if the document already is open
2820 Buffer * buf = theBufferList().getBuffer(lyxfile);
2823 if (!closeBuffer()) {
2824 message(_("Canceled."));
2829 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2831 // if the file exists already, and we didn't do
2832 // -i lyx thefile.lyx, warn
2833 if (lyxfile.exists() && fullname != lyxfile) {
2835 docstring text = bformat(_("The document %1$s already exists.\n\n"
2836 "Do you want to overwrite that document?"), displaypath);
2837 int const ret = Alert::prompt(_("Overwrite document?"),
2838 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2841 message(_("Canceled."));
2846 message(bformat(_("Importing %1$s..."), displaypath));
2847 ErrorList errorList;
2848 if (import(this, fullname, format, errorList))
2849 message(_("imported."));
2851 message(_("file not imported!"));
2853 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2857 void GuiView::newDocument(string const & filename, string templatefile,
2860 FileName initpath(lyxrc.document_path);
2861 if (documentBufferView()) {
2862 FileName const trypath(documentBufferView()->buffer().filePath());
2863 // If directory is writeable, use this as default.
2864 if (trypath.isDirWritable())
2868 if (from_template) {
2869 if (templatefile.empty())
2870 templatefile = selectTemplateFile().absFileName();
2871 if (templatefile.empty())
2876 if (filename.empty())
2877 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2879 b = newFile(filename, templatefile, true);
2884 // If no new document could be created, it is unsure
2885 // whether there is a valid BufferView.
2886 if (currentBufferView())
2887 // Ensure the cursor is correctly positioned on screen.
2888 currentBufferView()->showCursor();
2892 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2894 BufferView * bv = documentBufferView();
2899 FileName filename(to_utf8(fname));
2900 if (filename.empty()) {
2901 // Launch a file browser
2903 string initpath = lyxrc.document_path;
2904 string const trypath = bv->buffer().filePath();
2905 // If directory is writeable, use this as default.
2906 if (FileName(trypath).isDirWritable())
2910 FileDialog dlg(qt_("Select LyX document to insert"));
2911 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2912 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2914 FileDialog::Result result = dlg.open(toqstr(initpath),
2915 QStringList(qt_("LyX Documents (*.lyx)")));
2917 if (result.first == FileDialog::Later)
2921 filename.set(fromqstr(result.second));
2923 // check selected filename
2924 if (filename.empty()) {
2925 // emit message signal.
2926 message(_("Canceled."));
2931 bv->insertLyXFile(filename, ignorelang);
2932 bv->buffer().errors("Parse");
2937 string const GuiView::getTemplatesPath(Buffer & b)
2939 // We start off with the user's templates path
2940 string result = addPath(package().user_support().absFileName(), "templates");
2941 // Check for the document language
2942 string const langcode = b.params().language->code();
2943 string const shortcode = langcode.substr(0, 2);
2944 if (!langcode.empty() && shortcode != "en") {
2945 string subpath = addPath(result, shortcode);
2946 string subpath_long = addPath(result, langcode);
2947 // If we have a subdirectory for the language already,
2949 FileName sp = FileName(subpath);
2950 if (sp.isDirectory())
2952 else if (FileName(subpath_long).isDirectory())
2953 result = subpath_long;
2955 // Ask whether we should create such a subdirectory
2956 docstring const text =
2957 bformat(_("It is suggested to save the template in a subdirectory\n"
2958 "appropriate to the document language (%1$s).\n"
2959 "This subdirectory does not exists yet.\n"
2960 "Do you want to create it?"),
2961 _(b.params().language->display()));
2962 if (Alert::prompt(_("Create Language Directory?"),
2963 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2964 // If the user agreed, we try to create it and report if this failed.
2965 if (!sp.createDirectory(0777))
2966 Alert::error(_("Subdirectory creation failed!"),
2967 _("Could not create subdirectory.\n"
2968 "The template will be saved in the parent directory."));
2974 // Do we have a layout category?
2975 string const cat = b.params().baseClass() ?
2976 b.params().baseClass()->category()
2979 string subpath = addPath(result, cat);
2980 // If we have a subdirectory for the category already,
2982 FileName sp = FileName(subpath);
2983 if (sp.isDirectory())
2986 // Ask whether we should create such a subdirectory
2987 docstring const text =
2988 bformat(_("It is suggested to save the template in a subdirectory\n"
2989 "appropriate to the layout category (%1$s).\n"
2990 "This subdirectory does not exists yet.\n"
2991 "Do you want to create it?"),
2993 if (Alert::prompt(_("Create Category Directory?"),
2994 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2995 // If the user agreed, we try to create it and report if this failed.
2996 if (!sp.createDirectory(0777))
2997 Alert::error(_("Subdirectory creation failed!"),
2998 _("Could not create subdirectory.\n"
2999 "The template will be saved in the parent directory."));
3009 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3011 FileName fname = b.fileName();
3012 FileName const oldname = fname;
3013 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3015 if (!newname.empty()) {
3018 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3020 fname = support::makeAbsPath(to_utf8(newname),
3021 oldname.onlyPath().absFileName());
3023 // Switch to this Buffer.
3026 // No argument? Ask user through dialog.
3028 QString const title = as_template ? qt_("Choose a filename to save template as")
3029 : qt_("Choose a filename to save document as");
3030 FileDialog dlg(title);
3031 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3032 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3034 if (!isLyXFileName(fname.absFileName()))
3035 fname.changeExtension(".lyx");
3037 string const path = as_template ?
3039 : fname.onlyPath().absFileName();
3040 FileDialog::Result result =
3041 dlg.save(toqstr(path),
3042 QStringList(qt_("LyX Documents (*.lyx)")),
3043 toqstr(fname.onlyFileName()));
3045 if (result.first == FileDialog::Later)
3048 fname.set(fromqstr(result.second));
3053 if (!isLyXFileName(fname.absFileName()))
3054 fname.changeExtension(".lyx");
3057 // fname is now the new Buffer location.
3059 // if there is already a Buffer open with this name, we do not want
3060 // to have another one. (the second test makes sure we're not just
3061 // trying to overwrite ourselves, which is fine.)
3062 if (theBufferList().exists(fname) && fname != oldname
3063 && theBufferList().getBuffer(fname) != &b) {
3064 docstring const text =
3065 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3066 "Please close it before attempting to overwrite it.\n"
3067 "Do you want to choose a new filename?"),
3068 from_utf8(fname.absFileName()));
3069 int const ret = Alert::prompt(_("Chosen File Already Open"),
3070 text, 0, 1, _("&Rename"), _("&Cancel"));
3072 case 0: return renameBuffer(b, docstring(), kind);
3073 case 1: return false;
3078 bool const existsLocal = fname.exists();
3079 bool const existsInVC = LyXVC::fileInVC(fname);
3080 if (existsLocal || existsInVC) {
3081 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3082 if (kind != LV_WRITE_AS && existsInVC) {
3083 // renaming to a name that is already in VC
3085 docstring text = bformat(_("The document %1$s "
3086 "is already registered.\n\n"
3087 "Do you want to choose a new name?"),
3089 docstring const title = (kind == LV_VC_RENAME) ?
3090 _("Rename document?") : _("Copy document?");
3091 docstring const button = (kind == LV_VC_RENAME) ?
3092 _("&Rename") : _("&Copy");
3093 int const ret = Alert::prompt(title, text, 0, 1,
3094 button, _("&Cancel"));
3096 case 0: return renameBuffer(b, docstring(), kind);
3097 case 1: return false;
3102 docstring text = bformat(_("The document %1$s "
3103 "already exists.\n\n"
3104 "Do you want to overwrite that document?"),
3106 int const ret = Alert::prompt(_("Overwrite document?"),
3107 text, 0, 2, _("&Overwrite"),
3108 _("&Rename"), _("&Cancel"));
3111 case 1: return renameBuffer(b, docstring(), kind);
3112 case 2: return false;
3118 case LV_VC_RENAME: {
3119 string msg = b.lyxvc().rename(fname);
3122 message(from_utf8(msg));
3126 string msg = b.lyxvc().copy(fname);
3129 message(from_utf8(msg));
3133 case LV_WRITE_AS_TEMPLATE:
3136 // LyXVC created the file already in case of LV_VC_RENAME or
3137 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3138 // relative paths of included stuff right if we moved e.g. from
3139 // /a/b.lyx to /a/c/b.lyx.
3141 bool const saved = saveBuffer(b, fname);
3148 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3150 FileName fname = b.fileName();
3152 FileDialog dlg(qt_("Choose a filename to export the document as"));
3153 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3156 QString const anyformat = qt_("Guess from extension (*.*)");
3159 vector<Format const *> export_formats;
3160 for (Format const & f : theFormats())
3161 if (f.documentFormat())
3162 export_formats.push_back(&f);
3163 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3164 map<QString, string> fmap;
3167 for (Format const * f : export_formats) {
3168 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3169 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3171 from_ascii(f->extension())));
3172 types << loc_filter;
3173 fmap[loc_filter] = f->name();
3174 if (from_ascii(f->name()) == iformat) {
3175 filter = loc_filter;
3176 ext = f->extension();
3179 string ofname = fname.onlyFileName();
3181 ofname = support::changeExtension(ofname, ext);
3182 FileDialog::Result result =
3183 dlg.save(toqstr(fname.onlyPath().absFileName()),
3187 if (result.first != FileDialog::Chosen)
3191 fname.set(fromqstr(result.second));
3192 if (filter == anyformat)
3193 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3195 fmt_name = fmap[filter];
3196 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3197 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3199 if (fmt_name.empty() || fname.empty())
3202 // fname is now the new Buffer location.
3203 if (FileName(fname).exists()) {
3204 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3205 docstring text = bformat(_("The document %1$s already "
3206 "exists.\n\nDo you want to "
3207 "overwrite that document?"),
3209 int const ret = Alert::prompt(_("Overwrite document?"),
3210 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3213 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3214 case 2: return false;
3218 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3221 return dr.dispatched();
3225 bool GuiView::saveBuffer(Buffer & b)
3227 return saveBuffer(b, FileName());
3231 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3233 if (workArea(b) && workArea(b)->inDialogMode())
3236 if (fn.empty() && b.isUnnamed())
3237 return renameBuffer(b, docstring());
3239 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3241 theSession().lastFiles().add(b.fileName());
3242 theSession().writeFile();
3246 // Switch to this Buffer.
3249 // FIXME: we don't tell the user *WHY* the save failed !!
3250 docstring const file = makeDisplayPath(b.absFileName(), 30);
3251 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3252 "Do you want to rename the document and "
3253 "try again?"), file);
3254 int const ret = Alert::prompt(_("Rename and save?"),
3255 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3258 if (!renameBuffer(b, docstring()))
3267 return saveBuffer(b, fn);
3271 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3273 return closeWorkArea(wa, false);
3277 // We only want to close the buffer if it is not visible in other workareas
3278 // of the same view, nor in other views, and if this is not a child
3279 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3281 Buffer & buf = wa->bufferView().buffer();
3283 bool last_wa = d.countWorkAreasOf(buf) == 1
3284 && !inOtherView(buf) && !buf.parent();
3286 bool close_buffer = last_wa;
3289 if (lyxrc.close_buffer_with_last_view == "yes")
3291 else if (lyxrc.close_buffer_with_last_view == "no")
3292 close_buffer = false;
3295 if (buf.isUnnamed())
3296 file = from_utf8(buf.fileName().onlyFileName());
3298 file = buf.fileName().displayName(30);
3299 docstring const text = bformat(
3300 _("Last view on document %1$s is being closed.\n"
3301 "Would you like to close or hide the document?\n"
3303 "Hidden documents can be displayed back through\n"
3304 "the menu: View->Hidden->...\n"
3306 "To remove this question, set your preference in:\n"
3307 " Tools->Preferences->Look&Feel->UserInterface\n"
3309 int ret = Alert::prompt(_("Close or hide document?"),
3310 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3313 close_buffer = (ret == 0);
3317 return closeWorkArea(wa, close_buffer);
3321 bool GuiView::closeBuffer()
3323 GuiWorkArea * wa = currentMainWorkArea();
3324 // coverity complained about this
3325 // it seems unnecessary, but perhaps is worth the check
3326 LASSERT(wa, return false);
3328 setCurrentWorkArea(wa);
3329 Buffer & buf = wa->bufferView().buffer();
3330 return closeWorkArea(wa, !buf.parent());
3334 void GuiView::writeSession() const {
3335 GuiWorkArea const * active_wa = currentMainWorkArea();
3336 for (int i = 0; i < d.splitter_->count(); ++i) {
3337 TabWorkArea * twa = d.tabWorkArea(i);
3338 for (int j = 0; j < twa->count(); ++j) {
3339 GuiWorkArea * wa = twa->workArea(j);
3340 Buffer & buf = wa->bufferView().buffer();
3341 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3347 bool GuiView::closeBufferAll()
3350 for (auto & buf : theBufferList()) {
3351 if (!saveBufferIfNeeded(*buf, false)) {
3352 // Closing has been cancelled, so abort.
3357 // Close the workareas in all other views
3358 QList<int> const ids = guiApp->viewIds();
3359 for (int i = 0; i != ids.size(); ++i) {
3360 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3364 // Close our own workareas
3365 if (!closeWorkAreaAll())
3372 bool GuiView::closeWorkAreaAll()
3374 setCurrentWorkArea(currentMainWorkArea());
3376 // We might be in a situation that there is still a tabWorkArea, but
3377 // there are no tabs anymore. This can happen when we get here after a
3378 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3379 // many TabWorkArea's have no documents anymore.
3382 // We have to call count() each time, because it can happen that
3383 // more than one splitter will disappear in one iteration (bug 5998).
3384 while (d.splitter_->count() > empty_twa) {
3385 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3387 if (twa->count() == 0)
3390 setCurrentWorkArea(twa->currentWorkArea());
3391 if (!closeTabWorkArea(twa))
3399 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3404 Buffer & buf = wa->bufferView().buffer();
3406 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3407 Alert::warning(_("Close document"),
3408 _("Document could not be closed because it is being processed by LyX."));
3413 return closeBuffer(buf);
3415 if (!inMultiTabs(wa))
3416 if (!saveBufferIfNeeded(buf, true))
3424 bool GuiView::closeBuffer(Buffer & buf)
3426 bool success = true;
3427 for (Buffer * child_buf : buf.getChildren()) {
3428 if (theBufferList().isOthersChild(&buf, child_buf)) {
3429 child_buf->setParent(nullptr);
3433 // FIXME: should we look in other tabworkareas?
3434 // ANSWER: I don't think so. I've tested, and if the child is
3435 // open in some other window, it closes without a problem.
3436 GuiWorkArea * child_wa = workArea(*child_buf);
3439 // If we are in a close_event all children will be closed in some time,
3440 // so no need to do it here. This will ensure that the children end up
3441 // in the session file in the correct order. If we close the master
3442 // buffer, we can close or release the child buffers here too.
3444 success = closeWorkArea(child_wa, true);
3448 // In this case the child buffer is open but hidden.
3449 // Even in this case, children can be dirty (e.g.,
3450 // after a label change in the master, see #11405).
3451 // Therefore, check this
3452 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3453 // If we are in a close_event all children will be closed in some time,
3454 // so no need to do it here. This will ensure that the children end up
3455 // in the session file in the correct order. If we close the master
3456 // buffer, we can close or release the child buffers here too.
3459 // Save dirty buffers also if closing_!
3460 if (saveBufferIfNeeded(*child_buf, false)) {
3461 child_buf->removeAutosaveFile();
3462 theBufferList().release(child_buf);
3464 // Saving of dirty children has been cancelled.
3465 // Cancel the whole process.
3472 // goto bookmark to update bookmark pit.
3473 // FIXME: we should update only the bookmarks related to this buffer!
3474 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3475 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3476 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3477 guiApp->gotoBookmark(i, false, false);
3479 if (saveBufferIfNeeded(buf, false)) {
3480 buf.removeAutosaveFile();
3481 theBufferList().release(&buf);
3485 // open all children again to avoid a crash because of dangling
3486 // pointers (bug 6603)
3492 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3494 while (twa == d.currentTabWorkArea()) {
3495 twa->setCurrentIndex(twa->count() - 1);
3497 GuiWorkArea * wa = twa->currentWorkArea();
3498 Buffer & b = wa->bufferView().buffer();
3500 // We only want to close the buffer if the same buffer is not visible
3501 // in another view, and if this is not a child and if we are closing
3502 // a view (not a tabgroup).
3503 bool const close_buffer =
3504 !inOtherView(b) && !b.parent() && closing_;
3506 if (!closeWorkArea(wa, close_buffer))
3513 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3515 if (buf.isClean() || buf.paragraphs().empty())
3518 // Switch to this Buffer.
3524 if (buf.isUnnamed()) {
3525 file = from_utf8(buf.fileName().onlyFileName());
3528 FileName filename = buf.fileName();
3530 file = filename.displayName(30);
3531 exists = filename.exists();
3534 // Bring this window to top before asking questions.
3539 if (hiding && buf.isUnnamed()) {
3540 docstring const text = bformat(_("The document %1$s has not been "
3541 "saved yet.\n\nDo you want to save "
3542 "the document?"), file);
3543 ret = Alert::prompt(_("Save new document?"),
3544 text, 0, 1, _("&Save"), _("&Cancel"));
3548 docstring const text = exists ?
3549 bformat(_("The document %1$s has unsaved changes."
3550 "\n\nDo you want to save the document or "
3551 "discard the changes?"), file) :
3552 bformat(_("The document %1$s has not been saved yet."
3553 "\n\nDo you want to save the document or "
3554 "discard it entirely?"), file);
3555 docstring const title = exists ?
3556 _("Save changed document?") : _("Save document?");
3557 ret = Alert::prompt(title, text, 0, 2,
3558 _("&Save"), _("&Discard"), _("&Cancel"));
3563 if (!saveBuffer(buf))
3567 // If we crash after this we could have no autosave file
3568 // but I guess this is really improbable (Jug).
3569 // Sometimes improbable things happen:
3570 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3571 // buf.removeAutosaveFile();
3573 // revert all changes
3584 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3586 Buffer & buf = wa->bufferView().buffer();
3588 for (int i = 0; i != d.splitter_->count(); ++i) {
3589 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3590 if (wa_ && wa_ != wa)
3593 return inOtherView(buf);
3597 bool GuiView::inOtherView(Buffer & buf)
3599 QList<int> const ids = guiApp->viewIds();
3601 for (int i = 0; i != ids.size(); ++i) {
3605 if (guiApp->view(ids[i]).workArea(buf))
3612 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3614 if (!documentBufferView())
3617 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3618 Buffer * const curbuf = &documentBufferView()->buffer();
3619 int nwa = twa->count();
3620 for (int i = 0; i < nwa; ++i) {
3621 if (&workArea(i)->bufferView().buffer() == curbuf) {
3623 if (np == NEXTBUFFER)
3624 next_index = (i == nwa - 1 ? 0 : i + 1);
3626 next_index = (i == 0 ? nwa - 1 : i - 1);
3628 twa->moveTab(i, next_index);
3630 setBuffer(&workArea(next_index)->bufferView().buffer());
3638 /// make sure the document is saved
3639 static bool ensureBufferClean(Buffer * buffer)
3641 LASSERT(buffer, return false);
3642 if (buffer->isClean() && !buffer->isUnnamed())
3645 docstring const file = buffer->fileName().displayName(30);
3648 if (!buffer->isUnnamed()) {
3649 text = bformat(_("The document %1$s has unsaved "
3650 "changes.\n\nDo you want to save "
3651 "the document?"), file);
3652 title = _("Save changed document?");
3655 text = bformat(_("The document %1$s has not been "
3656 "saved yet.\n\nDo you want to save "
3657 "the document?"), file);
3658 title = _("Save new document?");
3660 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3663 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3665 return buffer->isClean() && !buffer->isUnnamed();
3669 bool GuiView::reloadBuffer(Buffer & buf)
3671 currentBufferView()->cursor().reset();
3672 Buffer::ReadStatus status = buf.reload();
3673 return status == Buffer::ReadSuccess;
3677 void GuiView::checkExternallyModifiedBuffers()
3679 for (Buffer * buf : theBufferList()) {
3680 if (buf->fileName().exists() && buf->isChecksumModified()) {
3681 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3682 " Reload now? Any local changes will be lost."),
3683 from_utf8(buf->absFileName()));
3684 int const ret = Alert::prompt(_("Reload externally changed document?"),
3685 text, 0, 1, _("&Reload"), _("&Cancel"));
3693 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3695 Buffer * buffer = documentBufferView()
3696 ? &(documentBufferView()->buffer()) : nullptr;
3698 switch (cmd.action()) {
3699 case LFUN_VC_REGISTER:
3700 if (!buffer || !ensureBufferClean(buffer))
3702 if (!buffer->lyxvc().inUse()) {
3703 if (buffer->lyxvc().registrer()) {
3704 reloadBuffer(*buffer);
3705 dr.clearMessageUpdate();
3710 case LFUN_VC_RENAME:
3711 case LFUN_VC_COPY: {
3712 if (!buffer || !ensureBufferClean(buffer))
3714 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3715 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3716 // Some changes are not yet committed.
3717 // We test here and not in getStatus(), since
3718 // this test is expensive.
3720 LyXVC::CommandResult ret =
3721 buffer->lyxvc().checkIn(log);
3723 if (ret == LyXVC::ErrorCommand ||
3724 ret == LyXVC::VCSuccess)
3725 reloadBuffer(*buffer);
3726 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3727 frontend::Alert::error(
3728 _("Revision control error."),
3729 _("Document could not be checked in."));
3733 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3734 LV_VC_RENAME : LV_VC_COPY;
3735 renameBuffer(*buffer, cmd.argument(), kind);
3740 case LFUN_VC_CHECK_IN:
3741 if (!buffer || !ensureBufferClean(buffer))
3743 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3745 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3747 // Only skip reloading if the checkin was cancelled or
3748 // an error occurred before the real checkin VCS command
3749 // was executed, since the VCS might have changed the
3750 // file even if it could not checkin successfully.
3751 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3752 reloadBuffer(*buffer);
3756 case LFUN_VC_CHECK_OUT:
3757 if (!buffer || !ensureBufferClean(buffer))
3759 if (buffer->lyxvc().inUse()) {
3760 dr.setMessage(buffer->lyxvc().checkOut());
3761 reloadBuffer(*buffer);
3765 case LFUN_VC_LOCKING_TOGGLE:
3766 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3768 if (buffer->lyxvc().inUse()) {
3769 string res = buffer->lyxvc().lockingToggle();
3771 frontend::Alert::error(_("Revision control error."),
3772 _("Error when setting the locking property."));
3775 reloadBuffer(*buffer);
3780 case LFUN_VC_REVERT:
3783 if (buffer->lyxvc().revert()) {
3784 reloadBuffer(*buffer);
3785 dr.clearMessageUpdate();
3789 case LFUN_VC_UNDO_LAST:
3792 buffer->lyxvc().undoLast();
3793 reloadBuffer(*buffer);
3794 dr.clearMessageUpdate();
3797 case LFUN_VC_REPO_UPDATE:
3800 if (ensureBufferClean(buffer)) {
3801 dr.setMessage(buffer->lyxvc().repoUpdate());
3802 checkExternallyModifiedBuffers();
3806 case LFUN_VC_COMMAND: {
3807 string flag = cmd.getArg(0);
3808 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3811 if (contains(flag, 'M')) {
3812 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3815 string path = cmd.getArg(1);
3816 if (contains(path, "$$p") && buffer)
3817 path = subst(path, "$$p", buffer->filePath());
3818 LYXERR(Debug::LYXVC, "Directory: " << path);
3820 if (!pp.isReadableDirectory()) {
3821 lyxerr << _("Directory is not accessible.") << endl;
3824 support::PathChanger p(pp);
3826 string command = cmd.getArg(2);
3827 if (command.empty())
3830 command = subst(command, "$$i", buffer->absFileName());
3831 command = subst(command, "$$p", buffer->filePath());
3833 command = subst(command, "$$m", to_utf8(message));
3834 LYXERR(Debug::LYXVC, "Command: " << command);
3836 one.startscript(Systemcall::Wait, command);
3840 if (contains(flag, 'I'))
3841 buffer->markDirty();
3842 if (contains(flag, 'R'))
3843 reloadBuffer(*buffer);
3848 case LFUN_VC_COMPARE: {
3849 if (cmd.argument().empty()) {
3850 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3856 string rev1 = cmd.getArg(0);
3860 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3863 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3864 f2 = buffer->absFileName();
3866 string rev2 = cmd.getArg(1);
3870 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3874 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3875 f1 << "\n" << f2 << "\n" );
3876 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3877 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3887 void GuiView::openChildDocument(string const & fname)
3889 LASSERT(documentBufferView(), return);
3890 Buffer & buffer = documentBufferView()->buffer();
3891 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3892 documentBufferView()->saveBookmark(false);
3893 Buffer * child = nullptr;
3894 if (theBufferList().exists(filename)) {
3895 child = theBufferList().getBuffer(filename);
3898 message(bformat(_("Opening child document %1$s..."),
3899 makeDisplayPath(filename.absFileName())));
3900 child = loadDocument(filename, false);
3902 // Set the parent name of the child document.
3903 // This makes insertion of citations and references in the child work,
3904 // when the target is in the parent or another child document.
3906 child->setParent(&buffer);
3910 bool GuiView::goToFileRow(string const & argument)
3914 size_t i = argument.find_last_of(' ');
3915 if (i != string::npos) {
3916 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3917 istringstream is(argument.substr(i + 1));
3922 if (i == string::npos) {
3923 LYXERR0("Wrong argument: " << argument);
3926 Buffer * buf = nullptr;
3927 string const realtmp = package().temp_dir().realPath();
3928 // We have to use os::path_prefix_is() here, instead of
3929 // simply prefixIs(), because the file name comes from
3930 // an external application and may need case adjustment.
3931 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3932 buf = theBufferList().getBufferFromTmp(file_name, true);
3933 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3934 << (buf ? " success" : " failed"));
3936 // Must replace extension of the file to be .lyx
3937 // and get full path
3938 FileName const s = fileSearch(string(),
3939 support::changeExtension(file_name, ".lyx"), "lyx");
3940 // Either change buffer or load the file
3941 if (theBufferList().exists(s))
3942 buf = theBufferList().getBuffer(s);
3943 else if (s.exists()) {
3944 buf = loadDocument(s);
3949 _("File does not exist: %1$s"),
3950 makeDisplayPath(file_name)));
3956 _("No buffer for file: %1$s."),
3957 makeDisplayPath(file_name))
3962 bool success = documentBufferView()->setCursorFromRow(row);
3964 LYXERR(Debug::LATEX,
3965 "setCursorFromRow: invalid position for row " << row);
3966 frontend::Alert::error(_("Inverse Search Failed"),
3967 _("Invalid position requested by inverse search.\n"
3968 "You may need to update the viewed document."));
3974 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3976 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3977 menu->exec(QCursor::pos());
3982 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3983 Buffer const * orig, Buffer * clone, string const & format)
3985 Buffer::ExportStatus const status = func(format);
3987 // the cloning operation will have produced a clone of the entire set of
3988 // documents, starting from the master. so we must delete those.
3989 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3991 busyBuffers.remove(orig);
3996 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3997 Buffer const * orig, Buffer * clone, string const & format)
3999 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4001 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4005 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4006 Buffer const * orig, Buffer * clone, string const & format)
4008 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4010 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4014 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4015 Buffer const * orig, Buffer * clone, string const & format)
4017 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4019 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4023 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4024 Buffer const * used_buffer,
4025 docstring const & msg,
4026 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4027 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4028 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4029 bool allow_async, bool use_tmpdir)
4034 string format = argument;
4036 format = used_buffer->params().getDefaultOutputFormat();
4037 processing_format = format;
4039 progress_->clearMessages();
4042 #if EXPORT_in_THREAD
4044 GuiViewPrivate::busyBuffers.insert(used_buffer);
4045 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4046 if (!cloned_buffer) {
4047 Alert::error(_("Export Error"),
4048 _("Error cloning the Buffer."));
4051 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4056 setPreviewFuture(f);
4057 last_export_format = used_buffer->params().bufferFormat();
4060 // We are asynchronous, so we don't know here anything about the success
4063 Buffer::ExportStatus status;
4065 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4066 } else if (previewFunc) {
4067 status = (used_buffer->*previewFunc)(format);
4070 handleExportStatus(gv_, status, format);
4072 return (status == Buffer::ExportSuccess
4073 || status == Buffer::PreviewSuccess);
4077 Buffer::ExportStatus status;
4079 status = (used_buffer->*syncFunc)(format, true);
4080 } else if (previewFunc) {
4081 status = (used_buffer->*previewFunc)(format);
4084 handleExportStatus(gv_, status, format);
4086 return (status == Buffer::ExportSuccess
4087 || status == Buffer::PreviewSuccess);
4091 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4093 BufferView * bv = currentBufferView();
4094 LASSERT(bv, return);
4096 // Let the current BufferView dispatch its own actions.
4097 bv->dispatch(cmd, dr);
4098 if (dr.dispatched()) {
4099 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4100 updateDialog("document", "");
4104 // Try with the document BufferView dispatch if any.
4105 BufferView * doc_bv = documentBufferView();
4106 if (doc_bv && doc_bv != bv) {
4107 doc_bv->dispatch(cmd, dr);
4108 if (dr.dispatched()) {
4109 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4110 updateDialog("document", "");
4115 // Then let the current Cursor dispatch its own actions.
4116 bv->cursor().dispatch(cmd);
4118 // update completion. We do it here and not in
4119 // processKeySym to avoid another redraw just for a
4120 // changed inline completion
4121 if (cmd.origin() == FuncRequest::KEYBOARD) {
4122 if (cmd.action() == LFUN_SELF_INSERT
4123 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4124 updateCompletion(bv->cursor(), true, true);
4125 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4126 updateCompletion(bv->cursor(), false, true);
4128 updateCompletion(bv->cursor(), false, false);
4131 dr = bv->cursor().result();
4135 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4137 BufferView * bv = currentBufferView();
4138 // By default we won't need any update.
4139 dr.screenUpdate(Update::None);
4140 // assume cmd will be dispatched
4141 dr.dispatched(true);
4143 Buffer * doc_buffer = documentBufferView()
4144 ? &(documentBufferView()->buffer()) : nullptr;
4146 if (cmd.origin() == FuncRequest::TOC) {
4147 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4148 toc->doDispatch(bv->cursor(), cmd, dr);
4152 string const argument = to_utf8(cmd.argument());
4154 switch(cmd.action()) {
4155 case LFUN_BUFFER_CHILD_OPEN:
4156 openChildDocument(to_utf8(cmd.argument()));
4159 case LFUN_BUFFER_IMPORT:
4160 importDocument(to_utf8(cmd.argument()));
4163 case LFUN_MASTER_BUFFER_EXPORT:
4165 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4167 case LFUN_BUFFER_EXPORT: {
4170 // GCC only sees strfwd.h when building merged
4171 if (::lyx::operator==(cmd.argument(), "custom")) {
4172 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4173 // so the following test should not be needed.
4174 // In principle, we could try to switch to such a view...
4175 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4176 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4180 string const dest = cmd.getArg(1);
4181 FileName target_dir;
4182 if (!dest.empty() && FileName::isAbsolute(dest))
4183 target_dir = FileName(support::onlyPath(dest));
4185 target_dir = doc_buffer->fileName().onlyPath();
4187 string const format = (argument.empty() || argument == "default") ?
4188 doc_buffer->params().getDefaultOutputFormat() : argument;
4190 if ((dest.empty() && doc_buffer->isUnnamed())
4191 || !target_dir.isDirWritable()) {
4192 exportBufferAs(*doc_buffer, from_utf8(format));
4195 /* TODO/Review: Is it a problem to also export the children?
4196 See the update_unincluded flag */
4197 d.asyncBufferProcessing(format,
4200 &GuiViewPrivate::exportAndDestroy,
4202 nullptr, cmd.allowAsync());
4203 // TODO Inform user about success
4207 case LFUN_BUFFER_EXPORT_AS: {
4208 LASSERT(doc_buffer, break);
4209 docstring f = cmd.argument();
4211 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4212 exportBufferAs(*doc_buffer, f);
4216 case LFUN_BUFFER_UPDATE: {
4217 d.asyncBufferProcessing(argument,
4220 &GuiViewPrivate::compileAndDestroy,
4222 nullptr, cmd.allowAsync(), true);
4225 case LFUN_BUFFER_VIEW: {
4226 d.asyncBufferProcessing(argument,
4228 _("Previewing ..."),
4229 &GuiViewPrivate::previewAndDestroy,
4231 &Buffer::preview, cmd.allowAsync());
4234 case LFUN_MASTER_BUFFER_UPDATE: {
4235 d.asyncBufferProcessing(argument,
4236 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4238 &GuiViewPrivate::compileAndDestroy,
4240 nullptr, cmd.allowAsync(), true);
4243 case LFUN_MASTER_BUFFER_VIEW: {
4244 d.asyncBufferProcessing(argument,
4245 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4247 &GuiViewPrivate::previewAndDestroy,
4248 nullptr, &Buffer::preview, cmd.allowAsync());
4251 case LFUN_EXPORT_CANCEL: {
4252 Systemcall::killscript();
4255 case LFUN_BUFFER_SWITCH: {
4256 string const file_name = to_utf8(cmd.argument());
4257 if (!FileName::isAbsolute(file_name)) {
4259 dr.setMessage(_("Absolute filename expected."));
4263 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4266 dr.setMessage(_("Document not loaded"));
4270 // Do we open or switch to the buffer in this view ?
4271 if (workArea(*buffer)
4272 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4277 // Look for the buffer in other views
4278 QList<int> const ids = guiApp->viewIds();
4280 for (; i != ids.size(); ++i) {
4281 GuiView & gv = guiApp->view(ids[i]);
4282 if (gv.workArea(*buffer)) {
4284 gv.activateWindow();
4286 gv.setBuffer(buffer);
4291 // If necessary, open a new window as a last resort
4292 if (i == ids.size()) {
4293 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4299 case LFUN_BUFFER_NEXT:
4300 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4303 case LFUN_BUFFER_MOVE_NEXT:
4304 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4307 case LFUN_BUFFER_PREVIOUS:
4308 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4311 case LFUN_BUFFER_MOVE_PREVIOUS:
4312 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4315 case LFUN_BUFFER_CHKTEX:
4316 LASSERT(doc_buffer, break);
4317 doc_buffer->runChktex();
4320 case LFUN_COMMAND_EXECUTE: {
4321 command_execute_ = true;
4322 minibuffer_focus_ = true;
4325 case LFUN_DROP_LAYOUTS_CHOICE:
4326 d.layout_->showPopup();
4329 case LFUN_MENU_OPEN:
4330 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4331 menu->exec(QCursor::pos());
4334 case LFUN_FILE_INSERT: {
4335 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4336 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4337 dr.forceBufferUpdate();
4338 dr.screenUpdate(Update::Force);
4343 case LFUN_FILE_INSERT_PLAINTEXT:
4344 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4345 string const fname = to_utf8(cmd.argument());
4346 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4347 dr.setMessage(_("Absolute filename expected."));
4351 FileName filename(fname);
4352 if (fname.empty()) {
4353 FileDialog dlg(qt_("Select file to insert"));
4355 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4356 QStringList(qt_("All Files (*)")));
4358 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4359 dr.setMessage(_("Canceled."));
4363 filename.set(fromqstr(result.second));
4367 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4368 bv->dispatch(new_cmd, dr);
4373 case LFUN_BUFFER_RELOAD: {
4374 LASSERT(doc_buffer, break);
4377 bool drop = (cmd.argument() == "dump");
4380 if (!drop && !doc_buffer->isClean()) {
4381 docstring const file =
4382 makeDisplayPath(doc_buffer->absFileName(), 20);
4383 if (doc_buffer->notifiesExternalModification()) {
4384 docstring text = _("The current version will be lost. "
4385 "Are you sure you want to load the version on disk "
4386 "of the document %1$s?");
4387 ret = Alert::prompt(_("Reload saved document?"),
4388 bformat(text, file), 1, 1,
4389 _("&Reload"), _("&Cancel"));
4391 docstring text = _("Any changes will be lost. "
4392 "Are you sure you want to revert to the saved version "
4393 "of the document %1$s?");
4394 ret = Alert::prompt(_("Revert to saved document?"),
4395 bformat(text, file), 1, 1,
4396 _("&Revert"), _("&Cancel"));
4401 doc_buffer->markClean();
4402 reloadBuffer(*doc_buffer);
4403 dr.forceBufferUpdate();
4408 case LFUN_BUFFER_RESET_EXPORT:
4409 LASSERT(doc_buffer, break);
4410 doc_buffer->requireFreshStart(true);
4411 dr.setMessage(_("Buffer export reset."));
4414 case LFUN_BUFFER_WRITE:
4415 LASSERT(doc_buffer, break);
4416 saveBuffer(*doc_buffer);
4419 case LFUN_BUFFER_WRITE_AS:
4420 LASSERT(doc_buffer, break);
4421 renameBuffer(*doc_buffer, cmd.argument());
4424 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4425 LASSERT(doc_buffer, break);
4426 renameBuffer(*doc_buffer, cmd.argument(),
4427 LV_WRITE_AS_TEMPLATE);
4430 case LFUN_BUFFER_WRITE_ALL: {
4431 Buffer * first = theBufferList().first();
4434 message(_("Saving all documents..."));
4435 // We cannot use a for loop as the buffer list cycles.
4438 if (!b->isClean()) {
4440 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4442 b = theBufferList().next(b);
4443 } while (b != first);
4444 dr.setMessage(_("All documents saved."));
4448 case LFUN_MASTER_BUFFER_FORALL: {
4452 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4453 funcToRun.allowAsync(false);
4455 for (Buffer const * buf : doc_buffer->allRelatives()) {
4456 // Switch to other buffer view and resend cmd
4457 lyx::dispatch(FuncRequest(
4458 LFUN_BUFFER_SWITCH, buf->absFileName()));
4459 lyx::dispatch(funcToRun);
4462 lyx::dispatch(FuncRequest(
4463 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4467 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4468 LASSERT(doc_buffer, break);
4469 doc_buffer->clearExternalModification();
4472 case LFUN_BUFFER_CLOSE:
4476 case LFUN_BUFFER_CLOSE_ALL:
4480 case LFUN_DEVEL_MODE_TOGGLE:
4481 devel_mode_ = !devel_mode_;
4483 dr.setMessage(_("Developer mode is now enabled."));
4485 dr.setMessage(_("Developer mode is now disabled."));
4488 case LFUN_TOOLBAR_SET: {
4489 string const name = cmd.getArg(0);
4490 string const state = cmd.getArg(1);
4491 if (GuiToolbar * t = toolbar(name))
4496 case LFUN_TOOLBAR_TOGGLE: {
4497 string const name = cmd.getArg(0);
4498 if (GuiToolbar * t = toolbar(name))
4503 case LFUN_TOOLBAR_MOVABLE: {
4504 string const name = cmd.getArg(0);
4506 // toggle (all) toolbars movablility
4507 toolbarsMovable_ = !toolbarsMovable_;
4508 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4509 GuiToolbar * tb = toolbar(ti.name);
4510 if (tb && tb->isMovable() != toolbarsMovable_)
4511 // toggle toolbar movablity if it does not fit lock
4512 // (all) toolbars positions state silent = true, since
4513 // status bar notifications are slow
4516 if (toolbarsMovable_)
4517 dr.setMessage(_("Toolbars unlocked."));
4519 dr.setMessage(_("Toolbars locked."));
4520 } else if (GuiToolbar * t = toolbar(name)) {
4521 // toggle current toolbar movablity
4523 // update lock (all) toolbars positions
4524 updateLockToolbars();
4529 case LFUN_ICON_SIZE: {
4530 QSize size = d.iconSize(cmd.argument());
4532 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4533 size.width(), size.height()));
4537 case LFUN_DIALOG_UPDATE: {
4538 string const name = to_utf8(cmd.argument());
4539 if (name == "prefs" || name == "document")
4540 updateDialog(name, string());
4541 else if (name == "paragraph")
4542 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4543 else if (currentBufferView()) {
4544 Inset * inset = currentBufferView()->editedInset(name);
4545 // Can only update a dialog connected to an existing inset
4547 // FIXME: get rid of this indirection; GuiView ask the inset
4548 // if he is kind enough to update itself...
4549 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4550 //FIXME: pass DispatchResult here?
4551 inset->dispatch(currentBufferView()->cursor(), fr);
4557 case LFUN_DIALOG_TOGGLE: {
4558 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4559 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4560 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4564 case LFUN_DIALOG_DISCONNECT_INSET:
4565 disconnectDialog(to_utf8(cmd.argument()));
4568 case LFUN_DIALOG_HIDE: {
4569 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4573 case LFUN_DIALOG_SHOW: {
4574 string const name = cmd.getArg(0);
4575 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4577 if (name == "latexlog") {
4578 // getStatus checks that
4579 LASSERT(doc_buffer, break);
4580 Buffer::LogType type;
4581 string const logfile = doc_buffer->logName(&type);
4583 case Buffer::latexlog:
4586 case Buffer::buildlog:
4587 sdata = "literate ";
4590 sdata += Lexer::quoteString(logfile);
4591 showDialog("log", sdata);
4592 } else if (name == "vclog") {
4593 // getStatus checks that
4594 LASSERT(doc_buffer, break);
4595 string const sdata2 = "vc " +
4596 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4597 showDialog("log", sdata2);
4598 } else if (name == "symbols") {
4599 sdata = bv->cursor().getEncoding()->name();
4601 showDialog("symbols", sdata);
4602 } else if (name == "findreplace") {
4603 sdata = to_utf8(bv->cursor().selectionAsString(false));
4604 showDialog(name, sdata);
4606 } else if (name == "prefs" && isFullScreen()) {
4607 lfunUiToggle("fullscreen");
4608 showDialog("prefs", sdata);
4610 showDialog(name, sdata);
4615 dr.setMessage(cmd.argument());
4618 case LFUN_UI_TOGGLE: {
4619 string arg = cmd.getArg(0);
4620 if (!lfunUiToggle(arg)) {
4621 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4622 dr.setMessage(bformat(msg, from_utf8(arg)));
4624 // Make sure the keyboard focus stays in the work area.
4629 case LFUN_VIEW_SPLIT: {
4630 LASSERT(doc_buffer, break);
4631 string const orientation = cmd.getArg(0);
4632 d.splitter_->setOrientation(orientation == "vertical"
4633 ? Qt::Vertical : Qt::Horizontal);
4634 TabWorkArea * twa = addTabWorkArea();
4635 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4636 setCurrentWorkArea(wa);
4639 case LFUN_TAB_GROUP_CLOSE:
4640 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4641 closeTabWorkArea(twa);
4642 d.current_work_area_ = nullptr;
4643 twa = d.currentTabWorkArea();
4644 // Switch to the next GuiWorkArea in the found TabWorkArea.
4646 // Make sure the work area is up to date.
4647 setCurrentWorkArea(twa->currentWorkArea());
4649 setCurrentWorkArea(nullptr);
4654 case LFUN_VIEW_CLOSE:
4655 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4656 closeWorkArea(twa->currentWorkArea());
4657 d.current_work_area_ = nullptr;
4658 twa = d.currentTabWorkArea();
4659 // Switch to the next GuiWorkArea in the found TabWorkArea.
4661 // Make sure the work area is up to date.
4662 setCurrentWorkArea(twa->currentWorkArea());
4664 setCurrentWorkArea(nullptr);
4669 case LFUN_COMPLETION_INLINE:
4670 if (d.current_work_area_)
4671 d.current_work_area_->completer().showInline();
4674 case LFUN_COMPLETION_POPUP:
4675 if (d.current_work_area_)
4676 d.current_work_area_->completer().showPopup();
4681 if (d.current_work_area_)
4682 d.current_work_area_->completer().tab();
4685 case LFUN_COMPLETION_CANCEL:
4686 if (d.current_work_area_) {
4687 if (d.current_work_area_->completer().popupVisible())
4688 d.current_work_area_->completer().hidePopup();
4690 d.current_work_area_->completer().hideInline();
4694 case LFUN_COMPLETION_ACCEPT:
4695 if (d.current_work_area_)
4696 d.current_work_area_->completer().activate();
4699 case LFUN_BUFFER_ZOOM_IN:
4700 case LFUN_BUFFER_ZOOM_OUT:
4701 case LFUN_BUFFER_ZOOM: {
4702 if (cmd.argument().empty()) {
4703 if (cmd.action() == LFUN_BUFFER_ZOOM)
4705 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4710 if (cmd.action() == LFUN_BUFFER_ZOOM)
4711 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4712 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4713 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4715 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4718 // Actual zoom value: default zoom + fractional extra value
4719 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4720 if (zoom < static_cast<int>(zoom_min_))
4723 setCurrentZoom(zoom);
4725 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4726 lyxrc.currentZoom, lyxrc.defaultZoom));
4728 guiApp->fontLoader().update();
4729 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4733 case LFUN_VC_REGISTER:
4734 case LFUN_VC_RENAME:
4736 case LFUN_VC_CHECK_IN:
4737 case LFUN_VC_CHECK_OUT:
4738 case LFUN_VC_REPO_UPDATE:
4739 case LFUN_VC_LOCKING_TOGGLE:
4740 case LFUN_VC_REVERT:
4741 case LFUN_VC_UNDO_LAST:
4742 case LFUN_VC_COMMAND:
4743 case LFUN_VC_COMPARE:
4744 dispatchVC(cmd, dr);
4747 case LFUN_SERVER_GOTO_FILE_ROW:
4748 if(goToFileRow(to_utf8(cmd.argument())))
4749 dr.screenUpdate(Update::Force | Update::FitCursor);
4752 case LFUN_LYX_ACTIVATE:
4756 case LFUN_WINDOW_RAISE:
4762 case LFUN_FORWARD_SEARCH: {
4763 // it seems safe to assume we have a document buffer, since
4764 // getStatus wants one.
4765 LASSERT(doc_buffer, break);
4766 Buffer const * doc_master = doc_buffer->masterBuffer();
4767 FileName const path(doc_master->temppath());
4768 string const texname = doc_master->isChild(doc_buffer)
4769 ? DocFileName(changeExtension(
4770 doc_buffer->absFileName(),
4771 "tex")).mangledFileName()
4772 : doc_buffer->latexName();
4773 string const fulltexname =
4774 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4775 string const mastername =
4776 removeExtension(doc_master->latexName());
4777 FileName const dviname(addName(path.absFileName(),
4778 addExtension(mastername, "dvi")));
4779 FileName const pdfname(addName(path.absFileName(),
4780 addExtension(mastername, "pdf")));
4781 bool const have_dvi = dviname.exists();
4782 bool const have_pdf = pdfname.exists();
4783 if (!have_dvi && !have_pdf) {
4784 dr.setMessage(_("Please, preview the document first."));
4787 string outname = dviname.onlyFileName();
4788 string command = lyxrc.forward_search_dvi;
4789 if (!have_dvi || (have_pdf &&
4790 pdfname.lastModified() > dviname.lastModified())) {
4791 outname = pdfname.onlyFileName();
4792 command = lyxrc.forward_search_pdf;
4795 DocIterator cur = bv->cursor();
4796 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4797 LYXERR(Debug::ACTION, "Forward search: row:" << row
4799 if (row == -1 || command.empty()) {
4800 dr.setMessage(_("Couldn't proceed."));
4803 string texrow = convert<string>(row);
4805 command = subst(command, "$$n", texrow);
4806 command = subst(command, "$$f", fulltexname);
4807 command = subst(command, "$$t", texname);
4808 command = subst(command, "$$o", outname);
4810 volatile PathChanger p(path);
4812 one.startscript(Systemcall::DontWait, command);
4816 case LFUN_SPELLING_CONTINUOUSLY:
4817 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4818 dr.screenUpdate(Update::Force);
4821 case LFUN_CITATION_OPEN: {
4823 if (theFormats().getFormat("pdf"))
4824 pdfv = theFormats().getFormat("pdf")->viewer();
4825 if (theFormats().getFormat("ps"))
4826 psv = theFormats().getFormat("ps")->viewer();
4827 frontend::showTarget(argument, pdfv, psv);
4832 // The LFUN must be for one of BufferView, Buffer or Cursor;
4834 dispatchToBufferView(cmd, dr);
4838 // Need to update bv because many LFUNs here might have destroyed it
4839 bv = currentBufferView();
4841 // Clear non-empty selections
4842 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4844 Cursor & cur = bv->cursor();
4845 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4846 cur.clearSelection();
4852 bool GuiView::lfunUiToggle(string const & ui_component)
4854 if (ui_component == "scrollbar") {
4855 // hide() is of no help
4856 if (d.current_work_area_->verticalScrollBarPolicy() ==
4857 Qt::ScrollBarAlwaysOff)
4859 d.current_work_area_->setVerticalScrollBarPolicy(
4860 Qt::ScrollBarAsNeeded);
4862 d.current_work_area_->setVerticalScrollBarPolicy(
4863 Qt::ScrollBarAlwaysOff);
4864 } else if (ui_component == "statusbar") {
4865 statusBar()->setVisible(!statusBar()->isVisible());
4866 } else if (ui_component == "menubar") {
4867 menuBar()->setVisible(!menuBar()->isVisible());
4868 } else if (ui_component == "zoomslider") {
4869 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4870 zoom_in_->setVisible(zoom_slider_->isVisible());
4871 zoom_out_->setVisible(zoom_slider_->isVisible());
4872 act_zoom_show_->setChecked(zoom_slider_->isVisible());
4873 } else if (ui_component == "frame") {
4874 int const l = contentsMargins().left();
4876 //are the frames in default state?
4877 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4879 #if QT_VERSION > 0x050903
4880 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4882 setContentsMargins(-2, -2, -2, -2);
4884 #if QT_VERSION > 0x050903
4885 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4887 setContentsMargins(0, 0, 0, 0);
4890 if (ui_component == "fullscreen") {
4898 void GuiView::toggleFullScreen()
4900 setWindowState(windowState() ^ Qt::WindowFullScreen);
4904 Buffer const * GuiView::updateInset(Inset const * inset)
4909 Buffer const * inset_buffer = &(inset->buffer());
4911 for (int i = 0; i != d.splitter_->count(); ++i) {
4912 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4915 Buffer const * buffer = &(wa->bufferView().buffer());
4916 if (inset_buffer == buffer)
4917 wa->scheduleRedraw(true);
4919 return inset_buffer;
4923 void GuiView::restartCaret()
4925 /* When we move around, or type, it's nice to be able to see
4926 * the caret immediately after the keypress.
4928 if (d.current_work_area_)
4929 d.current_work_area_->startBlinkingCaret();
4931 // Take this occasion to update the other GUI elements.
4937 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4939 if (d.current_work_area_)
4940 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4945 // This list should be kept in sync with the list of insets in
4946 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4947 // dialog should have the same name as the inset.
4948 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4949 // docs in LyXAction.cpp.
4951 char const * const dialognames[] = {
4953 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4954 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4955 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4956 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4957 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4958 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4959 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4960 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4962 char const * const * const end_dialognames =
4963 dialognames + (sizeof(dialognames) / sizeof(char *));
4967 cmpCStr(char const * name) : name_(name) {}
4968 bool operator()(char const * other) {
4969 return strcmp(other, name_) == 0;
4976 bool isValidName(string const & name)
4978 return find_if(dialognames, end_dialognames,
4979 cmpCStr(name.c_str())) != end_dialognames;
4985 void GuiView::resetDialogs()
4987 // Make sure that no LFUN uses any GuiView.
4988 guiApp->setCurrentView(nullptr);
4992 constructToolbars();
4993 guiApp->menus().fillMenuBar(menuBar(), this, false);
4994 d.layout_->updateContents(true);
4995 // Now update controls with current buffer.
4996 guiApp->setCurrentView(this);
5002 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5004 for (QObject * child: widget->children()) {
5005 if (child->inherits("QGroupBox")) {
5006 QGroupBox * box = (QGroupBox*) child;
5009 flatGroupBoxes(child, flag);
5015 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5017 if (!isValidName(name))
5020 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5022 if (it != d.dialogs_.end()) {
5024 it->second->hideView();
5025 return it->second.get();
5028 Dialog * dialog = build(name);
5029 d.dialogs_[name].reset(dialog);
5030 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5031 // Force a uniform style for group boxes
5032 // On Mac non-flat works better, on Linux flat is standard
5033 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5035 if (lyxrc.allow_geometry_session)
5036 dialog->restoreSession();
5043 void GuiView::showDialog(string const & name, string const & sdata,
5046 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5050 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5056 const string name = fromqstr(qname);
5057 const string sdata = fromqstr(qdata);
5061 Dialog * dialog = findOrBuild(name, false);
5063 bool const visible = dialog->isVisibleView();
5064 dialog->showData(sdata);
5065 if (currentBufferView())
5066 currentBufferView()->editInset(name, inset);
5067 // We only set the focus to the new dialog if it was not yet
5068 // visible in order not to change the existing previous behaviour
5070 // activateWindow is needed for floating dockviews
5071 dialog->asQWidget()->raise();
5072 dialog->asQWidget()->activateWindow();
5073 if (dialog->wantInitialFocus())
5074 dialog->asQWidget()->setFocus();
5078 catch (ExceptionMessage const &) {
5086 bool GuiView::isDialogVisible(string const & name) const
5088 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5089 if (it == d.dialogs_.end())
5091 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5095 void GuiView::hideDialog(string const & name, Inset * inset)
5097 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5098 if (it == d.dialogs_.end())
5102 if (!currentBufferView())
5104 if (inset != currentBufferView()->editedInset(name))
5108 Dialog * const dialog = it->second.get();
5109 if (dialog->isVisibleView())
5111 if (currentBufferView())
5112 currentBufferView()->editInset(name, nullptr);
5116 void GuiView::disconnectDialog(string const & name)
5118 if (!isValidName(name))
5120 if (currentBufferView())
5121 currentBufferView()->editInset(name, nullptr);
5125 void GuiView::hideAll() const
5127 for(auto const & dlg_p : d.dialogs_)
5128 dlg_p.second->hideView();
5132 void GuiView::updateDialogs()
5134 for(auto const & dlg_p : d.dialogs_) {
5135 Dialog * dialog = dlg_p.second.get();
5137 if (dialog->needBufferOpen() && !documentBufferView())
5138 hideDialog(fromqstr(dialog->name()), nullptr);
5139 else if (dialog->isVisibleView())
5140 dialog->checkStatus();
5148 Dialog * GuiView::build(string const & name)
5150 return createDialog(*this, name);
5154 SEMenu::SEMenu(QWidget * parent)
5156 QAction * action = addAction(qt_("Disable Shell Escape"));
5157 connect(action, SIGNAL(triggered()),
5158 parent, SLOT(disableShellEscape()));
5161 } // namespace frontend
5164 #include "moc_GuiView.cpp"