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 "FindAndReplace.h"
22 #include "FontLoader.h"
23 #include "GuiApplication.h"
24 #include "GuiClickableLabel.h"
25 #include "GuiCompleter.h"
26 #include "GuiFontMetrics.h"
27 #include "GuiKeySymbol.h"
29 #include "GuiToolbar.h"
30 #include "GuiWorkArea.h"
31 #include "GuiProgress.h"
32 #include "LayoutBox.h"
36 #include "qt_helpers.h"
37 #include "support/filetools.h"
39 #include "frontends/alert.h"
40 #include "frontends/KeySymbol.h"
42 #include "buffer_funcs.h"
44 #include "BufferList.h"
45 #include "BufferParams.h"
46 #include "BufferView.h"
48 #include "Converter.h"
50 #include "CutAndPaste.h"
52 #include "ErrorList.h"
54 #include "FuncStatus.h"
55 #include "FuncRequest.h"
56 #include "KeySymbol.h"
58 #include "LayoutFile.h"
60 #include "LyXAction.h"
64 #include "Paragraph.h"
65 #include "SpellChecker.h"
72 #include "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/gettext.h"
77 #include "support/ForkedCalls.h"
78 #include "support/lassert.h"
79 #include "support/lstrings.h"
80 #include "support/os.h"
81 #include "support/Package.h"
82 #include "support/PathChanger.h"
83 #include "support/Systemcall.h"
84 #include "support/Timeout.h"
85 #include "support/ProgressInterface.h"
88 #include <QApplication>
89 #include <QCloseEvent>
90 #include <QDragEnterEvent>
93 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
114 #include <QGestureEvent>
115 #include <QPinchGesture>
118 // sync with GuiAlert.cpp
119 #define EXPORT_in_THREAD 1
122 #include "support/bind.h"
126 #ifdef HAVE_SYS_TIME_H
127 # include <sys/time.h>
135 using namespace lyx::support;
139 using support::addExtension;
140 using support::changeExtension;
141 using support::removeExtension;
147 class BackgroundWidget : public QWidget
150 BackgroundWidget(int width, int height)
151 : width_(width), height_(height)
153 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
154 if (!lyxrc.show_banner)
156 /// The text to be written on top of the pixmap
157 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
158 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
159 /// The text to be written on top of the pixmap
160 QString const text = lyx_version ?
161 qt_("version ") + lyx_version : qt_("unknown version");
162 #if QT_VERSION >= 0x050000
163 QString imagedir = "images/";
164 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
165 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
166 if (svgRenderer.isValid()) {
167 splash_ = QPixmap(splashSize());
168 QPainter painter(&splash_);
169 svgRenderer.render(&painter);
170 splash_.setDevicePixelRatio(pixelRatio());
172 splash_ = getPixmap("images/", "banner", "png");
175 splash_ = getPixmap("images/", "banner", "svgz,png");
178 QPainter pain(&splash_);
179 pain.setPen(QColor(0, 0, 0));
180 qreal const fsize = fontSize();
183 qreal locscale = htextsize.toFloat(&ok);
186 QPointF const position = textPosition(false);
187 QPointF const hposition = textPosition(true);
188 QRectF const hrect(hposition, splashSize());
190 "widget pixel ratio: " << pixelRatio() <<
191 " splash pixel ratio: " << splashPixelRatio() <<
192 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
194 // The font used to display the version info
195 font.setStyleHint(QFont::SansSerif);
196 font.setWeight(QFont::Bold);
197 font.setPointSizeF(fsize);
199 pain.drawText(position, text);
200 // The font used to display the version info
201 font.setStyleHint(QFont::SansSerif);
202 font.setWeight(QFont::Normal);
203 font.setPointSizeF(hfsize);
204 // Check how long the logo gets with the current font
205 // and adapt if the font is running wider than what
207 GuiFontMetrics fm(font);
208 // Split the title into lines to measure the longest line
209 // in the current l7n.
210 QStringList titlesegs = htext.split('\n');
212 int hline = fm.maxHeight();
213 for (QString const & seg : titlesegs) {
214 if (fm.width(seg) > wline)
215 wline = fm.width(seg);
217 // The longest line in the reference font (for English)
218 // is 180. Calculate scale factor from that.
219 double const wscale = wline > 0 ? (180.0 / wline) : 1;
220 // Now do the same for the height (necessary for condensed fonts)
221 double const hscale = (34.0 / hline);
222 // take the lower of the two scale factors.
223 double const scale = min(wscale, hscale);
224 // Now rescale. Also consider l7n's offset factor.
225 font.setPointSizeF(hfsize * scale * locscale);
228 pain.drawText(hrect, Qt::AlignLeft, htext);
229 setFocusPolicy(Qt::StrongFocus);
232 void paintEvent(QPaintEvent *) override
234 int const w = width_;
235 int const h = height_;
236 int const x = (width() - w) / 2;
237 int const y = (height() - h) / 2;
239 "widget pixel ratio: " << pixelRatio() <<
240 " splash pixel ratio: " << splashPixelRatio() <<
241 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
243 pain.drawPixmap(x, y, w, h, splash_);
246 void keyPressEvent(QKeyEvent * ev) override
249 setKeySymbol(&sym, ev);
251 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
263 /// Current ratio between physical pixels and device-independent pixels
264 double pixelRatio() const {
265 #if QT_VERSION >= 0x050000
266 return qt_scale_factor * devicePixelRatio();
272 qreal fontSize() const {
273 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
276 QPointF textPosition(bool const heading) const {
277 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
278 : QPointF(width_/2 - 18, height_/2 + 45);
281 QSize splashSize() const {
283 static_cast<unsigned int>(width_ * pixelRatio()),
284 static_cast<unsigned int>(height_ * pixelRatio()));
287 /// Ratio between physical pixels and device-independent pixels of splash image
288 double splashPixelRatio() const {
289 #if QT_VERSION >= 0x050000
290 return splash_.devicePixelRatio();
298 /// Toolbar store providing access to individual toolbars by name.
299 typedef map<string, GuiToolbar *> ToolbarMap;
301 typedef shared_ptr<Dialog> DialogPtr;
306 class GuiView::GuiViewPrivate
309 GuiViewPrivate(GuiViewPrivate const &);
310 void operator=(GuiViewPrivate const &);
312 GuiViewPrivate(GuiView * gv)
313 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
314 layout_(nullptr), autosave_timeout_(5000),
317 // hardcode here the platform specific icon size
318 smallIconSize = 16; // scaling problems
319 normalIconSize = 20; // ok, default if iconsize.png is missing
320 bigIconSize = 26; // better for some math icons
321 hugeIconSize = 32; // better for hires displays
324 // if it exists, use width of iconsize.png as normal size
325 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
326 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
328 QImage image(toqstr(fn.absFileName()));
329 if (image.width() < int(smallIconSize))
330 normalIconSize = smallIconSize;
331 else if (image.width() > int(giantIconSize))
332 normalIconSize = giantIconSize;
334 normalIconSize = image.width();
337 splitter_ = new QSplitter;
338 bg_widget_ = new BackgroundWidget(400, 250);
339 stack_widget_ = new QStackedWidget;
340 stack_widget_->addWidget(bg_widget_);
341 stack_widget_->addWidget(splitter_);
344 // TODO cleanup, remove the singleton, handle multiple Windows?
345 progress_ = ProgressInterface::instance();
346 if (!dynamic_cast<GuiProgress*>(progress_)) {
347 progress_ = new GuiProgress; // TODO who deletes it
348 ProgressInterface::setInstance(progress_);
351 dynamic_cast<GuiProgress*>(progress_),
352 SIGNAL(updateStatusBarMessage(QString const&)),
353 gv, SLOT(updateStatusBarMessage(QString const&)));
355 dynamic_cast<GuiProgress*>(progress_),
356 SIGNAL(clearMessageText()),
357 gv, SLOT(clearMessageText()));
364 delete stack_widget_;
369 stack_widget_->setCurrentWidget(bg_widget_);
370 bg_widget_->setUpdatesEnabled(true);
371 bg_widget_->setFocus();
374 int tabWorkAreaCount()
376 return splitter_->count();
379 TabWorkArea * tabWorkArea(int i)
381 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
384 TabWorkArea * currentTabWorkArea()
386 int areas = tabWorkAreaCount();
388 // The first TabWorkArea is always the first one, if any.
389 return tabWorkArea(0);
391 for (int i = 0; i != areas; ++i) {
392 TabWorkArea * twa = tabWorkArea(i);
393 if (current_main_work_area_ == twa->currentWorkArea())
397 // None has the focus so we just take the first one.
398 return tabWorkArea(0);
401 int countWorkAreasOf(Buffer & buf)
403 int areas = tabWorkAreaCount();
405 for (int i = 0; i != areas; ++i) {
406 TabWorkArea * twa = tabWorkArea(i);
407 if (twa->workArea(buf))
413 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
415 if (processing_thread_watcher_.isRunning()) {
416 // we prefer to cancel this preview in order to keep a snappy
420 processing_thread_watcher_.setFuture(f);
423 QSize iconSize(docstring const & icon_size)
426 if (icon_size == "small")
427 size = smallIconSize;
428 else if (icon_size == "normal")
429 size = normalIconSize;
430 else if (icon_size == "big")
432 else if (icon_size == "huge")
434 else if (icon_size == "giant")
435 size = giantIconSize;
437 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
439 if (size < smallIconSize)
440 size = smallIconSize;
442 return QSize(size, size);
445 QSize iconSize(QString const & icon_size)
447 return iconSize(qstring_to_ucs4(icon_size));
450 string & iconSize(QSize const & qsize)
452 LATTEST(qsize.width() == qsize.height());
454 static string icon_size;
456 unsigned int size = qsize.width();
458 if (size < smallIconSize)
459 size = smallIconSize;
461 if (size == smallIconSize)
463 else if (size == normalIconSize)
464 icon_size = "normal";
465 else if (size == bigIconSize)
467 else if (size == hugeIconSize)
469 else if (size == giantIconSize)
472 icon_size = convert<string>(size);
477 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
482 Buffer * buffer, string const & format);
483 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
486 static Buffer::ExportStatus runAndDestroy(const T& func,
487 Buffer const * orig, Buffer * buffer, string const & format);
489 // TODO syncFunc/previewFunc: use bind
490 bool asyncBufferProcessing(string const & argument,
491 Buffer const * used_buffer,
492 docstring const & msg,
493 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
494 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
495 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
496 bool allow_async, bool use_tmpdir = false);
498 QVector<GuiWorkArea*> guiWorkAreas();
502 GuiWorkArea * current_work_area_;
503 GuiWorkArea * current_main_work_area_;
504 QSplitter * splitter_;
505 QStackedWidget * stack_widget_;
506 BackgroundWidget * bg_widget_;
508 ToolbarMap toolbars_;
509 ProgressInterface* progress_;
510 /// The main layout box.
512 * \warning Don't Delete! The layout box is actually owned by
513 * whichever toolbar contains it. All the GuiView class needs is a
514 * means of accessing it.
516 * FIXME: replace that with a proper model so that we are not limited
517 * to only one dialog.
522 map<string, DialogPtr> dialogs_;
525 QTimer statusbar_timer_;
526 QTimer statusbar_stats_timer_;
527 /// auto-saving of buffers
528 Timeout autosave_timeout_;
531 TocModels toc_models_;
534 QFutureWatcher<docstring> autosave_watcher_;
535 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
537 string last_export_format;
538 string processing_format;
540 static QSet<Buffer const *> busyBuffers;
542 unsigned int smallIconSize;
543 unsigned int normalIconSize;
544 unsigned int bigIconSize;
545 unsigned int hugeIconSize;
546 unsigned int giantIconSize;
548 /// flag against a race condition due to multiclicks, see bug #1119
551 // Timers for statistic updates in buffer
552 /// Current time left to the nearest info update
553 int time_to_update = 1000;
554 ///Basic step for timer in ms. Basically reaction time for short selections
555 int const timer_rate = 500;
556 /// Real stats updates infrequently. First they take long time for big buffers, second
557 /// they are visible for fast-repeat keyboards even for mid documents.
558 int const default_stats_rate = 5000;
559 /// Detection of new selection, so we can react fast
560 bool already_in_selection_ = false;
561 /// Maximum size of "short" selection for which we can update with faster timer_rate
562 int const max_sel_chars = 5000;
566 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
569 GuiView::GuiView(int id)
570 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
571 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
572 char_count_enabled_(true), char_nb_count_enabled_(false),
573 toolbarsMovable_(true), devel_mode_(false)
575 connect(this, SIGNAL(bufferViewChanged()),
576 this, SLOT(onBufferViewChanged()));
578 // GuiToolbars *must* be initialised before the menu bar.
579 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
582 // set ourself as the current view. This is needed for the menu bar
583 // filling, at least for the static special menu item on Mac. Otherwise
584 // they are greyed out.
585 guiApp->setCurrentView(this);
587 // Fill up the menu bar.
588 guiApp->menus().fillMenuBar(menuBar(), this, true);
590 setCentralWidget(d.stack_widget_);
592 // Start autosave timer
593 if (lyxrc.autosave) {
594 // The connection is closed when this is destroyed.
595 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
596 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
597 d.autosave_timeout_.start();
599 connect(&d.statusbar_timer_, SIGNAL(timeout()),
600 this, SLOT(clearMessage()));
601 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
602 this, SLOT(showStats()));
603 d.statusbar_stats_timer_.start(d.timer_rate);
605 // We don't want to keep the window in memory if it is closed.
606 setAttribute(Qt::WA_DeleteOnClose, true);
608 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
609 // QIcon::fromTheme was introduced in Qt 4.6
610 #if (QT_VERSION >= 0x040600)
611 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
612 // since the icon is provided in the application bundle. We use a themed
613 // version when available and use the bundled one as fallback.
614 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
616 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
622 // use tabbed dock area for multiple docks
623 // (such as "source" and "messages")
624 setDockOptions(QMainWindow::ForceTabbedDocks);
627 // use document mode tabs on docks
628 setDocumentMode(true);
632 setAcceptDrops(true);
634 // add busy indicator to statusbar
635 search_mode mode = theGuiApp()->imageSearchMode();
636 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
637 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
638 statusBar()->addPermanentWidget(busySVG);
639 // make busy indicator square with 5px margins
640 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
643 connect(&d.processing_thread_watcher_, SIGNAL(started()),
644 busySVG, SLOT(show()));
645 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
646 busySVG, SLOT(hide()));
647 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
649 stat_counts_ = new GuiClickableLabel(statusBar());
650 stat_counts_->setAlignment(Qt::AlignCenter);
651 stat_counts_->setFrameStyle(QFrame::StyledPanel);
652 stat_counts_->hide();
653 statusBar()->addPermanentWidget(stat_counts_);
655 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
658 QFontMetrics const fm(statusBar()->fontMetrics());
660 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
661 // Small size slider for macOS to prevent the status bar from enlarging
662 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
663 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
664 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
666 zoom_slider_->setFixedWidth(fm.width('x') * 15);
668 // Make the defaultZoom center
669 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
670 // Initialize proper zoom value
672 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
673 // Actual zoom value: default zoom + fractional offset
674 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
675 zoom = min(max(zoom, zoom_min_), zoom_max_);
676 zoom_slider_->setValue(zoom);
677 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
679 // Buttons to change zoom stepwise
680 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
681 QSize s(fm.horizontalAdvance('+'), fm.height());
683 QSize s(fm.width('+'), fm.height());
685 zoom_in_ = new GuiClickableLabel(statusBar());
686 zoom_in_->setText("+");
687 zoom_in_->setFixedSize(s);
688 zoom_in_->setAlignment(Qt::AlignCenter);
689 zoom_out_ = new GuiClickableLabel(statusBar());
690 zoom_out_->setText(QString(QChar(0x2212)));
691 zoom_out_->setFixedSize(s);
692 zoom_out_->setAlignment(Qt::AlignCenter);
694 statusBar()->addPermanentWidget(zoom_out_);
695 zoom_out_->setEnabled(currentBufferView());
696 statusBar()->addPermanentWidget(zoom_slider_);
697 zoom_slider_->setEnabled(currentBufferView());
698 zoom_in_->setEnabled(currentBufferView());
699 statusBar()->addPermanentWidget(zoom_in_);
701 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
702 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
703 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
704 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
705 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
707 // QPalette palette = statusBar()->palette();
709 zoom_value_ = new QLabel(statusBar());
710 // zoom_value_->setPalette(palette);
711 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
712 zoom_value_->setFixedHeight(fm.height());
713 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
714 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
716 zoom_value_->setMinimumWidth(fm.width("444\%"));
718 zoom_value_->setAlignment(Qt::AlignCenter);
719 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
720 statusBar()->addPermanentWidget(zoom_value_);
721 zoom_value_->setEnabled(currentBufferView());
723 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
724 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
725 this, SLOT(showZoomContextMenu()));
727 // enable pinch to zoom
728 grabGesture(Qt::PinchGesture);
730 int const iconheight = max(int(d.normalIconSize), fm.height());
731 QSize const iconsize(iconheight, iconheight);
733 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
734 shell_escape_ = new QLabel(statusBar());
735 shell_escape_->setPixmap(shellescape);
736 shell_escape_->setAlignment(Qt::AlignCenter);
737 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
738 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
739 "external commands for this document. "
740 "Right click to change."));
741 SEMenu * menu = new SEMenu(this);
742 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
743 menu, SLOT(showMenu(QPoint)));
744 shell_escape_->hide();
745 statusBar()->addPermanentWidget(shell_escape_);
747 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
748 read_only_ = new QLabel(statusBar());
749 read_only_->setPixmap(readonly);
750 read_only_->setAlignment(Qt::AlignCenter);
752 statusBar()->addPermanentWidget(read_only_);
754 version_control_ = new QLabel(statusBar());
755 version_control_->setAlignment(Qt::AlignCenter);
756 version_control_->setFrameStyle(QFrame::StyledPanel);
757 version_control_->hide();
758 statusBar()->addPermanentWidget(version_control_);
760 statusBar()->setSizeGripEnabled(true);
763 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
764 SLOT(autoSaveThreadFinished()));
766 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
767 SLOT(processingThreadStarted()));
768 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
769 SLOT(processingThreadFinished()));
771 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
772 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
774 // set custom application bars context menu, e.g. tool bar and menu bar
775 setContextMenuPolicy(Qt::CustomContextMenu);
776 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
777 SLOT(toolBarPopup(const QPoint &)));
779 // Forbid too small unresizable window because it can happen
780 // with some window manager under X11.
781 setMinimumSize(300, 200);
783 if (lyxrc.allow_geometry_session) {
784 // Now take care of session management.
789 // no session handling, default to a sane size.
790 setGeometry(50, 50, 690, 510);
793 // clear session data if any.
794 settings.remove("views");
804 void GuiView::disableShellEscape()
806 BufferView * bv = documentBufferView();
809 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
810 bv->buffer().params().shell_escape = false;
811 bv->processUpdateFlags(Update::Force);
815 void GuiView::checkCancelBackground()
817 docstring const ttl = _("Cancel Export?");
818 docstring const msg = _("Do you want to cancel the background export process?");
820 Alert::prompt(ttl, msg, 1, 1,
821 _("&Cancel export"), _("Co&ntinue"));
823 Systemcall::killscript();
826 void GuiView::statsPressed()
829 dispatch(FuncRequest(LFUN_STATISTICS), dr);
832 void GuiView::zoomSliderMoved(int value)
835 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
836 scheduleRedrawWorkAreas();
837 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
841 void GuiView::zoomValueChanged(int value)
843 if (value != lyxrc.currentZoom)
844 zoomSliderMoved(value);
848 void GuiView::zoomInPressed()
851 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
852 scheduleRedrawWorkAreas();
856 void GuiView::zoomOutPressed()
859 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
860 scheduleRedrawWorkAreas();
864 void GuiView::showZoomContextMenu()
866 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
869 menu->exec(QCursor::pos());
873 void GuiView::scheduleRedrawWorkAreas()
875 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
876 TabWorkArea* ta = d.tabWorkArea(i);
877 for (int u = 0; u < ta->count(); u++) {
878 ta->workArea(u)->scheduleRedraw(true);
884 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
886 QVector<GuiWorkArea*> areas;
887 for (int i = 0; i < tabWorkAreaCount(); i++) {
888 TabWorkArea* ta = tabWorkArea(i);
889 for (int u = 0; u < ta->count(); u++) {
890 areas << ta->workArea(u);
896 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
897 string const & format)
899 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
902 case Buffer::ExportSuccess:
903 msg = bformat(_("Successful export to format: %1$s"), fmt);
905 case Buffer::ExportCancel:
906 msg = _("Document export cancelled.");
908 case Buffer::ExportError:
909 case Buffer::ExportNoPathToFormat:
910 case Buffer::ExportTexPathHasSpaces:
911 case Buffer::ExportConverterError:
912 msg = bformat(_("Error while exporting format: %1$s"), fmt);
914 case Buffer::PreviewSuccess:
915 msg = bformat(_("Successful preview of format: %1$s"), fmt);
917 case Buffer::PreviewError:
918 msg = bformat(_("Error while previewing format: %1$s"), fmt);
920 case Buffer::ExportKilled:
921 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
928 void GuiView::processingThreadStarted()
933 void GuiView::processingThreadFinished()
935 QFutureWatcher<Buffer::ExportStatus> const * watcher =
936 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
938 Buffer::ExportStatus const status = watcher->result();
939 handleExportStatus(this, status, d.processing_format);
942 BufferView const * const bv = currentBufferView();
943 if (bv && !bv->buffer().errorList("Export").empty()) {
948 bool const error = (status != Buffer::ExportSuccess &&
949 status != Buffer::PreviewSuccess &&
950 status != Buffer::ExportCancel);
952 ErrorList & el = bv->buffer().errorList(d.last_export_format);
953 // at this point, we do not know if buffer-view or
954 // master-buffer-view was called. If there was an export error,
955 // and the current buffer's error log is empty, we guess that
956 // it must be master-buffer-view that was called so we set
958 errors(d.last_export_format, el.empty());
963 void GuiView::autoSaveThreadFinished()
965 QFutureWatcher<docstring> const * watcher =
966 static_cast<QFutureWatcher<docstring> const *>(sender());
967 message(watcher->result());
972 void GuiView::saveLayout() const
975 settings.setValue("zoom_ratio", zoom_ratio_);
976 settings.setValue("devel_mode", devel_mode_);
977 settings.beginGroup("views");
978 settings.beginGroup(QString::number(id_));
979 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
980 settings.setValue("pos", pos());
981 settings.setValue("size", size());
983 settings.setValue("geometry", saveGeometry());
984 settings.setValue("layout", saveState(0));
985 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
986 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
987 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
988 settings.setValue("word_count_enabled", word_count_enabled_);
989 settings.setValue("char_count_enabled", char_count_enabled_);
990 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
994 void GuiView::saveUISettings() const
998 // Save the toolbar private states
999 for (auto const & tb_p : d.toolbars_)
1000 tb_p.second->saveSession(settings);
1001 // Now take care of all other dialogs
1002 for (auto const & dlg_p : d.dialogs_)
1003 dlg_p.second->saveSession(settings);
1007 void GuiView::setCurrentZoom(const int v)
1009 lyxrc.currentZoom = v;
1010 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1011 Q_EMIT currentZoomChanged(v);
1015 bool GuiView::restoreLayout()
1018 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1019 // Actual zoom value: default zoom + fractional offset
1020 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1021 zoom = min(max(zoom, zoom_min_), zoom_max_);
1022 setCurrentZoom(zoom);
1023 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1024 settings.beginGroup("views");
1025 settings.beginGroup(QString::number(id_));
1026 QString const icon_key = "icon_size";
1027 if (!settings.contains(icon_key))
1030 //code below is skipped when when ~/.config/LyX is (re)created
1031 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1033 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1035 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1036 zoom_slider_->setVisible(show_zoom_slider);
1037 zoom_in_->setVisible(show_zoom_slider);
1038 zoom_out_->setVisible(show_zoom_slider);
1040 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1041 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1042 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1043 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1045 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1046 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1047 QSize size = settings.value("size", QSize(690, 510)).toSize();
1051 // Work-around for bug #6034: the window ends up in an undetermined
1052 // state when trying to restore a maximized window when it is
1053 // already maximized.
1054 if (!(windowState() & Qt::WindowMaximized))
1055 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1056 setGeometry(50, 50, 690, 510);
1059 // Make sure layout is correctly oriented.
1060 setLayoutDirection(qApp->layoutDirection());
1062 // Allow the toc and view-source dock widget to be restored if needed.
1064 if ((dialog = findOrBuild("toc", true)))
1065 // see bug 5082. At least setup title and enabled state.
1066 // Visibility will be adjusted by restoreState below.
1067 dialog->prepareView();
1068 if ((dialog = findOrBuild("view-source", true)))
1069 dialog->prepareView();
1070 if ((dialog = findOrBuild("progress", true)))
1071 dialog->prepareView();
1073 if (!restoreState(settings.value("layout").toByteArray(), 0))
1076 // init the toolbars that have not been restored
1077 for (auto const & tb_p : guiApp->toolbars()) {
1078 GuiToolbar * tb = toolbar(tb_p.name);
1079 if (tb && !tb->isRestored())
1080 initToolbar(tb_p.name);
1083 // update lock (all) toolbars positions
1084 updateLockToolbars();
1091 GuiToolbar * GuiView::toolbar(string const & name)
1093 ToolbarMap::iterator it = d.toolbars_.find(name);
1094 if (it != d.toolbars_.end())
1097 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1102 void GuiView::updateLockToolbars()
1104 toolbarsMovable_ = false;
1105 for (ToolbarInfo const & info : guiApp->toolbars()) {
1106 GuiToolbar * tb = toolbar(info.name);
1107 if (tb && tb->isMovable())
1108 toolbarsMovable_ = true;
1110 #if QT_VERSION >= 0x050200
1111 // set unified mac toolbars only when not movable as recommended:
1112 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1113 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1118 void GuiView::constructToolbars()
1120 for (auto const & tb_p : d.toolbars_)
1122 d.toolbars_.clear();
1124 // I don't like doing this here, but the standard toolbar
1125 // destroys this object when it's destroyed itself (vfr)
1126 d.layout_ = new LayoutBox(*this);
1127 d.stack_widget_->addWidget(d.layout_);
1128 d.layout_->move(0,0);
1130 // extracts the toolbars from the backend
1131 for (ToolbarInfo const & inf : guiApp->toolbars())
1132 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1134 DynamicMenuButton::resetIconCache();
1138 void GuiView::initToolbars()
1140 // extracts the toolbars from the backend
1141 for (ToolbarInfo const & inf : guiApp->toolbars())
1142 initToolbar(inf.name);
1146 void GuiView::initToolbar(string const & name)
1148 GuiToolbar * tb = toolbar(name);
1151 int const visibility = guiApp->toolbars().defaultVisibility(name);
1152 bool newline = !(visibility & Toolbars::SAMEROW);
1153 tb->setVisible(false);
1154 tb->setVisibility(visibility);
1156 if (visibility & Toolbars::TOP) {
1158 addToolBarBreak(Qt::TopToolBarArea);
1159 addToolBar(Qt::TopToolBarArea, tb);
1162 if (visibility & Toolbars::BOTTOM) {
1164 addToolBarBreak(Qt::BottomToolBarArea);
1165 addToolBar(Qt::BottomToolBarArea, tb);
1168 if (visibility & Toolbars::LEFT) {
1170 addToolBarBreak(Qt::LeftToolBarArea);
1171 addToolBar(Qt::LeftToolBarArea, tb);
1174 if (visibility & Toolbars::RIGHT) {
1176 addToolBarBreak(Qt::RightToolBarArea);
1177 addToolBar(Qt::RightToolBarArea, tb);
1180 if (visibility & Toolbars::ON)
1181 tb->setVisible(true);
1183 tb->setMovable(true);
1187 TocModels & GuiView::tocModels()
1189 return d.toc_models_;
1193 void GuiView::setFocus()
1195 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1196 QMainWindow::setFocus();
1200 bool GuiView::hasFocus() const
1202 if (currentWorkArea())
1203 return currentWorkArea()->hasFocus();
1204 if (currentMainWorkArea())
1205 return currentMainWorkArea()->hasFocus();
1206 return d.bg_widget_->hasFocus();
1210 void GuiView::focusInEvent(QFocusEvent * e)
1212 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1213 QMainWindow::focusInEvent(e);
1214 // Make sure guiApp points to the correct view.
1215 guiApp->setCurrentView(this);
1216 if (currentWorkArea())
1217 currentWorkArea()->setFocus();
1218 else if (currentMainWorkArea())
1219 currentMainWorkArea()->setFocus();
1221 d.bg_widget_->setFocus();
1225 void GuiView::showEvent(QShowEvent * e)
1227 LYXERR(Debug::GUI, "Passed Geometry "
1228 << size().height() << "x" << size().width()
1229 << "+" << pos().x() << "+" << pos().y());
1231 if (d.splitter_->count() == 0)
1232 // No work area, switch to the background widget.
1236 QMainWindow::showEvent(e);
1240 bool GuiView::closeScheduled()
1247 bool GuiView::prepareAllBuffersForLogout()
1249 Buffer * first = theBufferList().first();
1253 // First, iterate over all buffers and ask the users if unsaved
1254 // changes should be saved.
1255 // We cannot use a for loop as the buffer list cycles.
1258 if (!saveBufferIfNeeded(*b, false))
1260 b = theBufferList().next(b);
1261 } while (b != first);
1263 // Next, save session state
1264 // When a view/window was closed before without quitting LyX, there
1265 // are already entries in the lastOpened list.
1266 theSession().lastOpened().clear();
1273 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1274 ** is responsibility of the container (e.g., dialog)
1276 void GuiView::closeEvent(QCloseEvent * close_event)
1278 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1280 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1281 Alert::warning(_("Exit LyX"),
1282 _("LyX could not be closed because documents are being processed by LyX."));
1283 close_event->setAccepted(false);
1287 // If the user pressed the x (so we didn't call closeView
1288 // programmatically), we want to clear all existing entries.
1290 theSession().lastOpened().clear();
1295 // it can happen that this event arrives without selecting the view,
1296 // e.g. when clicking the close button on a background window.
1298 if (!closeWorkAreaAll()) {
1300 close_event->ignore();
1304 // Make sure that nothing will use this to be closed View.
1305 guiApp->unregisterView(this);
1307 if (isFullScreen()) {
1308 // Switch off fullscreen before closing.
1313 // Make sure the timer time out will not trigger a statusbar update.
1314 d.statusbar_timer_.stop();
1315 d.statusbar_stats_timer_.stop();
1317 // Saving fullscreen requires additional tweaks in the toolbar code.
1318 // It wouldn't also work under linux natively.
1319 if (lyxrc.allow_geometry_session) {
1324 close_event->accept();
1328 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1330 if (event->mimeData()->hasUrls())
1332 /// \todo Ask lyx-devel is this is enough:
1333 /// if (event->mimeData()->hasFormat("text/plain"))
1334 /// event->acceptProposedAction();
1338 void GuiView::dropEvent(QDropEvent * event)
1340 QList<QUrl> files = event->mimeData()->urls();
1341 if (files.isEmpty())
1344 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1345 for (int i = 0; i != files.size(); ++i) {
1346 string const file = os::internal_path(fromqstr(
1347 files.at(i).toLocalFile()));
1351 string const ext = support::getExtension(file);
1352 vector<const Format *> found_formats;
1354 // Find all formats that have the correct extension.
1355 for (const Format * fmt : theConverters().importableFormats())
1356 if (fmt->hasExtension(ext))
1357 found_formats.push_back(fmt);
1360 if (!found_formats.empty()) {
1361 if (found_formats.size() > 1) {
1362 //FIXME: show a dialog to choose the correct importable format
1363 LYXERR(Debug::FILES,
1364 "Multiple importable formats found, selecting first");
1366 string const arg = found_formats[0]->name() + " " + file;
1367 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1370 //FIXME: do we have to explicitly check whether it's a lyx file?
1371 LYXERR(Debug::FILES,
1372 "No formats found, trying to open it as a lyx file");
1373 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1375 // add the functions to the queue
1376 guiApp->addToFuncRequestQueue(cmd);
1379 // now process the collected functions. We perform the events
1380 // asynchronously. This prevents potential problems in case the
1381 // BufferView is closed within an event.
1382 guiApp->processFuncRequestQueueAsync();
1386 void GuiView::message(docstring const & str)
1388 if (ForkedProcess::iAmAChild())
1391 // call is moved to GUI-thread by GuiProgress
1392 d.progress_->appendMessage(toqstr(str));
1396 void GuiView::clearMessageText()
1398 message(docstring());
1402 void GuiView::updateStatusBarMessage(QString const & str)
1404 statusBar()->showMessage(str);
1405 d.statusbar_timer_.stop();
1406 d.statusbar_timer_.start(3000);
1410 void GuiView::clearMessage()
1412 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1413 // the hasFocus function mostly returns false, even if the focus is on
1414 // a workarea in this view.
1418 d.statusbar_timer_.stop();
1421 void GuiView::showStats()
1423 if (!statsEnabled())
1426 d.time_to_update -= d.timer_rate;
1428 BufferView * bv = currentBufferView();
1429 Buffer * buf = bv ? &bv->buffer() : nullptr;
1431 stat_counts_->hide();
1435 Cursor const & cur = bv->cursor();
1437 // we start new selection and need faster update
1438 if (!d.already_in_selection_ && cur.selection())
1439 d.time_to_update = 0;
1441 if (d.time_to_update > 0)
1444 DocIterator from, to;
1445 if (cur.selection()) {
1446 from = cur.selectionBegin();
1447 to = cur.selectionEnd();
1448 d.already_in_selection_ = true;
1450 from = doc_iterator_begin(buf);
1451 to = doc_iterator_end(buf);
1452 d.already_in_selection_ = false;
1455 buf->updateStatistics(from, to);
1458 if (word_count_enabled_) {
1459 int const words = buf->wordCount();
1461 stats << toqstr(bformat(_("%1$d Word"), words));
1463 stats << toqstr(bformat(_("%1$d Words"), words));
1465 int const chars_with_blanks = buf->charCount(true);
1466 if (char_count_enabled_) {
1467 if (chars_with_blanks == 1)
1468 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1470 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1472 if (char_nb_count_enabled_) {
1473 int const chars = buf->charCount(false);
1475 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1477 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1479 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1480 stat_counts_->show();
1482 d.time_to_update = d.default_stats_rate;
1483 // fast updates for small selections
1484 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1485 d.time_to_update = d.timer_rate;
1489 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1491 if (wa != d.current_work_area_
1492 || wa->bufferView().buffer().isInternal())
1494 Buffer const & buf = wa->bufferView().buffer();
1495 // Set the windows title
1496 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1497 if (buf.notifiesExternalModification()) {
1498 title = bformat(_("%1$s (modified externally)"), title);
1499 // If the external modification status has changed, then maybe the status of
1500 // buffer-save has changed too.
1504 title += from_ascii(" - LyX");
1506 setWindowTitle(toqstr(title));
1507 // Sets the path for the window: this is used by OSX to
1508 // allow a context click on the title bar showing a menu
1509 // with the path up to the file
1510 setWindowFilePath(toqstr(buf.absFileName()));
1511 // Tell Qt whether the current document is changed
1512 setWindowModified(!buf.isClean());
1514 if (buf.params().shell_escape)
1515 shell_escape_->show();
1517 shell_escape_->hide();
1519 if (buf.hasReadonlyFlag())
1524 if (buf.lyxvc().inUse()) {
1525 version_control_->show();
1526 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1528 version_control_->hide();
1532 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1534 if (d.current_work_area_)
1535 // disconnect the current work area from all slots
1536 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1538 disconnectBufferView();
1539 connectBufferView(wa->bufferView());
1540 connectBuffer(wa->bufferView().buffer());
1541 d.current_work_area_ = wa;
1542 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1543 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1544 QObject::connect(wa, SIGNAL(busy(bool)),
1545 this, SLOT(setBusy(bool)));
1546 // connection of a signal to a signal
1547 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1548 this, SIGNAL(bufferViewChanged()));
1549 Q_EMIT updateWindowTitle(wa);
1550 Q_EMIT bufferViewChanged();
1554 void GuiView::onBufferViewChanged()
1557 // Buffer-dependent dialogs must be updated. This is done here because
1558 // some dialogs require buffer()->text.
1560 zoom_slider_->setEnabled(currentBufferView());
1561 zoom_value_->setEnabled(currentBufferView());
1562 zoom_in_->setEnabled(currentBufferView());
1563 zoom_out_->setEnabled(currentBufferView());
1567 void GuiView::on_lastWorkAreaRemoved()
1570 // We already are in a close event. Nothing more to do.
1573 if (d.splitter_->count() > 1)
1574 // We have a splitter so don't close anything.
1577 // Reset and updates the dialogs.
1578 Q_EMIT bufferViewChanged();
1583 if (lyxrc.open_buffers_in_tabs)
1584 // Nothing more to do, the window should stay open.
1587 if (guiApp->viewIds().size() > 1) {
1593 // On Mac we also close the last window because the application stay
1594 // resident in memory. On other platforms we don't close the last
1595 // window because this would quit the application.
1601 void GuiView::updateStatusBar()
1603 // let the user see the explicit message
1604 if (d.statusbar_timer_.isActive())
1611 void GuiView::showMessage()
1615 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1616 if (msg.isEmpty()) {
1617 BufferView const * bv = currentBufferView();
1619 msg = toqstr(bv->cursor().currentState(devel_mode_));
1621 msg = qt_("Welcome to LyX!");
1623 statusBar()->showMessage(msg);
1627 bool GuiView::statsEnabled() const
1629 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1633 bool GuiView::event(QEvent * e)
1637 // Useful debug code:
1638 //case QEvent::ActivationChange:
1639 //case QEvent::WindowDeactivate:
1640 //case QEvent::Paint:
1641 //case QEvent::Enter:
1642 //case QEvent::Leave:
1643 //case QEvent::HoverEnter:
1644 //case QEvent::HoverLeave:
1645 //case QEvent::HoverMove:
1646 //case QEvent::StatusTip:
1647 //case QEvent::DragEnter:
1648 //case QEvent::DragLeave:
1649 //case QEvent::Drop:
1652 case QEvent::WindowStateChange: {
1653 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1654 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1655 bool result = QMainWindow::event(e);
1656 bool nfstate = (windowState() & Qt::WindowFullScreen);
1657 if (!ofstate && nfstate) {
1658 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1659 // switch to full-screen state
1660 if (lyxrc.full_screen_statusbar)
1661 statusBar()->hide();
1662 if (lyxrc.full_screen_menubar)
1664 if (lyxrc.full_screen_toolbars) {
1665 for (auto const & tb_p : d.toolbars_)
1666 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1667 tb_p.second->hide();
1669 for (int i = 0; i != d.splitter_->count(); ++i)
1670 d.tabWorkArea(i)->setFullScreen(true);
1671 #if QT_VERSION > 0x050903
1672 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1673 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1675 setContentsMargins(-2, -2, -2, -2);
1677 hideDialogs("prefs", nullptr);
1678 } else if (ofstate && !nfstate) {
1679 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1680 // switch back from full-screen state
1681 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1682 statusBar()->show();
1683 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1685 if (lyxrc.full_screen_toolbars) {
1686 for (auto const & tb_p : d.toolbars_)
1687 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1688 tb_p.second->show();
1691 for (int i = 0; i != d.splitter_->count(); ++i)
1692 d.tabWorkArea(i)->setFullScreen(false);
1693 #if QT_VERSION > 0x050903
1694 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1696 setContentsMargins(0, 0, 0, 0);
1701 case QEvent::WindowActivate: {
1702 GuiView * old_view = guiApp->currentView();
1703 if (this == old_view) {
1705 return QMainWindow::event(e);
1707 if (old_view && old_view->currentBufferView()) {
1708 // save current selection to the selection buffer to allow
1709 // middle-button paste in this window.
1710 cap::saveSelection(old_view->currentBufferView()->cursor());
1712 guiApp->setCurrentView(this);
1713 if (d.current_work_area_)
1714 on_currentWorkAreaChanged(d.current_work_area_);
1718 return QMainWindow::event(e);
1721 case QEvent::ShortcutOverride: {
1723 if (isFullScreen() && menuBar()->isHidden()) {
1724 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1725 // FIXME: we should also try to detect special LyX shortcut such as
1726 // Alt-P and Alt-M. Right now there is a hack in
1727 // GuiWorkArea::processKeySym() that hides again the menubar for
1729 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1731 return QMainWindow::event(e);
1734 return QMainWindow::event(e);
1737 case QEvent::ApplicationPaletteChange: {
1738 // runtime switch from/to dark mode
1740 return QMainWindow::event(e);
1743 case QEvent::Gesture: {
1744 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1745 QGesture *gp = ge->gesture(Qt::PinchGesture);
1747 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1748 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1749 qreal totalScaleFactor = pinch->totalScaleFactor();
1750 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1751 if (pinch->state() == Qt::GestureStarted) {
1752 initialZoom_ = lyxrc.currentZoom;
1753 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1755 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1756 qreal factor = initialZoom_ * totalScaleFactor;
1757 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1758 zoomValueChanged(factor);
1761 return QMainWindow::event(e);
1765 return QMainWindow::event(e);
1769 void GuiView::resetWindowTitle()
1771 setWindowTitle(qt_("LyX"));
1774 bool GuiView::focusNextPrevChild(bool /*next*/)
1781 bool GuiView::busy() const
1787 void GuiView::setBusy(bool busy)
1789 bool const busy_before = busy_ > 0;
1790 busy ? ++busy_ : --busy_;
1791 if ((busy_ > 0) == busy_before)
1792 // busy state didn't change
1796 QApplication::setOverrideCursor(Qt::WaitCursor);
1799 QApplication::restoreOverrideCursor();
1804 void GuiView::resetCommandExecute()
1806 command_execute_ = false;
1811 double GuiView::pixelRatio() const
1813 #if QT_VERSION >= 0x050000
1814 return qt_scale_factor * devicePixelRatio();
1821 GuiWorkArea * GuiView::workArea(int index)
1823 if (TabWorkArea * twa = d.currentTabWorkArea())
1824 if (index < twa->count())
1825 return twa->workArea(index);
1830 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1832 if (currentWorkArea()
1833 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1834 return currentWorkArea();
1835 if (TabWorkArea * twa = d.currentTabWorkArea())
1836 return twa->workArea(buffer);
1841 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1843 // Automatically create a TabWorkArea if there are none yet.
1844 TabWorkArea * tab_widget = d.splitter_->count()
1845 ? d.currentTabWorkArea() : addTabWorkArea();
1846 return tab_widget->addWorkArea(buffer, *this);
1850 TabWorkArea * GuiView::addTabWorkArea()
1852 TabWorkArea * twa = new TabWorkArea;
1853 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1854 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1855 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1856 this, SLOT(on_lastWorkAreaRemoved()));
1858 d.splitter_->addWidget(twa);
1859 d.stack_widget_->setCurrentWidget(d.splitter_);
1864 GuiWorkArea const * GuiView::currentWorkArea() const
1866 return d.current_work_area_;
1870 GuiWorkArea * GuiView::currentWorkArea()
1872 return d.current_work_area_;
1876 GuiWorkArea const * GuiView::currentMainWorkArea() const
1878 if (!d.currentTabWorkArea())
1880 return d.currentTabWorkArea()->currentWorkArea();
1884 GuiWorkArea * GuiView::currentMainWorkArea()
1886 if (!d.currentTabWorkArea())
1888 return d.currentTabWorkArea()->currentWorkArea();
1892 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1894 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1896 d.current_work_area_ = nullptr;
1898 Q_EMIT bufferViewChanged();
1902 // FIXME: I've no clue why this is here and why it accesses
1903 // theGuiApp()->currentView, which might be 0 (bug 6464).
1904 // See also 27525 (vfr).
1905 if (theGuiApp()->currentView() == this
1906 && theGuiApp()->currentView()->currentWorkArea() == wa)
1909 if (currentBufferView())
1910 cap::saveSelection(currentBufferView()->cursor());
1912 theGuiApp()->setCurrentView(this);
1913 d.current_work_area_ = wa;
1915 // We need to reset this now, because it will need to be
1916 // right if the tabWorkArea gets reset in the for loop. We
1917 // will change it back if we aren't in that case.
1918 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1919 d.current_main_work_area_ = wa;
1921 for (int i = 0; i != d.splitter_->count(); ++i) {
1922 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1923 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1924 << ", Current main wa: " << currentMainWorkArea());
1929 d.current_main_work_area_ = old_cmwa;
1931 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1932 on_currentWorkAreaChanged(wa);
1933 BufferView & bv = wa->bufferView();
1934 bv.cursor().fixIfBroken();
1936 wa->setUpdatesEnabled(true);
1937 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1941 void GuiView::removeWorkArea(GuiWorkArea * wa)
1943 LASSERT(wa, return);
1944 if (wa == d.current_work_area_) {
1946 disconnectBufferView();
1947 d.current_work_area_ = nullptr;
1948 d.current_main_work_area_ = nullptr;
1951 bool found_twa = false;
1952 for (int i = 0; i != d.splitter_->count(); ++i) {
1953 TabWorkArea * twa = d.tabWorkArea(i);
1954 if (twa->removeWorkArea(wa)) {
1955 // Found in this tab group, and deleted the GuiWorkArea.
1957 if (twa->count() != 0) {
1958 if (d.current_work_area_ == nullptr)
1959 // This means that we are closing the current GuiWorkArea, so
1960 // switch to the next GuiWorkArea in the found TabWorkArea.
1961 setCurrentWorkArea(twa->currentWorkArea());
1963 // No more WorkAreas in this tab group, so delete it.
1970 // It is not a tabbed work area (i.e., the search work area), so it
1971 // should be deleted by other means.
1972 LASSERT(found_twa, return);
1974 if (d.current_work_area_ == nullptr) {
1975 if (d.splitter_->count() != 0) {
1976 TabWorkArea * twa = d.currentTabWorkArea();
1977 setCurrentWorkArea(twa->currentWorkArea());
1979 // No more work areas, switch to the background widget.
1980 setCurrentWorkArea(nullptr);
1986 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1988 for (int i = 0; i < d.splitter_->count(); ++i)
1989 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1992 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1993 return fr->isVisible() && fr->hasWorkArea(wa);
1997 LayoutBox * GuiView::getLayoutDialog() const
2003 void GuiView::updateLayoutList()
2006 d.layout_->updateContents(false);
2010 void GuiView::updateToolbars()
2012 if (d.current_work_area_) {
2014 if (d.current_work_area_->bufferView().cursor().inMathed()
2015 && !d.current_work_area_->bufferView().cursor().inRegexped())
2016 context |= Toolbars::MATH;
2017 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2018 context |= Toolbars::TABLE;
2019 if (currentBufferView()->buffer().areChangesPresent()
2020 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2021 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2022 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2023 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2024 context |= Toolbars::REVIEW;
2025 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2026 context |= Toolbars::MATHMACROTEMPLATE;
2027 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2028 context |= Toolbars::IPA;
2029 if (command_execute_)
2030 context |= Toolbars::MINIBUFFER;
2031 if (minibuffer_focus_) {
2032 context |= Toolbars::MINIBUFFER_FOCUS;
2033 minibuffer_focus_ = false;
2036 for (auto const & tb_p : d.toolbars_)
2037 tb_p.second->update(context);
2039 for (auto const & tb_p : d.toolbars_)
2040 tb_p.second->update();
2044 void GuiView::refillToolbars()
2046 DynamicMenuButton::resetIconCache();
2047 for (auto const & tb_p : d.toolbars_)
2048 tb_p.second->refill();
2052 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2054 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2055 LASSERT(newBuffer, return);
2057 GuiWorkArea * wa = workArea(*newBuffer);
2058 if (wa == nullptr) {
2060 newBuffer->masterBuffer()->updateBuffer();
2062 wa = addWorkArea(*newBuffer);
2063 // scroll to the position when the BufferView was last closed
2064 if (lyxrc.use_lastfilepos) {
2065 LastFilePosSection::FilePos filepos =
2066 theSession().lastFilePos().load(newBuffer->fileName());
2067 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2070 //Disconnect the old buffer...there's no new one.
2073 connectBuffer(*newBuffer);
2074 connectBufferView(wa->bufferView());
2076 setCurrentWorkArea(wa);
2080 void GuiView::connectBuffer(Buffer & buf)
2082 buf.setGuiDelegate(this);
2086 void GuiView::disconnectBuffer()
2088 if (d.current_work_area_)
2089 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2093 void GuiView::connectBufferView(BufferView & bv)
2095 bv.setGuiDelegate(this);
2099 void GuiView::disconnectBufferView()
2101 if (d.current_work_area_)
2102 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2106 void GuiView::errors(string const & error_type, bool from_master)
2108 BufferView const * const bv = currentBufferView();
2112 ErrorList const & el = from_master ?
2113 bv->buffer().masterBuffer()->errorList(error_type) :
2114 bv->buffer().errorList(error_type);
2119 string err = error_type;
2121 err = "from_master|" + error_type;
2122 showDialog("errorlist", err);
2126 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2128 d.toc_models_.updateItem(toqstr(type), dit);
2132 void GuiView::structureChanged()
2134 // This is called from the Buffer, which has no way to ensure that cursors
2135 // in BufferView remain valid.
2136 if (documentBufferView())
2137 documentBufferView()->cursor().sanitize();
2138 // FIXME: This is slightly expensive, though less than the tocBackend update
2139 // (#9880). This also resets the view in the Toc Widget (#6675).
2140 d.toc_models_.reset(documentBufferView());
2141 // Navigator needs more than a simple update in this case. It needs to be
2143 updateDialog("toc", "");
2147 void GuiView::updateDialog(string const & name, string const & sdata)
2149 if (!isDialogVisible(name))
2152 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2153 if (it == d.dialogs_.end())
2156 Dialog * const dialog = it->second.get();
2157 if (dialog->isVisibleView())
2158 dialog->initialiseParams(sdata);
2162 BufferView * GuiView::documentBufferView()
2164 return currentMainWorkArea()
2165 ? ¤tMainWorkArea()->bufferView()
2170 BufferView const * GuiView::documentBufferView() const
2172 return currentMainWorkArea()
2173 ? ¤tMainWorkArea()->bufferView()
2178 BufferView * GuiView::currentBufferView()
2180 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2184 BufferView const * GuiView::currentBufferView() const
2186 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2190 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2191 Buffer const * orig, Buffer * clone)
2193 bool const success = clone->autoSave();
2195 busyBuffers.remove(orig);
2197 ? _("Automatic save done.")
2198 : _("Automatic save failed!");
2202 void GuiView::autoSave()
2204 LYXERR(Debug::INFO, "Running autoSave()");
2206 Buffer * buffer = documentBufferView()
2207 ? &documentBufferView()->buffer() : nullptr;
2209 resetAutosaveTimers();
2213 GuiViewPrivate::busyBuffers.insert(buffer);
2214 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2215 buffer, buffer->cloneBufferOnly());
2216 d.autosave_watcher_.setFuture(f);
2217 resetAutosaveTimers();
2221 void GuiView::resetAutosaveTimers()
2224 d.autosave_timeout_.restart();
2230 double zoomRatio(FuncRequest const & cmd, double const zr)
2232 if (cmd.argument().empty()) {
2233 if (cmd.action() == LFUN_BUFFER_ZOOM)
2235 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2237 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2240 if (cmd.action() == LFUN_BUFFER_ZOOM)
2241 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2242 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2243 return zr + convert<int>(cmd.argument()) / 100.0;
2244 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2245 return zr - convert<int>(cmd.argument()) / 100.0;
2252 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2255 Buffer * buf = currentBufferView()
2256 ? ¤tBufferView()->buffer() : nullptr;
2257 Buffer * doc_buffer = documentBufferView()
2258 ? &(documentBufferView()->buffer()) : nullptr;
2261 /* In LyX/Mac, when a dialog is open, the menus of the
2262 application can still be accessed without giving focus to
2263 the main window. In this case, we want to disable the menu
2264 entries that are buffer-related.
2265 This code must not be used on Linux and Windows, since it
2266 would disable buffer-related entries when hovering over the
2267 menu (see bug #9574).
2269 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2275 // Check whether we need a buffer
2276 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2277 // no, exit directly
2278 flag.message(from_utf8(N_("Command not allowed with"
2279 "out any document open")));
2280 flag.setEnabled(false);
2284 if (cmd.origin() == FuncRequest::TOC) {
2285 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2286 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2287 flag.setEnabled(false);
2291 switch(cmd.action()) {
2292 case LFUN_BUFFER_IMPORT:
2295 case LFUN_MASTER_BUFFER_EXPORT:
2297 && (doc_buffer->parent() != nullptr
2298 || doc_buffer->hasChildren())
2299 && !d.processing_thread_watcher_.isRunning()
2300 // this launches a dialog, which would be in the wrong Buffer
2301 && !(::lyx::operator==(cmd.argument(), "custom"));
2304 case LFUN_MASTER_BUFFER_UPDATE:
2305 case LFUN_MASTER_BUFFER_VIEW:
2307 && (doc_buffer->parent() != nullptr
2308 || doc_buffer->hasChildren())
2309 && !d.processing_thread_watcher_.isRunning();
2312 case LFUN_BUFFER_UPDATE:
2313 case LFUN_BUFFER_VIEW: {
2314 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2318 string format = to_utf8(cmd.argument());
2319 if (cmd.argument().empty())
2320 format = doc_buffer->params().getDefaultOutputFormat();
2321 enable = doc_buffer->params().isExportable(format, true);
2325 case LFUN_BUFFER_RELOAD:
2326 enable = doc_buffer && !doc_buffer->isUnnamed()
2327 && doc_buffer->fileName().exists()
2328 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2331 case LFUN_BUFFER_RESET_EXPORT:
2332 enable = doc_buffer != nullptr;
2335 case LFUN_BUFFER_CHILD_OPEN:
2336 enable = doc_buffer != nullptr;
2339 case LFUN_MASTER_BUFFER_FORALL: {
2340 if (doc_buffer == nullptr) {
2341 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2345 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2346 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2347 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2352 for (Buffer * buf : doc_buffer->allRelatives()) {
2353 GuiWorkArea * wa = workArea(*buf);
2356 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2357 enable = flag.enabled();
2364 case LFUN_BUFFER_WRITE:
2365 enable = doc_buffer && (doc_buffer->isUnnamed()
2366 || (!doc_buffer->isClean()
2367 || cmd.argument() == "force"));
2370 //FIXME: This LFUN should be moved to GuiApplication.
2371 case LFUN_BUFFER_WRITE_ALL: {
2372 // We enable the command only if there are some modified buffers
2373 Buffer * first = theBufferList().first();
2378 // We cannot use a for loop as the buffer list is a cycle.
2380 if (!b->isClean()) {
2384 b = theBufferList().next(b);
2385 } while (b != first);
2389 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2390 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2393 case LFUN_BUFFER_EXPORT: {
2394 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2398 return doc_buffer->getStatus(cmd, flag);
2401 case LFUN_BUFFER_EXPORT_AS:
2402 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2407 case LFUN_BUFFER_WRITE_AS:
2408 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2409 enable = doc_buffer != nullptr;
2412 case LFUN_EXPORT_CANCEL:
2413 enable = d.processing_thread_watcher_.isRunning();
2416 case LFUN_BUFFER_CLOSE:
2417 case LFUN_VIEW_CLOSE:
2418 enable = doc_buffer != nullptr;
2421 case LFUN_BUFFER_CLOSE_ALL:
2422 enable = theBufferList().last() != theBufferList().first();
2425 case LFUN_BUFFER_CHKTEX: {
2426 // hide if we have no checktex command
2427 if (lyxrc.chktex_command.empty()) {
2428 flag.setUnknown(true);
2432 if (!doc_buffer || !doc_buffer->params().isLatex()
2433 || d.processing_thread_watcher_.isRunning()) {
2434 // grey out, don't hide
2442 case LFUN_VIEW_SPLIT:
2443 if (cmd.getArg(0) == "vertical")
2444 enable = doc_buffer && (d.splitter_->count() == 1 ||
2445 d.splitter_->orientation() == Qt::Vertical);
2447 enable = doc_buffer && (d.splitter_->count() == 1 ||
2448 d.splitter_->orientation() == Qt::Horizontal);
2451 case LFUN_TAB_GROUP_CLOSE:
2452 enable = d.tabWorkAreaCount() > 1;
2455 case LFUN_DEVEL_MODE_TOGGLE:
2456 flag.setOnOff(devel_mode_);
2459 case LFUN_TOOLBAR_SET: {
2460 string const name = cmd.getArg(0);
2461 string const state = cmd.getArg(1);
2462 if (name.empty() || state.empty()) {
2464 docstring const msg =
2465 _("Function toolbar-set requires two arguments!");
2469 if (state != "on" && state != "off" && state != "auto") {
2471 docstring const msg =
2472 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2477 if (GuiToolbar * t = toolbar(name)) {
2478 bool const autovis = t->visibility() & Toolbars::AUTO;
2480 flag.setOnOff(t->isVisible() && !autovis);
2481 else if (state == "off")
2482 flag.setOnOff(!t->isVisible() && !autovis);
2483 else if (state == "auto")
2484 flag.setOnOff(autovis);
2487 docstring const msg =
2488 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2494 case LFUN_TOOLBAR_TOGGLE: {
2495 string const name = cmd.getArg(0);
2496 if (GuiToolbar * t = toolbar(name))
2497 flag.setOnOff(t->isVisible());
2500 docstring const msg =
2501 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2507 case LFUN_TOOLBAR_MOVABLE: {
2508 string const name = cmd.getArg(0);
2509 // use negation since locked == !movable
2511 // toolbar name * locks all toolbars
2512 flag.setOnOff(!toolbarsMovable_);
2513 else if (GuiToolbar * t = toolbar(name))
2514 flag.setOnOff(!(t->isMovable()));
2517 docstring const msg =
2518 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2524 case LFUN_ICON_SIZE:
2525 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2528 case LFUN_DROP_LAYOUTS_CHOICE:
2529 enable = buf != nullptr;
2532 case LFUN_UI_TOGGLE:
2533 if (cmd.argument() == "zoomlevel") {
2534 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2535 } else if (cmd.argument() == "zoomslider") {
2536 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2537 } else if (cmd.argument() == "statistics-w") {
2538 flag.setOnOff(word_count_enabled_);
2539 } else if (cmd.argument() == "statistics-cb") {
2540 flag.setOnOff(char_count_enabled_);
2541 } else if (cmd.argument() == "statistics-c") {
2542 flag.setOnOff(char_nb_count_enabled_);
2544 flag.setOnOff(isFullScreen());
2547 case LFUN_DIALOG_DISCONNECT_INSET:
2550 case LFUN_DIALOG_HIDE:
2551 // FIXME: should we check if the dialog is shown?
2554 case LFUN_DIALOG_TOGGLE:
2555 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2558 case LFUN_DIALOG_SHOW: {
2559 string const name = cmd.getArg(0);
2561 enable = name == "aboutlyx"
2562 || name == "file" //FIXME: should be removed.
2563 || name == "lyxfiles"
2565 || name == "texinfo"
2566 || name == "progress"
2567 || name == "compare";
2568 else if (name == "character" || name == "symbols"
2569 || name == "mathdelimiter" || name == "mathmatrix") {
2570 if (!buf || buf->isReadonly())
2573 Cursor const & cur = currentBufferView()->cursor();
2574 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2577 else if (name == "latexlog")
2578 enable = FileName(doc_buffer->logName()).isReadableFile();
2579 else if (name == "spellchecker")
2580 enable = theSpellChecker()
2581 && !doc_buffer->text().empty();
2582 else if (name == "vclog")
2583 enable = doc_buffer->lyxvc().inUse();
2587 case LFUN_DIALOG_UPDATE: {
2588 string const name = cmd.getArg(0);
2590 enable = name == "prefs";
2594 case LFUN_COMMAND_EXECUTE:
2596 case LFUN_MENU_OPEN:
2597 // Nothing to check.
2600 case LFUN_COMPLETION_INLINE:
2601 if (!d.current_work_area_
2602 || !d.current_work_area_->completer().inlinePossible(
2603 currentBufferView()->cursor()))
2607 case LFUN_COMPLETION_POPUP:
2608 if (!d.current_work_area_
2609 || !d.current_work_area_->completer().popupPossible(
2610 currentBufferView()->cursor()))
2615 if (!d.current_work_area_
2616 || !d.current_work_area_->completer().inlinePossible(
2617 currentBufferView()->cursor()))
2621 case LFUN_COMPLETION_ACCEPT:
2622 if (!d.current_work_area_
2623 || (!d.current_work_area_->completer().popupVisible()
2624 && !d.current_work_area_->completer().inlineVisible()
2625 && !d.current_work_area_->completer().completionAvailable()))
2629 case LFUN_COMPLETION_CANCEL:
2630 if (!d.current_work_area_
2631 || (!d.current_work_area_->completer().popupVisible()
2632 && !d.current_work_area_->completer().inlineVisible()))
2636 case LFUN_BUFFER_ZOOM_OUT:
2637 case LFUN_BUFFER_ZOOM_IN:
2638 case LFUN_BUFFER_ZOOM: {
2639 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2640 if (zoom < zoom_min_) {
2641 docstring const msg =
2642 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2645 } else if (zoom > zoom_max_) {
2646 docstring const msg =
2647 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2651 enable = doc_buffer;
2656 case LFUN_BUFFER_MOVE_NEXT:
2657 case LFUN_BUFFER_MOVE_PREVIOUS:
2658 // we do not cycle when moving
2659 case LFUN_BUFFER_NEXT:
2660 case LFUN_BUFFER_PREVIOUS:
2661 // because we cycle, it doesn't matter whether on first or last
2662 enable = (d.currentTabWorkArea()->count() > 1);
2664 case LFUN_BUFFER_SWITCH:
2665 // toggle on the current buffer, but do not toggle off
2666 // the other ones (is that a good idea?)
2668 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2669 flag.setOnOff(true);
2672 case LFUN_VC_REGISTER:
2673 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2675 case LFUN_VC_RENAME:
2676 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2679 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2681 case LFUN_VC_CHECK_IN:
2682 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2684 case LFUN_VC_CHECK_OUT:
2685 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2687 case LFUN_VC_LOCKING_TOGGLE:
2688 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2689 && doc_buffer->lyxvc().lockingToggleEnabled();
2690 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2692 case LFUN_VC_REVERT:
2693 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2694 && !doc_buffer->hasReadonlyFlag();
2696 case LFUN_VC_UNDO_LAST:
2697 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2699 case LFUN_VC_REPO_UPDATE:
2700 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2702 case LFUN_VC_COMMAND: {
2703 if (cmd.argument().empty())
2705 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2709 case LFUN_VC_COMPARE:
2710 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2713 case LFUN_SERVER_GOTO_FILE_ROW:
2714 case LFUN_LYX_ACTIVATE:
2715 case LFUN_WINDOW_RAISE:
2717 case LFUN_FORWARD_SEARCH:
2718 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2719 doc_buffer && doc_buffer->isSyncTeXenabled();
2722 case LFUN_FILE_INSERT_PLAINTEXT:
2723 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2724 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2727 case LFUN_SPELLING_CONTINUOUSLY:
2728 flag.setOnOff(lyxrc.spellcheck_continuously);
2731 case LFUN_CITATION_OPEN:
2740 flag.setEnabled(false);
2746 static FileName selectTemplateFile()
2748 FileDialog dlg(qt_("Select template file"));
2749 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2750 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2752 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2753 QStringList(qt_("LyX Documents (*.lyx)")));
2755 if (result.first == FileDialog::Later)
2757 if (result.second.isEmpty())
2759 return FileName(fromqstr(result.second));
2763 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2767 Buffer * newBuffer = nullptr;
2769 newBuffer = checkAndLoadLyXFile(filename);
2770 } catch (ExceptionMessage const &) {
2777 message(_("Document not loaded."));
2781 setBuffer(newBuffer);
2782 newBuffer->errors("Parse");
2785 theSession().lastFiles().add(filename);
2786 theSession().writeFile();
2793 void GuiView::openDocument(string const & fname)
2795 string initpath = lyxrc.document_path;
2797 if (documentBufferView()) {
2798 string const trypath = documentBufferView()->buffer().filePath();
2799 // If directory is writeable, use this as default.
2800 if (FileName(trypath).isDirWritable())
2806 if (fname.empty()) {
2807 FileDialog dlg(qt_("Select document to open"));
2808 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2809 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2811 QStringList const filter({
2812 qt_("LyX Documents (*.lyx)"),
2813 qt_("LyX Document Backups (*.lyx~)"),
2814 qt_("All Files (*.*)")
2816 FileDialog::Result result =
2817 dlg.open(toqstr(initpath), filter);
2819 if (result.first == FileDialog::Later)
2822 filename = fromqstr(result.second);
2824 // check selected filename
2825 if (filename.empty()) {
2826 message(_("Canceled."));
2832 // get absolute path of file and add ".lyx" to the filename if
2834 FileName const fullname =
2835 fileSearch(string(), filename, "lyx", support::may_not_exist);
2836 if (!fullname.empty())
2837 filename = fullname.absFileName();
2839 if (!fullname.onlyPath().isDirectory()) {
2840 Alert::warning(_("Invalid filename"),
2841 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2842 from_utf8(fullname.absFileName())));
2846 // if the file doesn't exist and isn't already open (bug 6645),
2847 // let the user create one
2848 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2849 !LyXVC::file_not_found_hook(fullname)) {
2850 // the user specifically chose this name. Believe him.
2851 Buffer * const b = newFile(filename, string(), true);
2857 docstring const disp_fn = makeDisplayPath(filename);
2858 message(bformat(_("Opening document %1$s..."), disp_fn));
2861 Buffer * buf = loadDocument(fullname);
2863 str2 = bformat(_("Document %1$s opened."), disp_fn);
2864 if (buf->lyxvc().inUse())
2865 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2866 " " + _("Version control detected.");
2868 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2873 // FIXME: clean that
2874 static bool import(GuiView * lv, FileName const & filename,
2875 string const & format, ErrorList & errorList)
2877 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2879 string loader_format;
2880 vector<string> loaders = theConverters().loaders();
2881 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2882 for (string const & loader : loaders) {
2883 if (!theConverters().isReachable(format, loader))
2886 string const tofile =
2887 support::changeExtension(filename.absFileName(),
2888 theFormats().extension(loader));
2889 if (theConverters().convert(nullptr, filename, FileName(tofile),
2890 filename, format, loader, errorList) != Converters::SUCCESS)
2892 loader_format = loader;
2895 if (loader_format.empty()) {
2896 frontend::Alert::error(_("Couldn't import file"),
2897 bformat(_("No information for importing the format %1$s."),
2898 translateIfPossible(theFormats().prettyName(format))));
2902 loader_format = format;
2904 if (loader_format == "lyx") {
2905 Buffer * buf = lv->loadDocument(lyxfile);
2909 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2913 bool as_paragraphs = loader_format == "textparagraph";
2914 string filename2 = (loader_format == format) ? filename.absFileName()
2915 : support::changeExtension(filename.absFileName(),
2916 theFormats().extension(loader_format));
2917 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2919 guiApp->setCurrentView(lv);
2920 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2927 void GuiView::importDocument(string const & argument)
2930 string filename = split(argument, format, ' ');
2932 LYXERR(Debug::INFO, format << " file: " << filename);
2934 // need user interaction
2935 if (filename.empty()) {
2936 string initpath = lyxrc.document_path;
2937 if (documentBufferView()) {
2938 string const trypath = documentBufferView()->buffer().filePath();
2939 // If directory is writeable, use this as default.
2940 if (FileName(trypath).isDirWritable())
2944 docstring const text = bformat(_("Select %1$s file to import"),
2945 translateIfPossible(theFormats().prettyName(format)));
2947 FileDialog dlg(toqstr(text));
2948 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2949 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2951 docstring filter = translateIfPossible(theFormats().prettyName(format));
2954 filter += from_utf8(theFormats().extensions(format));
2957 FileDialog::Result result =
2958 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2960 if (result.first == FileDialog::Later)
2963 filename = fromqstr(result.second);
2965 // check selected filename
2966 if (filename.empty())
2967 message(_("Canceled."));
2970 if (filename.empty())
2973 // get absolute path of file
2974 FileName const fullname(support::makeAbsPath(filename));
2976 // Can happen if the user entered a path into the dialog
2978 if (fullname.onlyFileName().empty()) {
2979 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2980 "Aborting import."),
2981 from_utf8(fullname.absFileName()));
2982 frontend::Alert::error(_("File name error"), msg);
2983 message(_("Canceled."));
2988 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2990 // Check if the document already is open
2991 Buffer * buf = theBufferList().getBuffer(lyxfile);
2994 if (!closeBuffer()) {
2995 message(_("Canceled."));
3000 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3002 // if the file exists already, and we didn't do
3003 // -i lyx thefile.lyx, warn
3004 if (lyxfile.exists() && fullname != lyxfile) {
3006 docstring text = bformat(_("The document %1$s already exists.\n\n"
3007 "Do you want to overwrite that document?"), displaypath);
3008 int const ret = Alert::prompt(_("Overwrite document?"),
3009 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3012 message(_("Canceled."));
3017 message(bformat(_("Importing %1$s..."), displaypath));
3018 ErrorList errorList;
3019 if (import(this, fullname, format, errorList))
3020 message(_("imported."));
3022 message(_("file not imported!"));
3024 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3028 void GuiView::newDocument(string const & filename, string templatefile,
3031 FileName initpath(lyxrc.document_path);
3032 if (documentBufferView()) {
3033 FileName const trypath(documentBufferView()->buffer().filePath());
3034 // If directory is writeable, use this as default.
3035 if (trypath.isDirWritable())
3039 if (from_template) {
3040 if (templatefile.empty())
3041 templatefile = selectTemplateFile().absFileName();
3042 if (templatefile.empty())
3047 if (filename.empty())
3048 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3050 b = newFile(filename, templatefile, true);
3055 // If no new document could be created, it is unsure
3056 // whether there is a valid BufferView.
3057 if (currentBufferView())
3058 // Ensure the cursor is correctly positioned on screen.
3059 currentBufferView()->showCursor();
3063 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3065 BufferView * bv = documentBufferView();
3070 FileName filename(to_utf8(fname));
3071 if (filename.empty()) {
3072 // Launch a file browser
3074 string initpath = lyxrc.document_path;
3075 string const trypath = bv->buffer().filePath();
3076 // If directory is writeable, use this as default.
3077 if (FileName(trypath).isDirWritable())
3081 FileDialog dlg(qt_("Select LyX document to insert"));
3082 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3083 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3085 FileDialog::Result result = dlg.open(toqstr(initpath),
3086 QStringList(qt_("LyX Documents (*.lyx)")));
3088 if (result.first == FileDialog::Later)
3092 filename.set(fromqstr(result.second));
3094 // check selected filename
3095 if (filename.empty()) {
3096 // emit message signal.
3097 message(_("Canceled."));
3102 bv->insertLyXFile(filename, ignorelang);
3103 bv->buffer().errors("Parse");
3108 string const GuiView::getTemplatesPath(Buffer & b)
3110 // We start off with the user's templates path
3111 string result = addPath(package().user_support().absFileName(), "templates");
3112 // Check for the document language
3113 string const langcode = b.params().language->code();
3114 string const shortcode = langcode.substr(0, 2);
3115 if (!langcode.empty() && shortcode != "en") {
3116 string subpath = addPath(result, shortcode);
3117 string subpath_long = addPath(result, langcode);
3118 // If we have a subdirectory for the language already,
3120 FileName sp = FileName(subpath);
3121 if (sp.isDirectory())
3123 else if (FileName(subpath_long).isDirectory())
3124 result = subpath_long;
3126 // Ask whether we should create such a subdirectory
3127 docstring const text =
3128 bformat(_("It is suggested to save the template in a subdirectory\n"
3129 "appropriate to the document language (%1$s).\n"
3130 "This subdirectory does not exists yet.\n"
3131 "Do you want to create it?"),
3132 _(b.params().language->display()));
3133 if (Alert::prompt(_("Create Language Directory?"),
3134 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3135 // If the user agreed, we try to create it and report if this failed.
3136 if (!sp.createDirectory(0777))
3137 Alert::error(_("Subdirectory creation failed!"),
3138 _("Could not create subdirectory.\n"
3139 "The template will be saved in the parent directory."));
3145 // Do we have a layout category?
3146 string const cat = b.params().baseClass() ?
3147 b.params().baseClass()->category()
3150 string subpath = addPath(result, cat);
3151 // If we have a subdirectory for the category already,
3153 FileName sp = FileName(subpath);
3154 if (sp.isDirectory())
3157 // Ask whether we should create such a subdirectory
3158 docstring const text =
3159 bformat(_("It is suggested to save the template in a subdirectory\n"
3160 "appropriate to the layout category (%1$s).\n"
3161 "This subdirectory does not exists yet.\n"
3162 "Do you want to create it?"),
3164 if (Alert::prompt(_("Create Category Directory?"),
3165 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3166 // If the user agreed, we try to create it and report if this failed.
3167 if (!sp.createDirectory(0777))
3168 Alert::error(_("Subdirectory creation failed!"),
3169 _("Could not create subdirectory.\n"
3170 "The template will be saved in the parent directory."));
3180 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3182 FileName fname = b.fileName();
3183 FileName const oldname = fname;
3184 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3186 if (!newname.empty()) {
3189 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3191 fname = support::makeAbsPath(to_utf8(newname),
3192 oldname.onlyPath().absFileName());
3194 // Switch to this Buffer.
3197 // No argument? Ask user through dialog.
3199 QString const title = as_template ? qt_("Choose a filename to save template as")
3200 : qt_("Choose a filename to save document as");
3201 FileDialog dlg(title);
3202 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3203 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3205 fname.ensureExtension(".lyx");
3207 string const path = as_template ?
3209 : fname.onlyPath().absFileName();
3210 FileDialog::Result result =
3211 dlg.save(toqstr(path),
3212 QStringList(qt_("LyX Documents (*.lyx)")),
3213 toqstr(fname.onlyFileName()));
3215 if (result.first == FileDialog::Later)
3218 fname.set(fromqstr(result.second));
3223 fname.ensureExtension(".lyx");
3226 // fname is now the new Buffer location.
3228 // if there is already a Buffer open with this name, we do not want
3229 // to have another one. (the second test makes sure we're not just
3230 // trying to overwrite ourselves, which is fine.)
3231 if (theBufferList().exists(fname) && fname != oldname
3232 && theBufferList().getBuffer(fname) != &b) {
3233 docstring const text =
3234 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3235 "Please close it before attempting to overwrite it.\n"
3236 "Do you want to choose a new filename?"),
3237 from_utf8(fname.absFileName()));
3238 int const ret = Alert::prompt(_("Chosen File Already Open"),
3239 text, 0, 1, _("&Rename"), _("&Cancel"));
3241 case 0: return renameBuffer(b, docstring(), kind);
3242 case 1: return false;
3247 bool const existsLocal = fname.exists();
3248 bool const existsInVC = LyXVC::fileInVC(fname);
3249 if (existsLocal || existsInVC) {
3250 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3251 if (kind != LV_WRITE_AS && existsInVC) {
3252 // renaming to a name that is already in VC
3254 docstring text = bformat(_("The document %1$s "
3255 "is already registered.\n\n"
3256 "Do you want to choose a new name?"),
3258 docstring const title = (kind == LV_VC_RENAME) ?
3259 _("Rename document?") : _("Copy document?");
3260 docstring const button = (kind == LV_VC_RENAME) ?
3261 _("&Rename") : _("&Copy");
3262 int const ret = Alert::prompt(title, text, 0, 1,
3263 button, _("&Cancel"));
3265 case 0: return renameBuffer(b, docstring(), kind);
3266 case 1: return false;
3271 docstring text = bformat(_("The document %1$s "
3272 "already exists.\n\n"
3273 "Do you want to overwrite that document?"),
3275 int const ret = Alert::prompt(_("Overwrite document?"),
3276 text, 0, 2, _("&Overwrite"),
3277 _("&Rename"), _("&Cancel"));
3280 case 1: return renameBuffer(b, docstring(), kind);
3281 case 2: return false;
3287 case LV_VC_RENAME: {
3288 string msg = b.lyxvc().rename(fname);
3291 message(from_utf8(msg));
3295 string msg = b.lyxvc().copy(fname);
3298 message(from_utf8(msg));
3302 case LV_WRITE_AS_TEMPLATE:
3305 // LyXVC created the file already in case of LV_VC_RENAME or
3306 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3307 // relative paths of included stuff right if we moved e.g. from
3308 // /a/b.lyx to /a/c/b.lyx.
3310 bool const saved = saveBuffer(b, fname);
3317 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3319 FileName fname = b.fileName();
3321 FileDialog dlg(qt_("Choose a filename to export the document as"));
3322 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3325 QString const anyformat = qt_("Guess from extension (*.*)");
3328 vector<Format const *> export_formats;
3329 for (Format const & f : theFormats())
3330 if (f.documentFormat())
3331 export_formats.push_back(&f);
3332 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3333 map<QString, string> fmap;
3336 for (Format const * f : export_formats) {
3337 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3338 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3340 from_ascii(f->extension())));
3341 types << loc_filter;
3342 fmap[loc_filter] = f->name();
3343 if (from_ascii(f->name()) == iformat) {
3344 filter = loc_filter;
3345 ext = f->extension();
3348 string ofname = fname.onlyFileName();
3350 ofname = support::changeExtension(ofname, ext);
3351 FileDialog::Result result =
3352 dlg.save(toqstr(fname.onlyPath().absFileName()),
3356 if (result.first != FileDialog::Chosen)
3360 fname.set(fromqstr(result.second));
3361 if (filter == anyformat)
3362 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3364 fmt_name = fmap[filter];
3365 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3366 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3368 if (fmt_name.empty() || fname.empty())
3371 fname.ensureExtension(theFormats().extension(fmt_name));
3373 // fname is now the new Buffer location.
3374 if (fname.exists()) {
3375 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3376 docstring text = bformat(_("The document %1$s already "
3377 "exists.\n\nDo you want to "
3378 "overwrite that document?"),
3380 int const ret = Alert::prompt(_("Overwrite document?"),
3381 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3384 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3385 case 2: return false;
3389 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3392 return dr.dispatched();
3396 bool GuiView::saveBuffer(Buffer & b)
3398 return saveBuffer(b, FileName());
3402 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3404 if (workArea(b) && workArea(b)->inDialogMode())
3407 if (fn.empty() && b.isUnnamed())
3408 return renameBuffer(b, docstring());
3410 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3412 theSession().lastFiles().add(b.fileName());
3413 theSession().writeFile();
3417 // Switch to this Buffer.
3420 // FIXME: we don't tell the user *WHY* the save failed !!
3421 docstring const file = makeDisplayPath(b.absFileName(), 30);
3422 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3423 "Do you want to rename the document and "
3424 "try again?"), file);
3425 int const ret = Alert::prompt(_("Rename and save?"),
3426 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3429 if (!renameBuffer(b, docstring()))
3438 return saveBuffer(b, fn);
3442 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3444 return closeWorkArea(wa, false);
3448 // We only want to close the buffer if it is not visible in other workareas
3449 // of the same view, nor in other views, and if this is not a child
3450 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3452 Buffer & buf = wa->bufferView().buffer();
3454 bool last_wa = d.countWorkAreasOf(buf) == 1
3455 && !inOtherView(buf) && !buf.parent();
3457 bool close_buffer = last_wa;
3460 if (lyxrc.close_buffer_with_last_view == "yes")
3462 else if (lyxrc.close_buffer_with_last_view == "no")
3463 close_buffer = false;
3466 if (buf.isUnnamed())
3467 file = from_utf8(buf.fileName().onlyFileName());
3469 file = buf.fileName().displayName(30);
3470 docstring const text = bformat(
3471 _("Last view on document %1$s is being closed.\n"
3472 "Would you like to close or hide the document?\n"
3474 "Hidden documents can be displayed back through\n"
3475 "the menu: View->Hidden->...\n"
3477 "To remove this question, set your preference in:\n"
3478 " Tools->Preferences->Look&Feel->UserInterface\n"
3480 int ret = Alert::prompt(_("Close or hide document?"),
3481 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3484 close_buffer = (ret == 0);
3488 return closeWorkArea(wa, close_buffer);
3492 bool GuiView::closeBuffer()
3494 GuiWorkArea * wa = currentMainWorkArea();
3495 // coverity complained about this
3496 // it seems unnecessary, but perhaps is worth the check
3497 LASSERT(wa, return false);
3499 setCurrentWorkArea(wa);
3500 Buffer & buf = wa->bufferView().buffer();
3501 return closeWorkArea(wa, !buf.parent());
3505 void GuiView::writeSession() const {
3506 GuiWorkArea const * active_wa = currentMainWorkArea();
3507 for (int i = 0; i < d.splitter_->count(); ++i) {
3508 TabWorkArea * twa = d.tabWorkArea(i);
3509 for (int j = 0; j < twa->count(); ++j) {
3510 GuiWorkArea * wa = twa->workArea(j);
3511 Buffer & buf = wa->bufferView().buffer();
3512 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3518 bool GuiView::closeBufferAll()
3521 for (auto & buf : theBufferList()) {
3522 if (!saveBufferIfNeeded(*buf, false)) {
3523 // Closing has been cancelled, so abort.
3528 // Close the workareas in all other views
3529 QList<int> const ids = guiApp->viewIds();
3530 for (int i = 0; i != ids.size(); ++i) {
3531 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3535 // Close our own workareas
3536 if (!closeWorkAreaAll())
3543 bool GuiView::closeWorkAreaAll()
3545 setCurrentWorkArea(currentMainWorkArea());
3547 // We might be in a situation that there is still a tabWorkArea, but
3548 // there are no tabs anymore. This can happen when we get here after a
3549 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3550 // many TabWorkArea's have no documents anymore.
3553 // We have to call count() each time, because it can happen that
3554 // more than one splitter will disappear in one iteration (bug 5998).
3555 while (d.splitter_->count() > empty_twa) {
3556 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3558 if (twa->count() == 0)
3561 setCurrentWorkArea(twa->currentWorkArea());
3562 if (!closeTabWorkArea(twa))
3570 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3575 Buffer & buf = wa->bufferView().buffer();
3577 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3578 Alert::warning(_("Close document"),
3579 _("Document could not be closed because it is being processed by LyX."));
3584 return closeBuffer(buf);
3586 if (!inMultiTabs(wa))
3587 if (!saveBufferIfNeeded(buf, true))
3595 bool GuiView::closeBuffer(Buffer & buf)
3597 bool success = true;
3598 for (Buffer * child_buf : buf.getChildren()) {
3599 if (theBufferList().isOthersChild(&buf, child_buf)) {
3600 child_buf->setParent(nullptr);
3604 // FIXME: should we look in other tabworkareas?
3605 // ANSWER: I don't think so. I've tested, and if the child is
3606 // open in some other window, it closes without a problem.
3607 GuiWorkArea * child_wa = workArea(*child_buf);
3610 // If we are in a close_event all children will be closed in some time,
3611 // so no need to do it here. This will ensure that the children end up
3612 // in the session file in the correct order. If we close the master
3613 // buffer, we can close or release the child buffers here too.
3615 success = closeWorkArea(child_wa, true);
3619 // In this case the child buffer is open but hidden.
3620 // Even in this case, children can be dirty (e.g.,
3621 // after a label change in the master, see #11405).
3622 // Therefore, check this
3623 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3624 // If we are in a close_event all children will be closed in some time,
3625 // so no need to do it here. This will ensure that the children end up
3626 // in the session file in the correct order. If we close the master
3627 // buffer, we can close or release the child buffers here too.
3630 // Save dirty buffers also if closing_!
3631 if (saveBufferIfNeeded(*child_buf, false)) {
3632 child_buf->removeAutosaveFile();
3633 theBufferList().release(child_buf);
3635 // Saving of dirty children has been cancelled.
3636 // Cancel the whole process.
3643 // goto bookmark to update bookmark pit.
3644 // FIXME: we should update only the bookmarks related to this buffer!
3645 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3646 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3647 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3648 guiApp->gotoBookmark(i, false, false);
3650 if (saveBufferIfNeeded(buf, false)) {
3651 buf.removeAutosaveFile();
3652 theBufferList().release(&buf);
3656 // open all children again to avoid a crash because of dangling
3657 // pointers (bug 6603)
3663 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3665 while (twa == d.currentTabWorkArea()) {
3666 twa->setCurrentIndex(twa->count() - 1);
3668 GuiWorkArea * wa = twa->currentWorkArea();
3669 Buffer & b = wa->bufferView().buffer();
3671 // We only want to close the buffer if the same buffer is not visible
3672 // in another view, and if this is not a child and if we are closing
3673 // a view (not a tabgroup).
3674 bool const close_buffer =
3675 !inOtherView(b) && !b.parent() && closing_;
3677 if (!closeWorkArea(wa, close_buffer))
3684 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3686 if (buf.isClean() || buf.paragraphs().empty())
3689 // Switch to this Buffer.
3695 if (buf.isUnnamed()) {
3696 file = from_utf8(buf.fileName().onlyFileName());
3699 FileName filename = buf.fileName();
3701 file = filename.displayName(30);
3702 exists = filename.exists();
3705 // Bring this window to top before asking questions.
3710 if (hiding && buf.isUnnamed()) {
3711 docstring const text = bformat(_("The document %1$s has not been "
3712 "saved yet.\n\nDo you want to save "
3713 "the document?"), file);
3714 ret = Alert::prompt(_("Save new document?"),
3715 text, 0, 1, _("&Save"), _("&Cancel"));
3719 docstring const text = exists ?
3720 bformat(_("The document %1$s has unsaved changes."
3721 "\n\nDo you want to save the document or "
3722 "discard the changes?"), file) :
3723 bformat(_("The document %1$s has not been saved yet."
3724 "\n\nDo you want to save the document or "
3725 "discard it entirely?"), file);
3726 docstring const title = exists ?
3727 _("Save changed document?") : _("Save document?");
3728 ret = Alert::prompt(title, text, 0, 2,
3729 _("&Save"), _("&Discard"), _("&Cancel"));
3734 if (!saveBuffer(buf))
3738 // If we crash after this we could have no autosave file
3739 // but I guess this is really improbable (Jug).
3740 // Sometimes improbable things happen:
3741 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3742 // buf.removeAutosaveFile();
3744 // revert all changes
3755 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3757 Buffer & buf = wa->bufferView().buffer();
3759 for (int i = 0; i != d.splitter_->count(); ++i) {
3760 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3761 if (wa_ && wa_ != wa)
3764 return inOtherView(buf);
3768 bool GuiView::inOtherView(Buffer & buf)
3770 QList<int> const ids = guiApp->viewIds();
3772 for (int i = 0; i != ids.size(); ++i) {
3776 if (guiApp->view(ids[i]).workArea(buf))
3783 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3785 if (!documentBufferView())
3788 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3789 Buffer * const curbuf = &documentBufferView()->buffer();
3790 int nwa = twa->count();
3791 for (int i = 0; i < nwa; ++i) {
3792 if (&workArea(i)->bufferView().buffer() == curbuf) {
3794 if (np == NEXTBUFFER)
3795 next_index = (i == nwa - 1 ? 0 : i + 1);
3797 next_index = (i == 0 ? nwa - 1 : i - 1);
3799 twa->moveTab(i, next_index);
3801 setBuffer(&workArea(next_index)->bufferView().buffer());
3809 /// make sure the document is saved
3810 static bool ensureBufferClean(Buffer * buffer)
3812 LASSERT(buffer, return false);
3813 if (buffer->isClean() && !buffer->isUnnamed())
3816 docstring const file = buffer->fileName().displayName(30);
3819 if (!buffer->isUnnamed()) {
3820 text = bformat(_("The document %1$s has unsaved "
3821 "changes.\n\nDo you want to save "
3822 "the document?"), file);
3823 title = _("Save changed document?");
3826 text = bformat(_("The document %1$s has not been "
3827 "saved yet.\n\nDo you want to save "
3828 "the document?"), file);
3829 title = _("Save new document?");
3831 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3834 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3836 return buffer->isClean() && !buffer->isUnnamed();
3840 bool GuiView::reloadBuffer(Buffer & buf)
3842 currentBufferView()->cursor().reset();
3843 Buffer::ReadStatus status = buf.reload();
3844 return status == Buffer::ReadSuccess;
3848 void GuiView::checkExternallyModifiedBuffers()
3850 for (Buffer * buf : theBufferList()) {
3851 if (buf->fileName().exists() && buf->isChecksumModified()) {
3852 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3853 " Reload now? Any local changes will be lost."),
3854 from_utf8(buf->absFileName()));
3855 int const ret = Alert::prompt(_("Reload externally changed document?"),
3856 text, 0, 1, _("&Reload"), _("&Cancel"));
3864 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3866 Buffer * buffer = documentBufferView()
3867 ? &(documentBufferView()->buffer()) : nullptr;
3869 switch (cmd.action()) {
3870 case LFUN_VC_REGISTER:
3871 if (!buffer || !ensureBufferClean(buffer))
3873 if (!buffer->lyxvc().inUse()) {
3874 if (buffer->lyxvc().registrer()) {
3875 reloadBuffer(*buffer);
3876 dr.clearMessageUpdate();
3881 case LFUN_VC_RENAME:
3882 case LFUN_VC_COPY: {
3883 if (!buffer || !ensureBufferClean(buffer))
3885 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3886 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3887 // Some changes are not yet committed.
3888 // We test here and not in getStatus(), since
3889 // this test is expensive.
3891 LyXVC::CommandResult ret =
3892 buffer->lyxvc().checkIn(log);
3894 if (ret == LyXVC::ErrorCommand ||
3895 ret == LyXVC::VCSuccess)
3896 reloadBuffer(*buffer);
3897 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3898 frontend::Alert::error(
3899 _("Revision control error."),
3900 _("Document could not be checked in."));
3904 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3905 LV_VC_RENAME : LV_VC_COPY;
3906 renameBuffer(*buffer, cmd.argument(), kind);
3911 case LFUN_VC_CHECK_IN:
3912 if (!buffer || !ensureBufferClean(buffer))
3914 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3916 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3918 // Only skip reloading if the checkin was cancelled or
3919 // an error occurred before the real checkin VCS command
3920 // was executed, since the VCS might have changed the
3921 // file even if it could not checkin successfully.
3922 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3923 reloadBuffer(*buffer);
3927 case LFUN_VC_CHECK_OUT:
3928 if (!buffer || !ensureBufferClean(buffer))
3930 if (buffer->lyxvc().inUse()) {
3931 dr.setMessage(buffer->lyxvc().checkOut());
3932 reloadBuffer(*buffer);
3936 case LFUN_VC_LOCKING_TOGGLE:
3937 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3939 if (buffer->lyxvc().inUse()) {
3940 string res = buffer->lyxvc().lockingToggle();
3942 frontend::Alert::error(_("Revision control error."),
3943 _("Error when setting the locking property."));
3946 reloadBuffer(*buffer);
3951 case LFUN_VC_REVERT:
3954 if (buffer->lyxvc().revert()) {
3955 reloadBuffer(*buffer);
3956 dr.clearMessageUpdate();
3960 case LFUN_VC_UNDO_LAST:
3963 buffer->lyxvc().undoLast();
3964 reloadBuffer(*buffer);
3965 dr.clearMessageUpdate();
3968 case LFUN_VC_REPO_UPDATE:
3971 if (ensureBufferClean(buffer)) {
3972 dr.setMessage(buffer->lyxvc().repoUpdate());
3973 checkExternallyModifiedBuffers();
3977 case LFUN_VC_COMMAND: {
3978 string flag = cmd.getArg(0);
3979 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3982 if (contains(flag, 'M')) {
3983 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3986 string path = cmd.getArg(1);
3987 if (contains(path, "$$p") && buffer)
3988 path = subst(path, "$$p", buffer->filePath());
3989 LYXERR(Debug::LYXVC, "Directory: " << path);
3991 if (!pp.isReadableDirectory()) {
3992 lyxerr << _("Directory is not accessible.") << endl;
3995 support::PathChanger p(pp);
3997 string command = cmd.getArg(2);
3998 if (command.empty())
4001 command = subst(command, "$$i", buffer->absFileName());
4002 command = subst(command, "$$p", buffer->filePath());
4004 command = subst(command, "$$m", to_utf8(message));
4005 LYXERR(Debug::LYXVC, "Command: " << command);
4007 one.startscript(Systemcall::Wait, command);
4011 if (contains(flag, 'I'))
4012 buffer->markDirty();
4013 if (contains(flag, 'R'))
4014 reloadBuffer(*buffer);
4019 case LFUN_VC_COMPARE: {
4020 if (cmd.argument().empty()) {
4021 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4027 string rev1 = cmd.getArg(0);
4031 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4034 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4035 f2 = buffer->absFileName();
4037 string rev2 = cmd.getArg(1);
4041 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4045 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4046 f1 << "\n" << f2 << "\n" );
4047 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4048 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4058 void GuiView::openChildDocument(string const & fname)
4060 LASSERT(documentBufferView(), return);
4061 Buffer & buffer = documentBufferView()->buffer();
4062 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4063 documentBufferView()->saveBookmark(false);
4064 Buffer * child = nullptr;
4065 if (theBufferList().exists(filename)) {
4066 child = theBufferList().getBuffer(filename);
4069 message(bformat(_("Opening child document %1$s..."),
4070 makeDisplayPath(filename.absFileName())));
4071 child = loadDocument(filename, false);
4073 // Set the parent name of the child document.
4074 // This makes insertion of citations and references in the child work,
4075 // when the target is in the parent or another child document.
4077 child->setParent(&buffer);
4081 bool GuiView::goToFileRow(string const & argument)
4085 size_t i = argument.find_last_of(' ');
4086 if (i != string::npos) {
4087 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4088 istringstream is(argument.substr(i + 1));
4093 if (i == string::npos) {
4094 LYXERR0("Wrong argument: " << argument);
4097 Buffer * buf = nullptr;
4098 string const realtmp = package().temp_dir().realPath();
4099 // We have to use os::path_prefix_is() here, instead of
4100 // simply prefixIs(), because the file name comes from
4101 // an external application and may need case adjustment.
4102 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4103 buf = theBufferList().getBufferFromTmp(file_name, true);
4104 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4105 << (buf ? " success" : " failed"));
4107 // Must replace extension of the file to be .lyx
4108 // and get full path
4109 FileName const s = fileSearch(string(),
4110 support::changeExtension(file_name, ".lyx"), "lyx");
4111 // Either change buffer or load the file
4112 if (theBufferList().exists(s))
4113 buf = theBufferList().getBuffer(s);
4114 else if (s.exists()) {
4115 buf = loadDocument(s);
4120 _("File does not exist: %1$s"),
4121 makeDisplayPath(file_name)));
4127 _("No buffer for file: %1$s."),
4128 makeDisplayPath(file_name))
4133 bool success = documentBufferView()->setCursorFromRow(row);
4135 LYXERR(Debug::OUTFILE,
4136 "setCursorFromRow: invalid position for row " << row);
4137 frontend::Alert::error(_("Inverse Search Failed"),
4138 _("Invalid position requested by inverse search.\n"
4139 "You may need to update the viewed document."));
4145 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4147 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4148 menu->exec(QCursor::pos());
4153 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4154 Buffer const * orig, Buffer * clone, string const & format)
4156 Buffer::ExportStatus const status = func(format);
4158 // the cloning operation will have produced a clone of the entire set of
4159 // documents, starting from the master. so we must delete those.
4160 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4162 busyBuffers.remove(orig);
4167 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4168 Buffer const * orig, Buffer * clone, string const & format)
4170 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4172 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4176 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4177 Buffer const * orig, Buffer * clone, string const & format)
4179 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4181 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4185 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4186 Buffer const * orig, Buffer * clone, string const & format)
4188 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4190 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4194 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4195 Buffer const * used_buffer,
4196 docstring const & msg,
4197 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4198 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4199 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4200 bool allow_async, bool use_tmpdir)
4205 string format = argument;
4207 format = used_buffer->params().getDefaultOutputFormat();
4208 processing_format = format;
4210 progress_->clearMessages();
4213 #if EXPORT_in_THREAD
4215 GuiViewPrivate::busyBuffers.insert(used_buffer);
4216 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4217 if (!cloned_buffer) {
4218 Alert::error(_("Export Error"),
4219 _("Error cloning the Buffer."));
4222 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4227 setPreviewFuture(f);
4228 last_export_format = used_buffer->params().bufferFormat();
4231 // We are asynchronous, so we don't know here anything about the success
4234 Buffer::ExportStatus status;
4236 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4237 } else if (previewFunc) {
4238 status = (used_buffer->*previewFunc)(format);
4241 handleExportStatus(gv_, status, format);
4243 return (status == Buffer::ExportSuccess
4244 || status == Buffer::PreviewSuccess);
4248 Buffer::ExportStatus status;
4250 status = (used_buffer->*syncFunc)(format, true);
4251 } else if (previewFunc) {
4252 status = (used_buffer->*previewFunc)(format);
4255 handleExportStatus(gv_, status, format);
4257 return (status == Buffer::ExportSuccess
4258 || status == Buffer::PreviewSuccess);
4262 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4264 BufferView * bv = currentBufferView();
4265 LASSERT(bv, return);
4267 // Let the current BufferView dispatch its own actions.
4268 bv->dispatch(cmd, dr);
4269 if (dr.dispatched()) {
4270 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4271 updateDialog("document", "");
4275 // Try with the document BufferView dispatch if any.
4276 BufferView * doc_bv = documentBufferView();
4277 if (doc_bv && doc_bv != bv) {
4278 doc_bv->dispatch(cmd, dr);
4279 if (dr.dispatched()) {
4280 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4281 updateDialog("document", "");
4286 // Then let the current Cursor dispatch its own actions.
4287 bv->cursor().dispatch(cmd);
4289 // update completion. We do it here and not in
4290 // processKeySym to avoid another redraw just for a
4291 // changed inline completion
4292 if (cmd.origin() == FuncRequest::KEYBOARD) {
4293 if (cmd.action() == LFUN_SELF_INSERT
4294 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4295 updateCompletion(bv->cursor(), true, true);
4296 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4297 updateCompletion(bv->cursor(), false, true);
4299 updateCompletion(bv->cursor(), false, false);
4302 dr = bv->cursor().result();
4306 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4308 BufferView * bv = currentBufferView();
4309 // By default we won't need any update.
4310 dr.screenUpdate(Update::None);
4311 // assume cmd will be dispatched
4312 dr.dispatched(true);
4314 Buffer * doc_buffer = documentBufferView()
4315 ? &(documentBufferView()->buffer()) : nullptr;
4317 if (cmd.origin() == FuncRequest::TOC) {
4318 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4319 toc->doDispatch(bv->cursor(), cmd, dr);
4323 string const argument = to_utf8(cmd.argument());
4325 switch(cmd.action()) {
4326 case LFUN_BUFFER_CHILD_OPEN:
4327 openChildDocument(to_utf8(cmd.argument()));
4330 case LFUN_BUFFER_IMPORT:
4331 importDocument(to_utf8(cmd.argument()));
4334 case LFUN_MASTER_BUFFER_EXPORT:
4336 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4338 case LFUN_BUFFER_EXPORT: {
4341 // GCC only sees strfwd.h when building merged
4342 if (::lyx::operator==(cmd.argument(), "custom")) {
4343 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4344 // so the following test should not be needed.
4345 // In principle, we could try to switch to such a view...
4346 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4347 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4351 string const dest = cmd.getArg(1);
4352 FileName target_dir;
4353 if (!dest.empty() && FileName::isAbsolute(dest))
4354 target_dir = FileName(support::onlyPath(dest));
4356 target_dir = doc_buffer->fileName().onlyPath();
4358 string const format = (argument.empty() || argument == "default") ?
4359 doc_buffer->params().getDefaultOutputFormat() : argument;
4361 if ((dest.empty() && doc_buffer->isUnnamed())
4362 || !target_dir.isDirWritable()) {
4363 exportBufferAs(*doc_buffer, from_utf8(format));
4366 /* TODO/Review: Is it a problem to also export the children?
4367 See the update_unincluded flag */
4368 d.asyncBufferProcessing(format,
4371 &GuiViewPrivate::exportAndDestroy,
4373 nullptr, cmd.allowAsync());
4374 // TODO Inform user about success
4378 case LFUN_BUFFER_EXPORT_AS: {
4379 LASSERT(doc_buffer, break);
4380 docstring f = cmd.argument();
4382 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4383 exportBufferAs(*doc_buffer, f);
4387 case LFUN_BUFFER_UPDATE: {
4388 d.asyncBufferProcessing(argument,
4391 &GuiViewPrivate::compileAndDestroy,
4393 nullptr, cmd.allowAsync(), true);
4396 case LFUN_BUFFER_VIEW: {
4397 d.asyncBufferProcessing(argument,
4399 _("Previewing ..."),
4400 &GuiViewPrivate::previewAndDestroy,
4402 &Buffer::preview, cmd.allowAsync());
4405 case LFUN_MASTER_BUFFER_UPDATE: {
4406 d.asyncBufferProcessing(argument,
4407 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4409 &GuiViewPrivate::compileAndDestroy,
4411 nullptr, cmd.allowAsync(), true);
4414 case LFUN_MASTER_BUFFER_VIEW: {
4415 d.asyncBufferProcessing(argument,
4416 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4418 &GuiViewPrivate::previewAndDestroy,
4419 nullptr, &Buffer::preview, cmd.allowAsync());
4422 case LFUN_EXPORT_CANCEL: {
4423 Systemcall::killscript();
4426 case LFUN_BUFFER_SWITCH: {
4427 string const file_name = to_utf8(cmd.argument());
4428 if (!FileName::isAbsolute(file_name)) {
4430 dr.setMessage(_("Absolute filename expected."));
4434 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4437 dr.setMessage(_("Document not loaded"));
4441 // Do we open or switch to the buffer in this view ?
4442 if (workArea(*buffer)
4443 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4448 // Look for the buffer in other views
4449 QList<int> const ids = guiApp->viewIds();
4451 for (; i != ids.size(); ++i) {
4452 GuiView & gv = guiApp->view(ids[i]);
4453 if (gv.workArea(*buffer)) {
4455 gv.activateWindow();
4457 gv.setBuffer(buffer);
4462 // If necessary, open a new window as a last resort
4463 if (i == ids.size()) {
4464 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4470 case LFUN_BUFFER_NEXT:
4471 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4474 case LFUN_BUFFER_MOVE_NEXT:
4475 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4478 case LFUN_BUFFER_PREVIOUS:
4479 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4482 case LFUN_BUFFER_MOVE_PREVIOUS:
4483 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4486 case LFUN_BUFFER_CHKTEX:
4487 LASSERT(doc_buffer, break);
4488 doc_buffer->runChktex();
4491 case LFUN_COMMAND_EXECUTE: {
4492 command_execute_ = true;
4493 minibuffer_focus_ = true;
4496 case LFUN_DROP_LAYOUTS_CHOICE:
4497 d.layout_->showPopup();
4500 case LFUN_MENU_OPEN:
4501 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4502 menu->exec(QCursor::pos());
4505 case LFUN_FILE_INSERT: {
4506 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4507 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4508 dr.forceBufferUpdate();
4509 dr.screenUpdate(Update::Force);
4514 case LFUN_FILE_INSERT_PLAINTEXT:
4515 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4516 string const fname = to_utf8(cmd.argument());
4517 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4518 dr.setMessage(_("Absolute filename expected."));
4522 FileName filename(fname);
4523 if (fname.empty()) {
4524 FileDialog dlg(qt_("Select file to insert"));
4526 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4527 QStringList(qt_("All Files (*)")));
4529 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4530 dr.setMessage(_("Canceled."));
4534 filename.set(fromqstr(result.second));
4538 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4539 bv->dispatch(new_cmd, dr);
4544 case LFUN_BUFFER_RELOAD: {
4545 LASSERT(doc_buffer, break);
4548 bool drop = (cmd.argument() == "dump");
4551 if (!drop && !doc_buffer->isClean()) {
4552 docstring const file =
4553 makeDisplayPath(doc_buffer->absFileName(), 20);
4554 if (doc_buffer->notifiesExternalModification()) {
4555 docstring text = _("The current version will be lost. "
4556 "Are you sure you want to load the version on disk "
4557 "of the document %1$s?");
4558 ret = Alert::prompt(_("Reload saved document?"),
4559 bformat(text, file), 1, 1,
4560 _("&Reload"), _("&Cancel"));
4562 docstring text = _("Any changes will be lost. "
4563 "Are you sure you want to revert to the saved version "
4564 "of the document %1$s?");
4565 ret = Alert::prompt(_("Revert to saved document?"),
4566 bformat(text, file), 1, 1,
4567 _("&Revert"), _("&Cancel"));
4572 doc_buffer->markClean();
4573 reloadBuffer(*doc_buffer);
4574 dr.forceBufferUpdate();
4579 case LFUN_BUFFER_RESET_EXPORT:
4580 LASSERT(doc_buffer, break);
4581 doc_buffer->requireFreshStart(true);
4582 dr.setMessage(_("Buffer export reset."));
4585 case LFUN_BUFFER_WRITE:
4586 LASSERT(doc_buffer, break);
4587 saveBuffer(*doc_buffer);
4590 case LFUN_BUFFER_WRITE_AS:
4591 LASSERT(doc_buffer, break);
4592 renameBuffer(*doc_buffer, cmd.argument());
4595 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4596 LASSERT(doc_buffer, break);
4597 renameBuffer(*doc_buffer, cmd.argument(),
4598 LV_WRITE_AS_TEMPLATE);
4601 case LFUN_BUFFER_WRITE_ALL: {
4602 Buffer * first = theBufferList().first();
4605 message(_("Saving all documents..."));
4606 // We cannot use a for loop as the buffer list cycles.
4609 if (!b->isClean()) {
4611 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4613 b = theBufferList().next(b);
4614 } while (b != first);
4615 dr.setMessage(_("All documents saved."));
4619 case LFUN_MASTER_BUFFER_FORALL: {
4623 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4624 funcToRun.allowAsync(false);
4626 for (Buffer const * buf : doc_buffer->allRelatives()) {
4627 // Switch to other buffer view and resend cmd
4628 lyx::dispatch(FuncRequest(
4629 LFUN_BUFFER_SWITCH, buf->absFileName()));
4630 lyx::dispatch(funcToRun);
4633 lyx::dispatch(FuncRequest(
4634 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4638 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4639 LASSERT(doc_buffer, break);
4640 doc_buffer->clearExternalModification();
4643 case LFUN_BUFFER_CLOSE:
4647 case LFUN_BUFFER_CLOSE_ALL:
4651 case LFUN_DEVEL_MODE_TOGGLE:
4652 devel_mode_ = !devel_mode_;
4654 dr.setMessage(_("Developer mode is now enabled."));
4656 dr.setMessage(_("Developer mode is now disabled."));
4659 case LFUN_TOOLBAR_SET: {
4660 string const name = cmd.getArg(0);
4661 string const state = cmd.getArg(1);
4662 if (GuiToolbar * t = toolbar(name))
4667 case LFUN_TOOLBAR_TOGGLE: {
4668 string const name = cmd.getArg(0);
4669 if (GuiToolbar * t = toolbar(name))
4674 case LFUN_TOOLBAR_MOVABLE: {
4675 string const name = cmd.getArg(0);
4677 // toggle (all) toolbars movablility
4678 toolbarsMovable_ = !toolbarsMovable_;
4679 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4680 GuiToolbar * tb = toolbar(ti.name);
4681 if (tb && tb->isMovable() != toolbarsMovable_)
4682 // toggle toolbar movablity if it does not fit lock
4683 // (all) toolbars positions state silent = true, since
4684 // status bar notifications are slow
4687 if (toolbarsMovable_)
4688 dr.setMessage(_("Toolbars unlocked."));
4690 dr.setMessage(_("Toolbars locked."));
4691 } else if (GuiToolbar * tb = toolbar(name))
4692 // toggle current toolbar movablity
4694 // update lock (all) toolbars positions
4695 updateLockToolbars();
4699 case LFUN_ICON_SIZE: {
4700 QSize size = d.iconSize(cmd.argument());
4702 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4703 size.width(), size.height()));
4707 case LFUN_DIALOG_UPDATE: {
4708 string const name = to_utf8(cmd.argument());
4709 if (name == "prefs" || name == "document")
4710 updateDialog(name, string());
4711 else if (name == "paragraph")
4712 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4713 else if (currentBufferView()) {
4714 Inset * inset = currentBufferView()->editedInset(name);
4715 // Can only update a dialog connected to an existing inset
4717 // FIXME: get rid of this indirection; GuiView ask the inset
4718 // if he is kind enough to update itself...
4719 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4720 //FIXME: pass DispatchResult here?
4721 inset->dispatch(currentBufferView()->cursor(), fr);
4727 case LFUN_DIALOG_TOGGLE: {
4728 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4729 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4730 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4734 case LFUN_DIALOG_DISCONNECT_INSET:
4735 disconnectDialog(to_utf8(cmd.argument()));
4738 case LFUN_DIALOG_HIDE: {
4739 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4743 case LFUN_DIALOG_SHOW: {
4744 string const name = cmd.getArg(0);
4745 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4747 if (name == "latexlog") {
4748 // getStatus checks that
4749 LASSERT(doc_buffer, break);
4750 Buffer::LogType type;
4751 string const logfile = doc_buffer->logName(&type);
4753 case Buffer::latexlog:
4756 case Buffer::buildlog:
4757 sdata = "literate ";
4760 sdata += Lexer::quoteString(logfile);
4761 showDialog("log", sdata);
4762 } else if (name == "vclog") {
4763 // getStatus checks that
4764 LASSERT(doc_buffer, break);
4765 string const sdata2 = "vc " +
4766 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4767 showDialog("log", sdata2);
4768 } else if (name == "symbols") {
4769 sdata = bv->cursor().getEncoding()->name();
4771 showDialog("symbols", sdata);
4772 } else if (name == "findreplace") {
4773 sdata = to_utf8(bv->cursor().selectionAsString(false));
4774 showDialog(name, sdata);
4776 } else if (name == "prefs" && isFullScreen()) {
4777 lfunUiToggle("fullscreen");
4778 showDialog("prefs", sdata);
4780 showDialog(name, sdata);
4785 dr.setMessage(cmd.argument());
4788 case LFUN_UI_TOGGLE: {
4789 string arg = cmd.getArg(0);
4790 if (!lfunUiToggle(arg)) {
4791 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4792 dr.setMessage(bformat(msg, from_utf8(arg)));
4794 // Make sure the keyboard focus stays in the work area.
4799 case LFUN_VIEW_SPLIT: {
4800 LASSERT(doc_buffer, break);
4801 string const orientation = cmd.getArg(0);
4802 d.splitter_->setOrientation(orientation == "vertical"
4803 ? Qt::Vertical : Qt::Horizontal);
4804 TabWorkArea * twa = addTabWorkArea();
4805 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4806 setCurrentWorkArea(wa);
4809 case LFUN_TAB_GROUP_CLOSE:
4810 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4811 closeTabWorkArea(twa);
4812 d.current_work_area_ = nullptr;
4813 twa = d.currentTabWorkArea();
4814 // Switch to the next GuiWorkArea in the found TabWorkArea.
4816 // Make sure the work area is up to date.
4817 setCurrentWorkArea(twa->currentWorkArea());
4819 setCurrentWorkArea(nullptr);
4824 case LFUN_VIEW_CLOSE:
4825 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4826 closeWorkArea(twa->currentWorkArea());
4827 d.current_work_area_ = nullptr;
4828 twa = d.currentTabWorkArea();
4829 // Switch to the next GuiWorkArea in the found TabWorkArea.
4831 // Make sure the work area is up to date.
4832 setCurrentWorkArea(twa->currentWorkArea());
4834 setCurrentWorkArea(nullptr);
4839 case LFUN_COMPLETION_INLINE:
4840 if (d.current_work_area_)
4841 d.current_work_area_->completer().showInline();
4844 case LFUN_COMPLETION_POPUP:
4845 if (d.current_work_area_)
4846 d.current_work_area_->completer().showPopup();
4851 if (d.current_work_area_)
4852 d.current_work_area_->completer().tab();
4855 case LFUN_COMPLETION_CANCEL:
4856 if (d.current_work_area_) {
4857 if (d.current_work_area_->completer().popupVisible())
4858 d.current_work_area_->completer().hidePopup();
4860 d.current_work_area_->completer().hideInline();
4864 case LFUN_COMPLETION_ACCEPT:
4865 if (d.current_work_area_)
4866 d.current_work_area_->completer().activate();
4869 case LFUN_BUFFER_ZOOM_IN:
4870 case LFUN_BUFFER_ZOOM_OUT:
4871 case LFUN_BUFFER_ZOOM: {
4872 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4874 // Actual zoom value: default zoom + fractional extra value
4875 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4876 zoom = min(max(zoom, zoom_min_), zoom_max_);
4878 setCurrentZoom(zoom);
4880 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4881 lyxrc.currentZoom, lyxrc.defaultZoom));
4883 guiApp->fontLoader().update();
4884 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4888 case LFUN_VC_REGISTER:
4889 case LFUN_VC_RENAME:
4891 case LFUN_VC_CHECK_IN:
4892 case LFUN_VC_CHECK_OUT:
4893 case LFUN_VC_REPO_UPDATE:
4894 case LFUN_VC_LOCKING_TOGGLE:
4895 case LFUN_VC_REVERT:
4896 case LFUN_VC_UNDO_LAST:
4897 case LFUN_VC_COMMAND:
4898 case LFUN_VC_COMPARE:
4899 dispatchVC(cmd, dr);
4902 case LFUN_SERVER_GOTO_FILE_ROW:
4903 if(goToFileRow(to_utf8(cmd.argument())))
4904 dr.screenUpdate(Update::Force | Update::FitCursor);
4907 case LFUN_LYX_ACTIVATE:
4911 case LFUN_WINDOW_RAISE:
4917 case LFUN_FORWARD_SEARCH: {
4918 // it seems safe to assume we have a document buffer, since
4919 // getStatus wants one.
4920 LASSERT(doc_buffer, break);
4921 Buffer const * doc_master = doc_buffer->masterBuffer();
4922 FileName const path(doc_master->temppath());
4923 string const texname = doc_master->isChild(doc_buffer)
4924 ? DocFileName(changeExtension(
4925 doc_buffer->absFileName(),
4926 "tex")).mangledFileName()
4927 : doc_buffer->latexName();
4928 string const fulltexname =
4929 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4930 string const mastername =
4931 removeExtension(doc_master->latexName());
4932 FileName const dviname(addName(path.absFileName(),
4933 addExtension(mastername, "dvi")));
4934 FileName const pdfname(addName(path.absFileName(),
4935 addExtension(mastername, "pdf")));
4936 bool const have_dvi = dviname.exists();
4937 bool const have_pdf = pdfname.exists();
4938 if (!have_dvi && !have_pdf) {
4939 dr.setMessage(_("Please, preview the document first."));
4942 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4943 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4944 string outname = dviname.onlyFileName();
4945 string command = lyxrc.forward_search_dvi;
4946 if ((!goto_dvi || goto_pdf) &&
4947 pdfname.lastModified() > dviname.lastModified()) {
4948 outname = pdfname.onlyFileName();
4949 command = lyxrc.forward_search_pdf;
4952 DocIterator cur = bv->cursor();
4953 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4954 LYXERR(Debug::ACTION, "Forward search: row:" << row
4956 if (row == -1 || command.empty()) {
4957 dr.setMessage(_("Couldn't proceed."));
4960 string texrow = convert<string>(row);
4962 command = subst(command, "$$n", texrow);
4963 command = subst(command, "$$f", fulltexname);
4964 command = subst(command, "$$t", texname);
4965 command = subst(command, "$$o", outname);
4967 volatile PathChanger p(path);
4969 one.startscript(Systemcall::DontWait, command);
4973 case LFUN_SPELLING_CONTINUOUSLY:
4974 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4975 dr.screenUpdate(Update::Force);
4978 case LFUN_CITATION_OPEN: {
4980 if (theFormats().getFormat("pdf"))
4981 pdfv = theFormats().getFormat("pdf")->viewer();
4982 if (theFormats().getFormat("ps"))
4983 psv = theFormats().getFormat("ps")->viewer();
4984 frontend::showTarget(argument, pdfv, psv);
4989 // The LFUN must be for one of BufferView, Buffer or Cursor;
4991 dispatchToBufferView(cmd, dr);
4995 // Need to update bv because many LFUNs here might have destroyed it
4996 bv = currentBufferView();
4998 // Clear non-empty selections
4999 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5001 Cursor & cur = bv->cursor();
5002 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5003 cur.clearSelection();
5009 bool GuiView::lfunUiToggle(string const & ui_component)
5011 if (ui_component == "scrollbar") {
5012 // hide() is of no help
5013 if (d.current_work_area_->verticalScrollBarPolicy() ==
5014 Qt::ScrollBarAlwaysOff)
5016 d.current_work_area_->setVerticalScrollBarPolicy(
5017 Qt::ScrollBarAsNeeded);
5019 d.current_work_area_->setVerticalScrollBarPolicy(
5020 Qt::ScrollBarAlwaysOff);
5021 } else if (ui_component == "statusbar") {
5022 statusBar()->setVisible(!statusBar()->isVisible());
5023 } else if (ui_component == "menubar") {
5024 menuBar()->setVisible(!menuBar()->isVisible());
5025 } else if (ui_component == "zoomlevel") {
5026 zoom_value_->setVisible(!zoom_value_->isVisible());
5027 } else if (ui_component == "zoomslider") {
5028 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5029 zoom_in_->setVisible(zoom_slider_->isVisible());
5030 zoom_out_->setVisible(zoom_slider_->isVisible());
5031 } else if (ui_component == "statistics-w") {
5032 word_count_enabled_ = !word_count_enabled_;
5035 } else if (ui_component == "statistics-cb") {
5036 char_count_enabled_ = !char_count_enabled_;
5039 } else if (ui_component == "statistics-c") {
5040 char_nb_count_enabled_ = !char_nb_count_enabled_;
5043 } else if (ui_component == "frame") {
5044 int const l = contentsMargins().left();
5046 //are the frames in default state?
5047 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5049 #if QT_VERSION > 0x050903
5050 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5052 setContentsMargins(-2, -2, -2, -2);
5054 #if QT_VERSION > 0x050903
5055 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5057 setContentsMargins(0, 0, 0, 0);
5060 if (ui_component == "fullscreen") {
5064 stat_counts_->setVisible(statsEnabled());
5069 void GuiView::toggleFullScreen()
5071 setWindowState(windowState() ^ Qt::WindowFullScreen);
5075 Buffer const * GuiView::updateInset(Inset const * inset)
5080 Buffer const * inset_buffer = &(inset->buffer());
5082 for (int i = 0; i != d.splitter_->count(); ++i) {
5083 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5086 Buffer const * buffer = &(wa->bufferView().buffer());
5087 if (inset_buffer == buffer)
5088 wa->scheduleRedraw(true);
5090 return inset_buffer;
5094 void GuiView::restartCaret()
5096 /* When we move around, or type, it's nice to be able to see
5097 * the caret immediately after the keypress.
5099 if (d.current_work_area_)
5100 d.current_work_area_->startBlinkingCaret();
5102 // Take this occasion to update the other GUI elements.
5108 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5110 if (d.current_work_area_)
5111 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5116 // This list should be kept in sync with the list of insets in
5117 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5118 // dialog should have the same name as the inset.
5119 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5120 // docs in LyXAction.cpp.
5122 char const * const dialognames[] = {
5124 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5125 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5126 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5127 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5128 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5129 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5130 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5131 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5133 char const * const * const end_dialognames =
5134 dialognames + (sizeof(dialognames) / sizeof(char *));
5138 cmpCStr(char const * name) : name_(name) {}
5139 bool operator()(char const * other) {
5140 return strcmp(other, name_) == 0;
5147 bool isValidName(string const & name)
5149 return find_if(dialognames, end_dialognames,
5150 cmpCStr(name.c_str())) != end_dialognames;
5156 void GuiView::resetDialogs()
5158 // Make sure that no LFUN uses any GuiView.
5159 guiApp->setCurrentView(nullptr);
5163 constructToolbars();
5164 guiApp->menus().fillMenuBar(menuBar(), this, false);
5165 d.layout_->updateContents(true);
5166 // Now update controls with current buffer.
5167 guiApp->setCurrentView(this);
5173 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5175 for (QObject * child: widget->children()) {
5176 if (child->inherits("QGroupBox")) {
5177 QGroupBox * box = (QGroupBox*) child;
5180 flatGroupBoxes(child, flag);
5186 Dialog * GuiView::find(string const & name, bool hide_it) const
5188 if (!isValidName(name))
5191 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5193 if (it != d.dialogs_.end()) {
5195 it->second->hideView();
5196 return it->second.get();
5202 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5204 Dialog * dialog = find(name, hide_it);
5205 if (dialog != nullptr)
5208 dialog = build(name);
5209 d.dialogs_[name].reset(dialog);
5210 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5211 // Force a uniform style for group boxes
5212 // On Mac non-flat works better, on Linux flat is standard
5213 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5215 if (lyxrc.allow_geometry_session)
5216 dialog->restoreSession();
5223 void GuiView::showDialog(string const & name, string const & sdata,
5226 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5230 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5236 const string name = fromqstr(qname);
5237 const string sdata = fromqstr(qdata);
5241 Dialog * dialog = findOrBuild(name, false);
5243 bool const visible = dialog->isVisibleView();
5244 dialog->showData(sdata);
5245 if (currentBufferView())
5246 currentBufferView()->editInset(name, inset);
5247 // We only set the focus to the new dialog if it was not yet
5248 // visible in order not to change the existing previous behaviour
5250 // activateWindow is needed for floating dockviews
5251 dialog->asQWidget()->raise();
5252 dialog->asQWidget()->activateWindow();
5253 if (dialog->wantInitialFocus())
5254 dialog->asQWidget()->setFocus();
5258 catch (ExceptionMessage const &) {
5266 bool GuiView::isDialogVisible(string const & name) const
5268 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5269 if (it == d.dialogs_.end())
5271 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5275 void GuiView::hideDialog(string const & name, Inset * inset)
5277 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5278 if (it == d.dialogs_.end())
5282 if (!currentBufferView())
5284 if (inset != currentBufferView()->editedInset(name))
5288 Dialog * const dialog = it->second.get();
5289 if (dialog->isVisibleView())
5291 if (currentBufferView())
5292 currentBufferView()->editInset(name, nullptr);
5296 void GuiView::disconnectDialog(string const & name)
5298 if (!isValidName(name))
5300 if (currentBufferView())
5301 currentBufferView()->editInset(name, nullptr);
5305 void GuiView::hideAll() const
5307 for(auto const & dlg_p : d.dialogs_)
5308 dlg_p.second->hideView();
5312 void GuiView::updateDialogs()
5314 for(auto const & dlg_p : d.dialogs_) {
5315 Dialog * dialog = dlg_p.second.get();
5317 if (dialog->needBufferOpen() && !documentBufferView())
5318 hideDialog(fromqstr(dialog->name()), nullptr);
5319 else if (dialog->isVisibleView())
5320 dialog->checkStatus();
5328 Dialog * GuiView::build(string const & name)
5330 return createDialog(*this, name);
5334 SEMenu::SEMenu(QWidget * parent)
5336 QAction * action = addAction(qt_("Disable Shell Escape"));
5337 connect(action, SIGNAL(triggered()),
5338 parent, SLOT(disableShellEscape()));
5342 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5344 if (event->button() == Qt::LeftButton) {
5349 } // namespace frontend
5352 #include "moc_GuiView.cpp"