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 QString imagedir = "images/";
163 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
164 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
165 if (svgRenderer.isValid()) {
166 splash_ = QPixmap(splashSize());
167 QPainter painter(&splash_);
168 svgRenderer.render(&painter);
169 splash_.setDevicePixelRatio(pixelRatio());
171 splash_ = getPixmap("images/", "banner", "png");
174 QPainter pain(&splash_);
175 pain.setPen(QColor(0, 0, 0));
176 qreal const fsize = fontSize();
179 qreal locscale = htextsize.toFloat(&ok);
182 QPointF const position = textPosition(false);
183 QPointF const hposition = textPosition(true);
184 QRectF const hrect(hposition, splashSize());
186 "widget pixel ratio: " << pixelRatio() <<
187 " splash pixel ratio: " << splashPixelRatio() <<
188 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
190 // The font used to display the version info
191 font.setStyleHint(QFont::SansSerif);
192 font.setWeight(QFont::Bold);
193 font.setPointSizeF(fsize);
195 pain.drawText(position, text);
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Normal);
199 font.setPointSizeF(hfsize);
200 // Check how long the logo gets with the current font
201 // and adapt if the font is running wider than what
203 GuiFontMetrics fm(font);
204 // Split the title into lines to measure the longest line
205 // in the current l7n.
206 QStringList titlesegs = htext.split('\n');
208 int hline = fm.maxHeight();
209 for (QString const & seg : titlesegs) {
210 if (fm.width(seg) > wline)
211 wline = fm.width(seg);
213 // The longest line in the reference font (for English)
214 // is 180. Calculate scale factor from that.
215 double const wscale = wline > 0 ? (180.0 / wline) : 1;
216 // Now do the same for the height (necessary for condensed fonts)
217 double const hscale = (34.0 / hline);
218 // take the lower of the two scale factors.
219 double const scale = min(wscale, hscale);
220 // Now rescale. Also consider l7n's offset factor.
221 font.setPointSizeF(hfsize * scale * locscale);
224 pain.drawText(hrect, Qt::AlignLeft, htext);
225 setFocusPolicy(Qt::StrongFocus);
228 void paintEvent(QPaintEvent *) override
230 int const w = width_;
231 int const h = height_;
232 int const x = (width() - w) / 2;
233 int const y = (height() - h) / 2;
235 "widget pixel ratio: " << pixelRatio() <<
236 " splash pixel ratio: " << splashPixelRatio() <<
237 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
239 pain.drawPixmap(x, y, w, h, splash_);
242 void keyPressEvent(QKeyEvent * ev) override
245 setKeySymbol(&sym, ev);
247 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
259 /// Current ratio between physical pixels and device-independent pixels
260 double pixelRatio() const {
261 return qt_scale_factor * devicePixelRatio();
264 qreal fontSize() const {
265 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
268 QPointF textPosition(bool const heading) const {
269 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
270 : QPointF(width_/2 - 18, height_/2 + 45);
273 QSize splashSize() const {
275 static_cast<unsigned int>(width_ * pixelRatio()),
276 static_cast<unsigned int>(height_ * pixelRatio()));
279 /// Ratio between physical pixels and device-independent pixels of splash image
280 double splashPixelRatio() const {
281 return splash_.devicePixelRatio();
286 /// Toolbar store providing access to individual toolbars by name.
287 typedef map<string, GuiToolbar *> ToolbarMap;
289 typedef shared_ptr<Dialog> DialogPtr;
294 class GuiView::GuiViewPrivate
297 GuiViewPrivate(GuiViewPrivate const &);
298 void operator=(GuiViewPrivate const &);
300 GuiViewPrivate(GuiView * gv)
301 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
302 layout_(nullptr), autosave_timeout_(5000),
305 // hardcode here the platform specific icon size
306 smallIconSize = 16; // scaling problems
307 normalIconSize = 20; // ok, default if iconsize.png is missing
308 bigIconSize = 26; // better for some math icons
309 hugeIconSize = 32; // better for hires displays
312 // if it exists, use width of iconsize.png as normal size
313 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
314 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
316 QImage image(toqstr(fn.absFileName()));
317 if (image.width() < int(smallIconSize))
318 normalIconSize = smallIconSize;
319 else if (image.width() > int(giantIconSize))
320 normalIconSize = giantIconSize;
322 normalIconSize = image.width();
325 splitter_ = new QSplitter;
326 bg_widget_ = new BackgroundWidget(400, 250);
327 stack_widget_ = new QStackedWidget;
328 stack_widget_->addWidget(bg_widget_);
329 stack_widget_->addWidget(splitter_);
332 // TODO cleanup, remove the singleton, handle multiple Windows?
333 progress_ = ProgressInterface::instance();
334 if (!dynamic_cast<GuiProgress*>(progress_)) {
335 progress_ = new GuiProgress; // TODO who deletes it
336 ProgressInterface::setInstance(progress_);
339 dynamic_cast<GuiProgress*>(progress_),
340 SIGNAL(updateStatusBarMessage(QString const&)),
341 gv, SLOT(updateStatusBarMessage(QString const&)));
343 dynamic_cast<GuiProgress*>(progress_),
344 SIGNAL(clearMessageText()),
345 gv, SLOT(clearMessageText()));
352 delete stack_widget_;
357 stack_widget_->setCurrentWidget(bg_widget_);
358 bg_widget_->setUpdatesEnabled(true);
359 bg_widget_->setFocus();
362 int tabWorkAreaCount()
364 return splitter_->count();
367 TabWorkArea * tabWorkArea(int i)
369 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
372 TabWorkArea * currentTabWorkArea()
374 int areas = tabWorkAreaCount();
376 // The first TabWorkArea is always the first one, if any.
377 return tabWorkArea(0);
379 for (int i = 0; i != areas; ++i) {
380 TabWorkArea * twa = tabWorkArea(i);
381 if (current_main_work_area_ == twa->currentWorkArea())
385 // None has the focus so we just take the first one.
386 return tabWorkArea(0);
389 int countWorkAreasOf(Buffer & buf)
391 int areas = tabWorkAreaCount();
393 for (int i = 0; i != areas; ++i) {
394 TabWorkArea * twa = tabWorkArea(i);
395 if (twa->workArea(buf))
401 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
403 if (processing_thread_watcher_.isRunning()) {
404 // we prefer to cancel this preview in order to keep a snappy
408 processing_thread_watcher_.setFuture(f);
411 QSize iconSize(docstring const & icon_size)
414 if (icon_size == "small")
415 size = smallIconSize;
416 else if (icon_size == "normal")
417 size = normalIconSize;
418 else if (icon_size == "big")
420 else if (icon_size == "huge")
422 else if (icon_size == "giant")
423 size = giantIconSize;
425 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
427 if (size < smallIconSize)
428 size = smallIconSize;
430 return QSize(size, size);
433 QSize iconSize(QString const & icon_size)
435 return iconSize(qstring_to_ucs4(icon_size));
438 string & iconSize(QSize const & qsize)
440 LATTEST(qsize.width() == qsize.height());
442 static string icon_size;
444 unsigned int size = qsize.width();
446 if (size < smallIconSize)
447 size = smallIconSize;
449 if (size == smallIconSize)
451 else if (size == normalIconSize)
452 icon_size = "normal";
453 else if (size == bigIconSize)
455 else if (size == hugeIconSize)
457 else if (size == giantIconSize)
460 icon_size = convert<string>(size);
465 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
466 Buffer * buffer, string const & format);
467 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
468 Buffer * buffer, string const & format);
469 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
470 Buffer * buffer, string const & format);
471 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
474 static Buffer::ExportStatus runAndDestroy(const T& func,
475 Buffer const * orig, Buffer * buffer, string const & format);
477 // TODO syncFunc/previewFunc: use bind
478 bool asyncBufferProcessing(string const & argument,
479 Buffer const * used_buffer,
480 docstring const & msg,
481 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
482 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
483 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
484 bool allow_async, bool use_tmpdir = false);
486 QVector<GuiWorkArea*> guiWorkAreas();
490 GuiWorkArea * current_work_area_;
491 GuiWorkArea * current_main_work_area_;
492 QSplitter * splitter_;
493 QStackedWidget * stack_widget_;
494 BackgroundWidget * bg_widget_;
496 ToolbarMap toolbars_;
497 ProgressInterface* progress_;
498 /// The main layout box.
500 * \warning Don't Delete! The layout box is actually owned by
501 * whichever toolbar contains it. All the GuiView class needs is a
502 * means of accessing it.
504 * FIXME: replace that with a proper model so that we are not limited
505 * to only one dialog.
510 map<string, DialogPtr> dialogs_;
513 QTimer statusbar_timer_;
514 QTimer statusbar_stats_timer_;
515 /// auto-saving of buffers
516 Timeout autosave_timeout_;
519 TocModels toc_models_;
522 QFutureWatcher<docstring> autosave_watcher_;
523 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
525 string last_export_format;
526 string processing_format;
528 static QSet<Buffer const *> busyBuffers;
530 unsigned int smallIconSize;
531 unsigned int normalIconSize;
532 unsigned int bigIconSize;
533 unsigned int hugeIconSize;
534 unsigned int giantIconSize;
536 /// flag against a race condition due to multiclicks, see bug #1119
539 // Timers for statistic updates in buffer
540 /// Current time left to the nearest info update
541 int time_to_update = 1000;
542 ///Basic step for timer in ms. Basically reaction time for short selections
543 int const timer_rate = 500;
544 /// Real stats updates infrequently. First they take long time for big buffers, second
545 /// they are visible for fast-repeat keyboards even for mid documents.
546 int const default_stats_rate = 5000;
547 /// Detection of new selection, so we can react fast
548 bool already_in_selection_ = false;
549 /// Maximum size of "short" selection for which we can update with faster timer_rate
550 int const max_sel_chars = 5000;
554 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
557 GuiView::GuiView(int id)
558 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
559 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
560 char_count_enabled_(true), char_nb_count_enabled_(false),
561 toolbarsMovable_(true), devel_mode_(false)
563 connect(this, SIGNAL(bufferViewChanged()),
564 this, SLOT(onBufferViewChanged()));
566 // GuiToolbars *must* be initialised before the menu bar.
567 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
570 // set ourself as the current view. This is needed for the menu bar
571 // filling, at least for the static special menu item on Mac. Otherwise
572 // they are greyed out.
573 guiApp->setCurrentView(this);
575 // Fill up the menu bar.
576 guiApp->menus().fillMenuBar(menuBar(), this, true);
578 setCentralWidget(d.stack_widget_);
580 // Start autosave timer
581 if (lyxrc.autosave) {
582 // The connection is closed when this is destroyed.
583 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
584 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
585 d.autosave_timeout_.start();
587 connect(&d.statusbar_timer_, SIGNAL(timeout()),
588 this, SLOT(clearMessage()));
589 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
590 this, SLOT(showStats()));
591 d.statusbar_stats_timer_.start(d.timer_rate);
593 // We don't want to keep the window in memory if it is closed.
594 setAttribute(Qt::WA_DeleteOnClose, true);
596 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
597 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
598 // since the icon is provided in the application bundle. We use a themed
599 // version when available and use the bundled one as fallback.
600 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
605 // use tabbed dock area for multiple docks
606 // (such as "source" and "messages")
607 setDockOptions(QMainWindow::ForceTabbedDocks);
610 // use document mode tabs on docks
611 setDocumentMode(true);
615 setAcceptDrops(true);
617 // add busy indicator to statusbar
618 search_mode mode = theGuiApp()->imageSearchMode();
619 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
620 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
621 statusBar()->addPermanentWidget(busySVG);
622 // make busy indicator square with 5px margins
623 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
626 connect(&d.processing_thread_watcher_, SIGNAL(started()),
627 busySVG, SLOT(show()));
628 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
629 busySVG, SLOT(hide()));
630 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
632 stat_counts_ = new GuiClickableLabel(statusBar());
633 stat_counts_->setAlignment(Qt::AlignCenter);
634 stat_counts_->setFrameStyle(QFrame::StyledPanel);
635 stat_counts_->hide();
636 statusBar()->addPermanentWidget(stat_counts_);
638 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
641 QFontMetrics const fm(statusBar()->fontMetrics());
643 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
644 // Small size slider for macOS to prevent the status bar from enlarging
645 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
646 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
647 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
649 zoom_slider_->setFixedWidth(fm.width('x') * 15);
651 // Make the defaultZoom center
652 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
653 // Initialize proper zoom value
655 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
656 // Actual zoom value: default zoom + fractional offset
657 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
658 zoom = min(max(zoom, zoom_min_), zoom_max_);
659 zoom_slider_->setValue(zoom);
660 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
662 // Buttons to change zoom stepwise
663 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
664 QSize s(fm.horizontalAdvance('+'), fm.height());
666 QSize s(fm.width('+'), fm.height());
668 zoom_in_ = new GuiClickableLabel(statusBar());
669 zoom_in_->setText("+");
670 zoom_in_->setFixedSize(s);
671 zoom_in_->setAlignment(Qt::AlignCenter);
672 zoom_out_ = new GuiClickableLabel(statusBar());
673 zoom_out_->setText(QString(QChar(0x2212)));
674 zoom_out_->setFixedSize(s);
675 zoom_out_->setAlignment(Qt::AlignCenter);
677 statusBar()->addPermanentWidget(zoom_out_);
678 zoom_out_->setEnabled(currentBufferView());
679 statusBar()->addPermanentWidget(zoom_slider_);
680 zoom_slider_->setEnabled(currentBufferView());
681 zoom_in_->setEnabled(currentBufferView());
682 statusBar()->addPermanentWidget(zoom_in_);
684 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
685 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
686 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
687 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
688 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
690 // QPalette palette = statusBar()->palette();
692 zoom_value_ = new GuiClickableLabel(statusBar());
693 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
694 // zoom_value_->setPalette(palette);
695 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
696 zoom_value_->setFixedHeight(fm.height());
697 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
698 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
700 zoom_value_->setMinimumWidth(fm.width("444\%"));
702 zoom_value_->setAlignment(Qt::AlignCenter);
703 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
704 statusBar()->addPermanentWidget(zoom_value_);
705 zoom_value_->setEnabled(currentBufferView());
707 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
708 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
709 this, SLOT(showStatusBarContextMenu()));
711 // enable pinch to zoom
712 grabGesture(Qt::PinchGesture);
714 int const iconheight = max(int(d.normalIconSize), fm.height());
715 QSize const iconsize(iconheight, iconheight);
717 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
718 shell_escape_ = new QLabel(statusBar());
719 shell_escape_->setPixmap(shellescape);
720 shell_escape_->setAlignment(Qt::AlignCenter);
721 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
722 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
723 "external commands for this document. "
724 "Right click to change."));
725 SEMenu * menu = new SEMenu(this);
726 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
727 menu, SLOT(showMenu(QPoint)));
728 shell_escape_->hide();
729 statusBar()->addPermanentWidget(shell_escape_);
731 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
732 read_only_ = new QLabel(statusBar());
733 read_only_->setPixmap(readonly);
734 read_only_->setAlignment(Qt::AlignCenter);
736 statusBar()->addPermanentWidget(read_only_);
738 version_control_ = new QLabel(statusBar());
739 version_control_->setAlignment(Qt::AlignCenter);
740 version_control_->setFrameStyle(QFrame::StyledPanel);
741 version_control_->hide();
742 statusBar()->addPermanentWidget(version_control_);
744 statusBar()->setSizeGripEnabled(true);
747 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
748 SLOT(autoSaveThreadFinished()));
750 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
751 SLOT(processingThreadStarted()));
752 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
753 SLOT(processingThreadFinished()));
755 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
756 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
758 // set custom application bars context menu, e.g. tool bar and menu bar
759 setContextMenuPolicy(Qt::CustomContextMenu);
760 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
761 SLOT(toolBarPopup(const QPoint &)));
763 // Forbid too small unresizable window because it can happen
764 // with some window manager under X11.
765 setMinimumSize(300, 200);
767 if (lyxrc.allow_geometry_session) {
768 // Now take care of session management.
773 // no session handling, default to a sane size.
774 setGeometry(50, 50, 690, 510);
777 // clear session data if any.
778 settings.remove("views");
788 void GuiView::disableShellEscape()
790 BufferView * bv = documentBufferView();
793 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
794 bv->buffer().params().shell_escape = false;
795 bv->processUpdateFlags(Update::Force);
799 void GuiView::checkCancelBackground()
801 docstring const ttl = _("Cancel Export?");
802 docstring const msg = _("Do you want to cancel the background export process?");
804 Alert::prompt(ttl, msg, 1, 1,
805 _("&Cancel export"), _("Co&ntinue"));
807 Systemcall::killscript();
810 void GuiView::statsPressed()
813 dispatch(FuncRequest(LFUN_STATISTICS), dr);
816 void GuiView::zoomSliderMoved(int value)
819 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
820 scheduleRedrawWorkAreas();
821 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
825 void GuiView::zoomValueChanged(int value)
827 if (value != lyxrc.currentZoom)
828 zoomSliderMoved(value);
832 void GuiView::zoomInPressed()
835 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
836 scheduleRedrawWorkAreas();
840 void GuiView::zoomOutPressed()
843 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
844 scheduleRedrawWorkAreas();
848 void GuiView::showZoomContextMenu()
850 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
853 menu->exec(QCursor::pos());
857 void GuiView::showStatusBarContextMenu()
859 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
862 menu->exec(QCursor::pos());
866 void GuiView::scheduleRedrawWorkAreas()
868 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
869 TabWorkArea* ta = d.tabWorkArea(i);
870 for (int u = 0; u < ta->count(); u++) {
871 ta->workArea(u)->scheduleRedraw(true);
877 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
879 QVector<GuiWorkArea*> areas;
880 for (int i = 0; i < tabWorkAreaCount(); i++) {
881 TabWorkArea* ta = tabWorkArea(i);
882 for (int u = 0; u < ta->count(); u++) {
883 areas << ta->workArea(u);
889 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
890 string const & format)
892 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
895 case Buffer::ExportSuccess:
896 msg = bformat(_("Successful export to format: %1$s"), fmt);
898 case Buffer::ExportCancel:
899 msg = _("Document export cancelled.");
901 case Buffer::ExportError:
902 case Buffer::ExportNoPathToFormat:
903 case Buffer::ExportTexPathHasSpaces:
904 case Buffer::ExportConverterError:
905 msg = bformat(_("Error while exporting format: %1$s"), fmt);
907 case Buffer::PreviewSuccess:
908 msg = bformat(_("Successful preview of format: %1$s"), fmt);
910 case Buffer::PreviewError:
911 msg = bformat(_("Error while previewing format: %1$s"), fmt);
913 case Buffer::ExportKilled:
914 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
921 void GuiView::processingThreadStarted()
926 void GuiView::processingThreadFinished()
928 QFutureWatcher<Buffer::ExportStatus> const * watcher =
929 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
931 Buffer::ExportStatus const status = watcher->result();
932 handleExportStatus(this, status, d.processing_format);
935 BufferView const * const bv = currentBufferView();
936 if (bv && !bv->buffer().errorList("Export").empty()) {
941 bool const error = (status != Buffer::ExportSuccess &&
942 status != Buffer::PreviewSuccess &&
943 status != Buffer::ExportCancel);
945 ErrorList & el = bv->buffer().errorList(d.last_export_format);
946 // at this point, we do not know if buffer-view or
947 // master-buffer-view was called. If there was an export error,
948 // and the current buffer's error log is empty, we guess that
949 // it must be master-buffer-view that was called so we set
951 errors(d.last_export_format, el.empty());
956 void GuiView::autoSaveThreadFinished()
958 QFutureWatcher<docstring> const * watcher =
959 static_cast<QFutureWatcher<docstring> const *>(sender());
960 message(watcher->result());
965 void GuiView::saveLayout() const
968 settings.setValue("zoom_ratio", zoom_ratio_);
969 settings.setValue("devel_mode", devel_mode_);
970 settings.beginGroup("views");
971 settings.beginGroup(QString::number(id_));
972 if (guiApp->platformName() == "xcb") {
973 settings.setValue("pos", pos());
974 settings.setValue("size", size());
976 settings.setValue("geometry", saveGeometry());
977 settings.setValue("layout", saveState(0));
978 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
979 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
980 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
981 settings.setValue("word_count_enabled", word_count_enabled_);
982 settings.setValue("char_count_enabled", char_count_enabled_);
983 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
987 void GuiView::saveUISettings() const
991 // Save the toolbar private states
992 for (auto const & tb_p : d.toolbars_)
993 tb_p.second->saveSession(settings);
994 // Now take care of all other dialogs
995 for (auto const & dlg_p : d.dialogs_)
996 dlg_p.second->saveSession(settings);
1000 void GuiView::setCurrentZoom(const int v)
1002 lyxrc.currentZoom = v;
1003 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1004 Q_EMIT currentZoomChanged(v);
1008 bool GuiView::restoreLayout()
1011 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1012 // Actual zoom value: default zoom + fractional offset
1013 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1014 zoom = min(max(zoom, zoom_min_), zoom_max_);
1015 setCurrentZoom(zoom);
1016 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1017 settings.beginGroup("views");
1018 settings.beginGroup(QString::number(id_));
1019 QString const icon_key = "icon_size";
1020 if (!settings.contains(icon_key))
1023 //code below is skipped when when ~/.config/LyX is (re)created
1024 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1026 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1028 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1029 zoom_slider_->setVisible(show_zoom_slider);
1030 zoom_in_->setVisible(show_zoom_slider);
1031 zoom_out_->setVisible(show_zoom_slider);
1033 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1034 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1035 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1036 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1038 if (guiApp->platformName() == "xcb") {
1039 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1040 QSize size = settings.value("size", QSize(690, 510)).toSize();
1044 // Work-around for bug #6034: the window ends up in an undetermined
1045 // state when trying to restore a maximized window when it is
1046 // already maximized.
1047 if (!(windowState() & Qt::WindowMaximized))
1048 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1049 setGeometry(50, 50, 690, 510);
1052 // Make sure layout is correctly oriented.
1053 setLayoutDirection(qApp->layoutDirection());
1055 // Allow the toc and view-source dock widget to be restored if needed.
1057 if ((dialog = findOrBuild("toc", true)))
1058 // see bug 5082. At least setup title and enabled state.
1059 // Visibility will be adjusted by restoreState below.
1060 dialog->prepareView();
1061 if ((dialog = findOrBuild("view-source", true)))
1062 dialog->prepareView();
1063 if ((dialog = findOrBuild("progress", true)))
1064 dialog->prepareView();
1066 if (!restoreState(settings.value("layout").toByteArray(), 0))
1069 // init the toolbars that have not been restored
1070 for (auto const & tb_p : guiApp->toolbars()) {
1071 GuiToolbar * tb = toolbar(tb_p.name);
1072 if (tb && !tb->isRestored())
1073 initToolbar(tb_p.name);
1076 // update lock (all) toolbars positions
1077 updateLockToolbars();
1084 GuiToolbar * GuiView::toolbar(string const & name)
1086 ToolbarMap::iterator it = d.toolbars_.find(name);
1087 if (it != d.toolbars_.end())
1090 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1095 void GuiView::updateLockToolbars()
1097 toolbarsMovable_ = false;
1098 for (ToolbarInfo const & info : guiApp->toolbars()) {
1099 GuiToolbar * tb = toolbar(info.name);
1100 if (tb && tb->isMovable())
1101 toolbarsMovable_ = true;
1103 #if QT_VERSION >= 0x050200
1104 // set unified mac toolbars only when not movable as recommended:
1105 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1106 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1111 void GuiView::constructToolbars()
1113 for (auto const & tb_p : d.toolbars_)
1115 d.toolbars_.clear();
1117 // I don't like doing this here, but the standard toolbar
1118 // destroys this object when it's destroyed itself (vfr)
1119 d.layout_ = new LayoutBox(*this);
1120 d.stack_widget_->addWidget(d.layout_);
1121 d.layout_->move(0,0);
1123 // extracts the toolbars from the backend
1124 for (ToolbarInfo const & inf : guiApp->toolbars())
1125 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1127 DynamicMenuButton::resetIconCache();
1131 void GuiView::initToolbars()
1133 // extracts the toolbars from the backend
1134 for (ToolbarInfo const & inf : guiApp->toolbars())
1135 initToolbar(inf.name);
1139 void GuiView::initToolbar(string const & name)
1141 GuiToolbar * tb = toolbar(name);
1144 int const visibility = guiApp->toolbars().defaultVisibility(name);
1145 bool newline = !(visibility & Toolbars::SAMEROW);
1146 tb->setVisible(false);
1147 tb->setVisibility(visibility);
1149 if (visibility & Toolbars::TOP) {
1151 addToolBarBreak(Qt::TopToolBarArea);
1152 addToolBar(Qt::TopToolBarArea, tb);
1155 if (visibility & Toolbars::BOTTOM) {
1157 addToolBarBreak(Qt::BottomToolBarArea);
1158 addToolBar(Qt::BottomToolBarArea, tb);
1161 if (visibility & Toolbars::LEFT) {
1163 addToolBarBreak(Qt::LeftToolBarArea);
1164 addToolBar(Qt::LeftToolBarArea, tb);
1167 if (visibility & Toolbars::RIGHT) {
1169 addToolBarBreak(Qt::RightToolBarArea);
1170 addToolBar(Qt::RightToolBarArea, tb);
1173 if (visibility & Toolbars::ON)
1174 tb->setVisible(true);
1176 tb->setMovable(true);
1180 TocModels & GuiView::tocModels()
1182 return d.toc_models_;
1186 void GuiView::setFocus()
1188 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1189 QMainWindow::setFocus();
1193 bool GuiView::hasFocus() const
1195 if (currentWorkArea())
1196 return currentWorkArea()->hasFocus();
1197 if (currentMainWorkArea())
1198 return currentMainWorkArea()->hasFocus();
1199 return d.bg_widget_->hasFocus();
1203 void GuiView::focusInEvent(QFocusEvent * e)
1205 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1206 QMainWindow::focusInEvent(e);
1207 // Make sure guiApp points to the correct view.
1208 guiApp->setCurrentView(this);
1209 if (currentWorkArea())
1210 currentWorkArea()->setFocus();
1211 else if (currentMainWorkArea())
1212 currentMainWorkArea()->setFocus();
1214 d.bg_widget_->setFocus();
1218 void GuiView::showEvent(QShowEvent * e)
1220 LYXERR(Debug::GUI, "Passed Geometry "
1221 << size().height() << "x" << size().width()
1222 << "+" << pos().x() << "+" << pos().y());
1224 if (d.splitter_->count() == 0)
1225 // No work area, switch to the background widget.
1229 QMainWindow::showEvent(e);
1233 bool GuiView::closeScheduled()
1240 bool GuiView::prepareAllBuffersForLogout()
1242 Buffer * first = theBufferList().first();
1246 // First, iterate over all buffers and ask the users if unsaved
1247 // changes should be saved.
1248 // We cannot use a for loop as the buffer list cycles.
1251 if (!saveBufferIfNeeded(*b, false))
1253 b = theBufferList().next(b);
1254 } while (b != first);
1256 // Next, save session state
1257 // When a view/window was closed before without quitting LyX, there
1258 // are already entries in the lastOpened list.
1259 theSession().lastOpened().clear();
1266 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1267 ** is responsibility of the container (e.g., dialog)
1269 void GuiView::closeEvent(QCloseEvent * close_event)
1271 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1273 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1274 Alert::warning(_("Exit LyX"),
1275 _("LyX could not be closed because documents are being processed by LyX."));
1276 close_event->setAccepted(false);
1280 // If the user pressed the x (so we didn't call closeView
1281 // programmatically), we want to clear all existing entries.
1283 theSession().lastOpened().clear();
1288 // it can happen that this event arrives without selecting the view,
1289 // e.g. when clicking the close button on a background window.
1291 if (!closeWorkAreaAll()) {
1293 close_event->ignore();
1297 // Make sure that nothing will use this to be closed View.
1298 guiApp->unregisterView(this);
1300 if (isFullScreen()) {
1301 // Switch off fullscreen before closing.
1306 // Make sure the timer time out will not trigger a statusbar update.
1307 d.statusbar_timer_.stop();
1308 d.statusbar_stats_timer_.stop();
1310 // Saving fullscreen requires additional tweaks in the toolbar code.
1311 // It wouldn't also work under linux natively.
1312 if (lyxrc.allow_geometry_session) {
1317 close_event->accept();
1321 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1323 if (event->mimeData()->hasUrls())
1325 /// \todo Ask lyx-devel is this is enough:
1326 /// if (event->mimeData()->hasFormat("text/plain"))
1327 /// event->acceptProposedAction();
1331 void GuiView::dropEvent(QDropEvent * event)
1333 QList<QUrl> files = event->mimeData()->urls();
1334 if (files.isEmpty())
1337 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1338 for (int i = 0; i != files.size(); ++i) {
1339 string const file = os::internal_path(fromqstr(
1340 files.at(i).toLocalFile()));
1344 string const ext = support::getExtension(file);
1345 vector<const Format *> found_formats;
1347 // Find all formats that have the correct extension.
1348 for (const Format * fmt : theConverters().importableFormats())
1349 if (fmt->hasExtension(ext))
1350 found_formats.push_back(fmt);
1353 if (!found_formats.empty()) {
1354 if (found_formats.size() > 1) {
1355 //FIXME: show a dialog to choose the correct importable format
1356 LYXERR(Debug::FILES,
1357 "Multiple importable formats found, selecting first");
1359 string const arg = found_formats[0]->name() + " " + file;
1360 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1363 //FIXME: do we have to explicitly check whether it's a lyx file?
1364 LYXERR(Debug::FILES,
1365 "No formats found, trying to open it as a lyx file");
1366 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1368 // add the functions to the queue
1369 guiApp->addToFuncRequestQueue(cmd);
1372 // now process the collected functions. We perform the events
1373 // asynchronously. This prevents potential problems in case the
1374 // BufferView is closed within an event.
1375 guiApp->processFuncRequestQueueAsync();
1379 void GuiView::message(docstring const & str)
1381 if (ForkedProcess::iAmAChild())
1384 // call is moved to GUI-thread by GuiProgress
1385 d.progress_->appendMessage(toqstr(str));
1389 void GuiView::clearMessageText()
1391 message(docstring());
1395 void GuiView::updateStatusBarMessage(QString const & str)
1397 statusBar()->showMessage(str);
1398 d.statusbar_timer_.stop();
1399 d.statusbar_timer_.start(3000);
1403 void GuiView::clearMessage()
1405 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1406 // the hasFocus function mostly returns false, even if the focus is on
1407 // a workarea in this view.
1411 d.statusbar_timer_.stop();
1414 void GuiView::showStats()
1416 if (!statsEnabled())
1419 d.time_to_update -= d.timer_rate;
1421 BufferView * bv = currentBufferView();
1422 Buffer * buf = bv ? &bv->buffer() : nullptr;
1424 stat_counts_->hide();
1428 Cursor const & cur = bv->cursor();
1430 // we start new selection and need faster update
1431 if (!d.already_in_selection_ && cur.selection())
1432 d.time_to_update = 0;
1434 if (d.time_to_update > 0)
1437 DocIterator from, to;
1438 if (cur.selection()) {
1439 from = cur.selectionBegin();
1440 to = cur.selectionEnd();
1441 d.already_in_selection_ = true;
1443 from = doc_iterator_begin(buf);
1444 to = doc_iterator_end(buf);
1445 d.already_in_selection_ = false;
1448 buf->updateStatistics(from, to);
1451 if (word_count_enabled_) {
1452 int const words = buf->wordCount();
1454 stats << toqstr(bformat(_("%1$d Word"), words));
1456 stats << toqstr(bformat(_("%1$d Words"), words));
1458 int const chars_with_blanks = buf->charCount(true);
1459 if (char_count_enabled_) {
1460 if (chars_with_blanks == 1)
1461 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1463 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1465 if (char_nb_count_enabled_) {
1466 int const chars = buf->charCount(false);
1468 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1470 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1472 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1473 stat_counts_->show();
1475 d.time_to_update = d.default_stats_rate;
1476 // fast updates for small selections
1477 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1478 d.time_to_update = d.timer_rate;
1482 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1484 if (wa != d.current_work_area_
1485 || wa->bufferView().buffer().isInternal())
1487 Buffer const & buf = wa->bufferView().buffer();
1488 // Set the windows title
1489 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1490 if (buf.notifiesExternalModification()) {
1491 title = bformat(_("%1$s (modified externally)"), title);
1492 // If the external modification status has changed, then maybe the status of
1493 // buffer-save has changed too.
1496 title += from_ascii(" - LyX");
1497 setWindowTitle(toqstr(title));
1498 // Sets the path for the window: this is used by OSX to
1499 // allow a context click on the title bar showing a menu
1500 // with the path up to the file
1501 setWindowFilePath(toqstr(buf.absFileName()));
1502 // Tell Qt whether the current document is changed
1503 setWindowModified(!buf.isClean());
1505 if (buf.params().shell_escape)
1506 shell_escape_->show();
1508 shell_escape_->hide();
1510 if (buf.hasReadonlyFlag())
1515 if (buf.lyxvc().inUse()) {
1516 version_control_->show();
1517 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1519 version_control_->hide();
1523 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1525 if (d.current_work_area_)
1526 // disconnect the current work area from all slots
1527 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1529 disconnectBufferView();
1530 connectBufferView(wa->bufferView());
1531 connectBuffer(wa->bufferView().buffer());
1532 d.current_work_area_ = wa;
1533 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1534 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1535 QObject::connect(wa, SIGNAL(busy(bool)),
1536 this, SLOT(setBusy(bool)));
1537 // connection of a signal to a signal
1538 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1539 this, SIGNAL(bufferViewChanged()));
1540 Q_EMIT updateWindowTitle(wa);
1541 Q_EMIT bufferViewChanged();
1545 void GuiView::onBufferViewChanged()
1548 // Buffer-dependent dialogs must be updated. This is done here because
1549 // some dialogs require buffer()->text.
1551 zoom_slider_->setEnabled(currentBufferView());
1552 zoom_value_->setEnabled(currentBufferView());
1553 zoom_in_->setEnabled(currentBufferView());
1554 zoom_out_->setEnabled(currentBufferView());
1558 void GuiView::on_lastWorkAreaRemoved()
1561 // We already are in a close event. Nothing more to do.
1564 if (d.splitter_->count() > 1)
1565 // We have a splitter so don't close anything.
1568 // Reset and updates the dialogs.
1569 Q_EMIT bufferViewChanged();
1574 if (lyxrc.open_buffers_in_tabs)
1575 // Nothing more to do, the window should stay open.
1578 if (guiApp->viewIds().size() > 1) {
1584 // On Mac we also close the last window because the application stay
1585 // resident in memory. On other platforms we don't close the last
1586 // window because this would quit the application.
1592 void GuiView::updateStatusBar()
1594 // let the user see the explicit message
1595 if (d.statusbar_timer_.isActive())
1602 void GuiView::showMessage()
1606 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1607 if (msg.isEmpty()) {
1608 BufferView const * bv = currentBufferView();
1610 msg = toqstr(bv->cursor().currentState(devel_mode_));
1612 msg = qt_("Welcome to LyX!");
1614 statusBar()->showMessage(msg);
1618 bool GuiView::statsEnabled() const
1620 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1624 bool GuiView::event(QEvent * e)
1628 // Useful debug code:
1629 //case QEvent::ActivationChange:
1630 //case QEvent::WindowDeactivate:
1631 //case QEvent::Paint:
1632 //case QEvent::Enter:
1633 //case QEvent::Leave:
1634 //case QEvent::HoverEnter:
1635 //case QEvent::HoverLeave:
1636 //case QEvent::HoverMove:
1637 //case QEvent::StatusTip:
1638 //case QEvent::DragEnter:
1639 //case QEvent::DragLeave:
1640 //case QEvent::Drop:
1643 case QEvent::WindowStateChange: {
1644 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1645 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1646 bool result = QMainWindow::event(e);
1647 bool nfstate = (windowState() & Qt::WindowFullScreen);
1648 if (!ofstate && nfstate) {
1649 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1650 // switch to full-screen state
1651 if (lyxrc.full_screen_statusbar)
1652 statusBar()->hide();
1653 if (lyxrc.full_screen_menubar)
1655 if (lyxrc.full_screen_toolbars) {
1656 for (auto const & tb_p : d.toolbars_)
1657 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1658 tb_p.second->hide();
1660 for (int i = 0; i != d.splitter_->count(); ++i)
1661 d.tabWorkArea(i)->setFullScreen(true);
1662 #if QT_VERSION > 0x050903
1663 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1664 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1666 setContentsMargins(-2, -2, -2, -2);
1668 hideDialogs("prefs", nullptr);
1669 } else if (ofstate && !nfstate) {
1670 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1671 // switch back from full-screen state
1672 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1673 statusBar()->show();
1674 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1676 if (lyxrc.full_screen_toolbars) {
1677 for (auto const & tb_p : d.toolbars_)
1678 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1679 tb_p.second->show();
1682 for (int i = 0; i != d.splitter_->count(); ++i)
1683 d.tabWorkArea(i)->setFullScreen(false);
1684 #if QT_VERSION > 0x050903
1685 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1687 setContentsMargins(0, 0, 0, 0);
1692 case QEvent::WindowActivate: {
1693 GuiView * old_view = guiApp->currentView();
1694 if (this == old_view) {
1696 return QMainWindow::event(e);
1698 if (old_view && old_view->currentBufferView()) {
1699 // save current selection to the selection buffer to allow
1700 // middle-button paste in this window.
1701 cap::saveSelection(old_view->currentBufferView()->cursor());
1703 guiApp->setCurrentView(this);
1704 if (d.current_work_area_)
1705 on_currentWorkAreaChanged(d.current_work_area_);
1709 return QMainWindow::event(e);
1712 case QEvent::ShortcutOverride: {
1714 if (isFullScreen() && menuBar()->isHidden()) {
1715 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1716 // FIXME: we should also try to detect special LyX shortcut such as
1717 // Alt-P and Alt-M. Right now there is a hack in
1718 // GuiWorkArea::processKeySym() that hides again the menubar for
1720 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1722 return QMainWindow::event(e);
1725 return QMainWindow::event(e);
1728 case QEvent::ApplicationPaletteChange: {
1729 // runtime switch from/to dark mode
1731 return QMainWindow::event(e);
1734 case QEvent::Gesture: {
1735 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1736 QGesture *gp = ge->gesture(Qt::PinchGesture);
1738 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1739 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1740 qreal totalScaleFactor = pinch->totalScaleFactor();
1741 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1742 if (pinch->state() == Qt::GestureStarted) {
1743 initialZoom_ = lyxrc.currentZoom;
1744 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1746 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1747 qreal factor = initialZoom_ * totalScaleFactor;
1748 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1749 zoomValueChanged(factor);
1752 return QMainWindow::event(e);
1756 return QMainWindow::event(e);
1760 void GuiView::resetWindowTitle()
1762 setWindowTitle(qt_("LyX"));
1765 bool GuiView::focusNextPrevChild(bool /*next*/)
1772 bool GuiView::busy() const
1778 void GuiView::setBusy(bool busy)
1780 bool const busy_before = busy_ > 0;
1781 busy ? ++busy_ : --busy_;
1782 if ((busy_ > 0) == busy_before)
1783 // busy state didn't change
1787 QApplication::setOverrideCursor(Qt::WaitCursor);
1790 QApplication::restoreOverrideCursor();
1795 void GuiView::resetCommandExecute()
1797 command_execute_ = false;
1802 double GuiView::pixelRatio() const
1804 return qt_scale_factor * devicePixelRatio();
1808 GuiWorkArea * GuiView::workArea(int index)
1810 if (TabWorkArea * twa = d.currentTabWorkArea())
1811 if (index < twa->count())
1812 return twa->workArea(index);
1817 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1819 if (currentWorkArea()
1820 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1821 return currentWorkArea();
1822 if (TabWorkArea * twa = d.currentTabWorkArea())
1823 return twa->workArea(buffer);
1828 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1830 // Automatically create a TabWorkArea if there are none yet.
1831 TabWorkArea * tab_widget = d.splitter_->count()
1832 ? d.currentTabWorkArea() : addTabWorkArea();
1833 return tab_widget->addWorkArea(buffer, *this);
1837 TabWorkArea * GuiView::addTabWorkArea()
1839 TabWorkArea * twa = new TabWorkArea;
1840 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1841 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1842 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1843 this, SLOT(on_lastWorkAreaRemoved()));
1845 d.splitter_->addWidget(twa);
1846 d.stack_widget_->setCurrentWidget(d.splitter_);
1851 GuiWorkArea const * GuiView::currentWorkArea() const
1853 return d.current_work_area_;
1857 GuiWorkArea * GuiView::currentWorkArea()
1859 return d.current_work_area_;
1863 GuiWorkArea const * GuiView::currentMainWorkArea() const
1865 if (!d.currentTabWorkArea())
1867 return d.currentTabWorkArea()->currentWorkArea();
1871 GuiWorkArea * GuiView::currentMainWorkArea()
1873 if (!d.currentTabWorkArea())
1875 return d.currentTabWorkArea()->currentWorkArea();
1879 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1881 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1883 d.current_work_area_ = nullptr;
1885 Q_EMIT bufferViewChanged();
1889 // FIXME: I've no clue why this is here and why it accesses
1890 // theGuiApp()->currentView, which might be 0 (bug 6464).
1891 // See also 27525 (vfr).
1892 if (theGuiApp()->currentView() == this
1893 && theGuiApp()->currentView()->currentWorkArea() == wa)
1896 if (currentBufferView())
1897 cap::saveSelection(currentBufferView()->cursor());
1899 theGuiApp()->setCurrentView(this);
1900 d.current_work_area_ = wa;
1902 // We need to reset this now, because it will need to be
1903 // right if the tabWorkArea gets reset in the for loop. We
1904 // will change it back if we aren't in that case.
1905 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1906 d.current_main_work_area_ = wa;
1908 for (int i = 0; i != d.splitter_->count(); ++i) {
1909 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1910 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1911 << ", Current main wa: " << currentMainWorkArea());
1916 d.current_main_work_area_ = old_cmwa;
1918 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1919 on_currentWorkAreaChanged(wa);
1920 BufferView & bv = wa->bufferView();
1921 bv.cursor().fixIfBroken();
1923 wa->setUpdatesEnabled(true);
1924 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1928 void GuiView::removeWorkArea(GuiWorkArea * wa)
1930 LASSERT(wa, return);
1931 if (wa == d.current_work_area_) {
1933 disconnectBufferView();
1934 d.current_work_area_ = nullptr;
1935 d.current_main_work_area_ = nullptr;
1938 bool found_twa = false;
1939 for (int i = 0; i != d.splitter_->count(); ++i) {
1940 TabWorkArea * twa = d.tabWorkArea(i);
1941 if (twa->removeWorkArea(wa)) {
1942 // Found in this tab group, and deleted the GuiWorkArea.
1944 if (twa->count() != 0) {
1945 if (d.current_work_area_ == nullptr)
1946 // This means that we are closing the current GuiWorkArea, so
1947 // switch to the next GuiWorkArea in the found TabWorkArea.
1948 setCurrentWorkArea(twa->currentWorkArea());
1950 // No more WorkAreas in this tab group, so delete it.
1957 // It is not a tabbed work area (i.e., the search work area), so it
1958 // should be deleted by other means.
1959 LASSERT(found_twa, return);
1961 if (d.current_work_area_ == nullptr) {
1962 if (d.splitter_->count() != 0) {
1963 TabWorkArea * twa = d.currentTabWorkArea();
1964 setCurrentWorkArea(twa->currentWorkArea());
1966 // No more work areas, switch to the background widget.
1967 setCurrentWorkArea(nullptr);
1973 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1975 for (int i = 0; i < d.splitter_->count(); ++i)
1976 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1979 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1980 return fr->isVisible() && fr->hasWorkArea(wa);
1984 LayoutBox * GuiView::getLayoutDialog() const
1990 void GuiView::updateLayoutList()
1993 d.layout_->updateContents(false);
1997 void GuiView::updateToolbars()
1999 if (d.current_work_area_) {
2001 if (d.current_work_area_->bufferView().cursor().inMathed()
2002 && !d.current_work_area_->bufferView().cursor().inRegexped())
2003 context |= Toolbars::MATH;
2004 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2005 context |= Toolbars::TABLE;
2006 if (currentBufferView()->buffer().areChangesPresent()
2007 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2008 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2009 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2010 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2011 context |= Toolbars::REVIEW;
2012 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2013 context |= Toolbars::MATHMACROTEMPLATE;
2014 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2015 context |= Toolbars::IPA;
2016 if (command_execute_)
2017 context |= Toolbars::MINIBUFFER;
2018 if (minibuffer_focus_) {
2019 context |= Toolbars::MINIBUFFER_FOCUS;
2020 minibuffer_focus_ = false;
2023 for (auto const & tb_p : d.toolbars_)
2024 tb_p.second->update(context);
2026 for (auto const & tb_p : d.toolbars_)
2027 tb_p.second->update();
2031 void GuiView::refillToolbars()
2033 DynamicMenuButton::resetIconCache();
2034 for (auto const & tb_p : d.toolbars_)
2035 tb_p.second->refill();
2039 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2041 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2042 LASSERT(newBuffer, return);
2044 GuiWorkArea * wa = workArea(*newBuffer);
2045 if (wa == nullptr) {
2047 newBuffer->masterBuffer()->updateBuffer();
2049 wa = addWorkArea(*newBuffer);
2050 // scroll to the position when the BufferView was last closed
2051 if (lyxrc.use_lastfilepos) {
2052 LastFilePosSection::FilePos filepos =
2053 theSession().lastFilePos().load(newBuffer->fileName());
2054 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2057 //Disconnect the old buffer...there's no new one.
2060 connectBuffer(*newBuffer);
2061 connectBufferView(wa->bufferView());
2063 setCurrentWorkArea(wa);
2067 void GuiView::connectBuffer(Buffer & buf)
2069 buf.setGuiDelegate(this);
2073 void GuiView::disconnectBuffer()
2075 if (d.current_work_area_)
2076 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2080 void GuiView::connectBufferView(BufferView & bv)
2082 bv.setGuiDelegate(this);
2086 void GuiView::disconnectBufferView()
2088 if (d.current_work_area_)
2089 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2093 void GuiView::errors(string const & error_type, bool from_master)
2095 BufferView const * const bv = currentBufferView();
2099 ErrorList const & el = from_master ?
2100 bv->buffer().masterBuffer()->errorList(error_type) :
2101 bv->buffer().errorList(error_type);
2106 string err = error_type;
2108 err = "from_master|" + error_type;
2109 showDialog("errorlist", err);
2113 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2115 d.toc_models_.updateItem(toqstr(type), dit);
2119 void GuiView::structureChanged()
2121 // This is called from the Buffer, which has no way to ensure that cursors
2122 // in BufferView remain valid.
2123 if (documentBufferView())
2124 documentBufferView()->cursor().sanitize();
2125 // FIXME: This is slightly expensive, though less than the tocBackend update
2126 // (#9880). This also resets the view in the Toc Widget (#6675).
2127 d.toc_models_.reset(documentBufferView());
2128 // Navigator needs more than a simple update in this case. It needs to be
2130 updateDialog("toc", "");
2134 void GuiView::updateDialog(string const & name, string const & sdata)
2136 if (!isDialogVisible(name))
2139 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2140 if (it == d.dialogs_.end())
2143 Dialog * const dialog = it->second.get();
2144 if (dialog->isVisibleView())
2145 dialog->initialiseParams(sdata);
2149 BufferView * GuiView::documentBufferView()
2151 return currentMainWorkArea()
2152 ? ¤tMainWorkArea()->bufferView()
2157 BufferView const * GuiView::documentBufferView() const
2159 return currentMainWorkArea()
2160 ? ¤tMainWorkArea()->bufferView()
2165 BufferView * GuiView::currentBufferView()
2167 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2171 BufferView const * GuiView::currentBufferView() const
2173 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2177 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2178 Buffer const * orig, Buffer * clone)
2180 bool const success = clone->autoSave();
2182 busyBuffers.remove(orig);
2184 ? _("Automatic save done.")
2185 : _("Automatic save failed!");
2189 void GuiView::autoSave()
2191 LYXERR(Debug::INFO, "Running autoSave()");
2193 Buffer * buffer = documentBufferView()
2194 ? &documentBufferView()->buffer() : nullptr;
2196 resetAutosaveTimers();
2200 GuiViewPrivate::busyBuffers.insert(buffer);
2201 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2202 buffer, buffer->cloneBufferOnly());
2203 d.autosave_watcher_.setFuture(f);
2204 resetAutosaveTimers();
2208 void GuiView::resetAutosaveTimers()
2211 d.autosave_timeout_.restart();
2217 double zoomRatio(FuncRequest const & cmd, double const zr)
2219 if (cmd.argument().empty()) {
2220 if (cmd.action() == LFUN_BUFFER_ZOOM)
2222 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2224 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2227 if (cmd.action() == LFUN_BUFFER_ZOOM)
2228 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2229 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2230 return zr + convert<int>(cmd.argument()) / 100.0;
2231 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2232 return zr - convert<int>(cmd.argument()) / 100.0;
2239 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2242 Buffer * buf = currentBufferView()
2243 ? ¤tBufferView()->buffer() : nullptr;
2244 Buffer * doc_buffer = documentBufferView()
2245 ? &(documentBufferView()->buffer()) : nullptr;
2248 /* In LyX/Mac, when a dialog is open, the menus of the
2249 application can still be accessed without giving focus to
2250 the main window. In this case, we want to disable the menu
2251 entries that are buffer-related.
2252 This code must not be used on Linux and Windows, since it
2253 would disable buffer-related entries when hovering over the
2254 menu (see bug #9574).
2256 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2262 // Check whether we need a buffer
2263 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2264 // no, exit directly
2265 flag.message(from_utf8(N_("Command not allowed with"
2266 "out any document open")));
2267 flag.setEnabled(false);
2271 if (cmd.origin() == FuncRequest::TOC) {
2272 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2273 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2274 flag.setEnabled(false);
2278 switch(cmd.action()) {
2279 case LFUN_BUFFER_IMPORT:
2282 case LFUN_MASTER_BUFFER_EXPORT:
2284 && (doc_buffer->parent() != nullptr
2285 || doc_buffer->hasChildren())
2286 && !d.processing_thread_watcher_.isRunning()
2287 // this launches a dialog, which would be in the wrong Buffer
2288 && !(::lyx::operator==(cmd.argument(), "custom"));
2291 case LFUN_MASTER_BUFFER_UPDATE:
2292 case LFUN_MASTER_BUFFER_VIEW:
2294 && (doc_buffer->parent() != nullptr
2295 || doc_buffer->hasChildren())
2296 && !d.processing_thread_watcher_.isRunning();
2299 case LFUN_BUFFER_UPDATE:
2300 case LFUN_BUFFER_VIEW: {
2301 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2305 string format = to_utf8(cmd.argument());
2306 if (cmd.argument().empty())
2307 format = doc_buffer->params().getDefaultOutputFormat();
2308 enable = doc_buffer->params().isExportable(format, true);
2312 case LFUN_BUFFER_RELOAD:
2313 enable = doc_buffer && !doc_buffer->isUnnamed()
2314 && doc_buffer->fileName().exists()
2315 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2318 case LFUN_BUFFER_RESET_EXPORT:
2319 enable = doc_buffer != nullptr;
2322 case LFUN_BUFFER_CHILD_OPEN:
2323 enable = doc_buffer != nullptr;
2326 case LFUN_MASTER_BUFFER_FORALL: {
2327 if (doc_buffer == nullptr) {
2328 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2332 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2333 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2334 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2339 for (Buffer * buf : doc_buffer->allRelatives()) {
2340 GuiWorkArea * wa = workArea(*buf);
2343 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2344 enable = flag.enabled();
2351 case LFUN_BUFFER_WRITE:
2352 enable = doc_buffer && (doc_buffer->isUnnamed()
2353 || (!doc_buffer->isClean()
2354 || cmd.argument() == "force"));
2357 //FIXME: This LFUN should be moved to GuiApplication.
2358 case LFUN_BUFFER_WRITE_ALL: {
2359 // We enable the command only if there are some modified buffers
2360 Buffer * first = theBufferList().first();
2365 // We cannot use a for loop as the buffer list is a cycle.
2367 if (!b->isClean()) {
2371 b = theBufferList().next(b);
2372 } while (b != first);
2376 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2377 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2380 case LFUN_BUFFER_EXPORT: {
2381 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2385 return doc_buffer->getStatus(cmd, flag);
2388 case LFUN_BUFFER_EXPORT_AS:
2389 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2394 case LFUN_BUFFER_WRITE_AS:
2395 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2396 enable = doc_buffer != nullptr;
2399 case LFUN_EXPORT_CANCEL:
2400 enable = d.processing_thread_watcher_.isRunning();
2403 case LFUN_BUFFER_CLOSE:
2404 case LFUN_VIEW_CLOSE:
2405 enable = doc_buffer != nullptr;
2408 case LFUN_BUFFER_CLOSE_ALL:
2409 enable = theBufferList().last() != theBufferList().first();
2412 case LFUN_BUFFER_CHKTEX: {
2413 // hide if we have no checktex command
2414 if (lyxrc.chktex_command.empty()) {
2415 flag.setUnknown(true);
2419 if (!doc_buffer || !doc_buffer->params().isLatex()
2420 || d.processing_thread_watcher_.isRunning()) {
2421 // grey out, don't hide
2429 case LFUN_VIEW_SPLIT:
2430 if (cmd.getArg(0) == "vertical")
2431 enable = doc_buffer && (d.splitter_->count() == 1 ||
2432 d.splitter_->orientation() == Qt::Vertical);
2434 enable = doc_buffer && (d.splitter_->count() == 1 ||
2435 d.splitter_->orientation() == Qt::Horizontal);
2438 case LFUN_TAB_GROUP_CLOSE:
2439 enable = d.tabWorkAreaCount() > 1;
2442 case LFUN_DEVEL_MODE_TOGGLE:
2443 flag.setOnOff(devel_mode_);
2446 case LFUN_TOOLBAR_SET: {
2447 string const name = cmd.getArg(0);
2448 string const state = cmd.getArg(1);
2449 if (name.empty() || state.empty()) {
2451 docstring const msg =
2452 _("Function toolbar-set requires two arguments!");
2456 if (state != "on" && state != "off" && state != "auto") {
2458 docstring const msg =
2459 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2464 if (GuiToolbar * t = toolbar(name)) {
2465 bool const autovis = t->visibility() & Toolbars::AUTO;
2467 flag.setOnOff(t->isVisible() && !autovis);
2468 else if (state == "off")
2469 flag.setOnOff(!t->isVisible() && !autovis);
2470 else if (state == "auto")
2471 flag.setOnOff(autovis);
2474 docstring const msg =
2475 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2481 case LFUN_TOOLBAR_TOGGLE: {
2482 string const name = cmd.getArg(0);
2483 if (GuiToolbar * t = toolbar(name))
2484 flag.setOnOff(t->isVisible());
2487 docstring const msg =
2488 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2494 case LFUN_TOOLBAR_MOVABLE: {
2495 string const name = cmd.getArg(0);
2496 // use negation since locked == !movable
2498 // toolbar name * locks all toolbars
2499 flag.setOnOff(!toolbarsMovable_);
2500 else if (GuiToolbar * t = toolbar(name))
2501 flag.setOnOff(!(t->isMovable()));
2504 docstring const msg =
2505 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2511 case LFUN_ICON_SIZE:
2512 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2515 case LFUN_DROP_LAYOUTS_CHOICE:
2516 enable = buf != nullptr;
2519 case LFUN_UI_TOGGLE:
2520 if (cmd.argument() == "zoomlevel") {
2521 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2522 } else if (cmd.argument() == "zoomslider") {
2523 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2524 } else if (cmd.argument() == "statistics-w") {
2525 flag.setOnOff(word_count_enabled_);
2526 } else if (cmd.argument() == "statistics-cb") {
2527 flag.setOnOff(char_count_enabled_);
2528 } else if (cmd.argument() == "statistics-c") {
2529 flag.setOnOff(char_nb_count_enabled_);
2531 flag.setOnOff(isFullScreen());
2534 case LFUN_DIALOG_DISCONNECT_INSET:
2537 case LFUN_DIALOG_HIDE:
2538 // FIXME: should we check if the dialog is shown?
2541 case LFUN_DIALOG_TOGGLE:
2542 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2545 case LFUN_DIALOG_SHOW: {
2546 string const name = cmd.getArg(0);
2548 enable = name == "aboutlyx"
2549 || name == "file" //FIXME: should be removed.
2550 || name == "lyxfiles"
2552 || name == "texinfo"
2553 || name == "progress"
2554 || name == "compare";
2555 else if (name == "character" || name == "symbols"
2556 || name == "mathdelimiter" || name == "mathmatrix") {
2557 if (!buf || buf->isReadonly())
2560 Cursor const & cur = currentBufferView()->cursor();
2561 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2564 else if (name == "latexlog")
2565 enable = FileName(doc_buffer->logName()).isReadableFile();
2566 else if (name == "spellchecker")
2567 enable = theSpellChecker()
2568 && !doc_buffer->text().empty();
2569 else if (name == "vclog")
2570 enable = doc_buffer->lyxvc().inUse();
2574 case LFUN_DIALOG_UPDATE: {
2575 string const name = cmd.getArg(0);
2577 enable = name == "prefs";
2581 case LFUN_COMMAND_EXECUTE:
2583 case LFUN_MENU_OPEN:
2584 // Nothing to check.
2587 case LFUN_COMPLETION_INLINE:
2588 if (!d.current_work_area_
2589 || !d.current_work_area_->completer().inlinePossible(
2590 currentBufferView()->cursor()))
2594 case LFUN_COMPLETION_POPUP:
2595 if (!d.current_work_area_
2596 || !d.current_work_area_->completer().popupPossible(
2597 currentBufferView()->cursor()))
2602 if (!d.current_work_area_
2603 || !d.current_work_area_->completer().inlinePossible(
2604 currentBufferView()->cursor()))
2608 case LFUN_COMPLETION_ACCEPT:
2609 if (!d.current_work_area_
2610 || (!d.current_work_area_->completer().popupVisible()
2611 && !d.current_work_area_->completer().inlineVisible()
2612 && !d.current_work_area_->completer().completionAvailable()))
2616 case LFUN_COMPLETION_CANCEL:
2617 if (!d.current_work_area_
2618 || (!d.current_work_area_->completer().popupVisible()
2619 && !d.current_work_area_->completer().inlineVisible()))
2623 case LFUN_BUFFER_ZOOM_OUT:
2624 case LFUN_BUFFER_ZOOM_IN:
2625 case LFUN_BUFFER_ZOOM: {
2626 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2627 if (zoom < zoom_min_) {
2628 docstring const msg =
2629 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2632 } else if (zoom > zoom_max_) {
2633 docstring const msg =
2634 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2638 enable = doc_buffer;
2643 case LFUN_BUFFER_MOVE_NEXT:
2644 case LFUN_BUFFER_MOVE_PREVIOUS:
2645 // we do not cycle when moving
2646 case LFUN_BUFFER_NEXT:
2647 case LFUN_BUFFER_PREVIOUS:
2648 // because we cycle, it doesn't matter whether on first or last
2649 enable = (d.currentTabWorkArea()->count() > 1);
2651 case LFUN_BUFFER_SWITCH:
2652 // toggle on the current buffer, but do not toggle off
2653 // the other ones (is that a good idea?)
2655 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2656 flag.setOnOff(true);
2659 case LFUN_VC_REGISTER:
2660 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2662 case LFUN_VC_RENAME:
2663 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2666 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2668 case LFUN_VC_CHECK_IN:
2669 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2671 case LFUN_VC_CHECK_OUT:
2672 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2674 case LFUN_VC_LOCKING_TOGGLE:
2675 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2676 && doc_buffer->lyxvc().lockingToggleEnabled();
2677 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2679 case LFUN_VC_REVERT:
2680 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2681 && !doc_buffer->hasReadonlyFlag();
2683 case LFUN_VC_UNDO_LAST:
2684 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2686 case LFUN_VC_REPO_UPDATE:
2687 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2689 case LFUN_VC_COMMAND: {
2690 if (cmd.argument().empty())
2692 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2696 case LFUN_VC_COMPARE:
2697 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2700 case LFUN_SERVER_GOTO_FILE_ROW:
2701 case LFUN_LYX_ACTIVATE:
2702 case LFUN_WINDOW_RAISE:
2704 case LFUN_FORWARD_SEARCH:
2705 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2706 doc_buffer && doc_buffer->isSyncTeXenabled();
2709 case LFUN_FILE_INSERT_PLAINTEXT:
2710 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2711 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2714 case LFUN_SPELLING_CONTINUOUSLY:
2715 flag.setOnOff(lyxrc.spellcheck_continuously);
2718 case LFUN_CITATION_OPEN:
2727 flag.setEnabled(false);
2733 static FileName selectTemplateFile()
2735 FileDialog dlg(qt_("Select template file"));
2736 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2737 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2739 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2740 QStringList(qt_("LyX Documents (*.lyx)")));
2742 if (result.first == FileDialog::Later)
2744 if (result.second.isEmpty())
2746 return FileName(fromqstr(result.second));
2750 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2754 Buffer * newBuffer = nullptr;
2756 newBuffer = checkAndLoadLyXFile(filename);
2757 } catch (ExceptionMessage const &) {
2764 message(_("Document not loaded."));
2768 setBuffer(newBuffer);
2769 newBuffer->errors("Parse");
2772 theSession().lastFiles().add(filename);
2773 theSession().writeFile();
2780 void GuiView::openDocument(string const & fname)
2782 string initpath = lyxrc.document_path;
2784 if (documentBufferView()) {
2785 string const trypath = documentBufferView()->buffer().filePath();
2786 // If directory is writeable, use this as default.
2787 if (FileName(trypath).isDirWritable())
2793 if (fname.empty()) {
2794 FileDialog dlg(qt_("Select document to open"));
2795 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2796 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2798 QStringList const filter({
2799 qt_("LyX Documents (*.lyx)"),
2800 qt_("LyX Document Backups (*.lyx~)"),
2801 qt_("All Files (*.*)")
2803 FileDialog::Result result =
2804 dlg.open(toqstr(initpath), filter);
2806 if (result.first == FileDialog::Later)
2809 filename = fromqstr(result.second);
2811 // check selected filename
2812 if (filename.empty()) {
2813 message(_("Canceled."));
2819 // get absolute path of file and add ".lyx" to the filename if
2821 FileName const fullname =
2822 fileSearch(string(), filename, "lyx", support::may_not_exist);
2823 if (!fullname.empty())
2824 filename = fullname.absFileName();
2826 if (!fullname.onlyPath().isDirectory()) {
2827 Alert::warning(_("Invalid filename"),
2828 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2829 from_utf8(fullname.absFileName())));
2833 // if the file doesn't exist and isn't already open (bug 6645),
2834 // let the user create one
2835 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2836 !LyXVC::file_not_found_hook(fullname)) {
2837 // the user specifically chose this name. Believe him.
2838 Buffer * const b = newFile(filename, string(), true);
2844 docstring const disp_fn = makeDisplayPath(filename);
2845 message(bformat(_("Opening document %1$s..."), disp_fn));
2848 Buffer * buf = loadDocument(fullname);
2850 str2 = bformat(_("Document %1$s opened."), disp_fn);
2851 if (buf->lyxvc().inUse())
2852 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2853 " " + _("Version control detected.");
2855 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2860 // FIXME: clean that
2861 static bool import(GuiView * lv, FileName const & filename,
2862 string const & format, ErrorList & errorList)
2864 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2866 string loader_format;
2867 vector<string> loaders = theConverters().loaders();
2868 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2869 for (string const & loader : loaders) {
2870 if (!theConverters().isReachable(format, loader))
2873 string const tofile =
2874 support::changeExtension(filename.absFileName(),
2875 theFormats().extension(loader));
2876 if (theConverters().convert(nullptr, filename, FileName(tofile),
2877 filename, format, loader, errorList) != Converters::SUCCESS)
2879 loader_format = loader;
2882 if (loader_format.empty()) {
2883 frontend::Alert::error(_("Couldn't import file"),
2884 bformat(_("No information for importing the format %1$s."),
2885 translateIfPossible(theFormats().prettyName(format))));
2889 loader_format = format;
2891 if (loader_format == "lyx") {
2892 Buffer * buf = lv->loadDocument(lyxfile);
2896 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2900 bool as_paragraphs = loader_format == "textparagraph";
2901 string filename2 = (loader_format == format) ? filename.absFileName()
2902 : support::changeExtension(filename.absFileName(),
2903 theFormats().extension(loader_format));
2904 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2906 guiApp->setCurrentView(lv);
2907 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2914 void GuiView::importDocument(string const & argument)
2917 string filename = split(argument, format, ' ');
2919 LYXERR(Debug::INFO, format << " file: " << filename);
2921 // need user interaction
2922 if (filename.empty()) {
2923 string initpath = lyxrc.document_path;
2924 if (documentBufferView()) {
2925 string const trypath = documentBufferView()->buffer().filePath();
2926 // If directory is writeable, use this as default.
2927 if (FileName(trypath).isDirWritable())
2931 docstring const text = bformat(_("Select %1$s file to import"),
2932 translateIfPossible(theFormats().prettyName(format)));
2934 FileDialog dlg(toqstr(text));
2935 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2936 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2938 docstring filter = translateIfPossible(theFormats().prettyName(format));
2941 filter += from_utf8(theFormats().extensions(format));
2944 FileDialog::Result result =
2945 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2947 if (result.first == FileDialog::Later)
2950 filename = fromqstr(result.second);
2952 // check selected filename
2953 if (filename.empty())
2954 message(_("Canceled."));
2957 if (filename.empty())
2960 // get absolute path of file
2961 FileName const fullname(support::makeAbsPath(filename));
2963 // Can happen if the user entered a path into the dialog
2965 if (fullname.onlyFileName().empty()) {
2966 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2967 "Aborting import."),
2968 from_utf8(fullname.absFileName()));
2969 frontend::Alert::error(_("File name error"), msg);
2970 message(_("Canceled."));
2975 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2977 // Check if the document already is open
2978 Buffer * buf = theBufferList().getBuffer(lyxfile);
2981 if (!closeBuffer()) {
2982 message(_("Canceled."));
2987 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2989 // if the file exists already, and we didn't do
2990 // -i lyx thefile.lyx, warn
2991 if (lyxfile.exists() && fullname != lyxfile) {
2993 docstring text = bformat(_("The document %1$s already exists.\n\n"
2994 "Do you want to overwrite that document?"), displaypath);
2995 int const ret = Alert::prompt(_("Overwrite document?"),
2996 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2999 message(_("Canceled."));
3004 message(bformat(_("Importing %1$s..."), displaypath));
3005 ErrorList errorList;
3006 if (import(this, fullname, format, errorList))
3007 message(_("imported."));
3009 message(_("file not imported!"));
3011 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3015 void GuiView::newDocument(string const & filename, string templatefile,
3018 FileName initpath(lyxrc.document_path);
3019 if (documentBufferView()) {
3020 FileName const trypath(documentBufferView()->buffer().filePath());
3021 // If directory is writeable, use this as default.
3022 if (trypath.isDirWritable())
3026 if (from_template) {
3027 if (templatefile.empty())
3028 templatefile = selectTemplateFile().absFileName();
3029 if (templatefile.empty())
3034 if (filename.empty())
3035 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3037 b = newFile(filename, templatefile, true);
3042 // If no new document could be created, it is unsure
3043 // whether there is a valid BufferView.
3044 if (currentBufferView())
3045 // Ensure the cursor is correctly positioned on screen.
3046 currentBufferView()->showCursor();
3050 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3052 BufferView * bv = documentBufferView();
3057 FileName filename(to_utf8(fname));
3058 if (filename.empty()) {
3059 // Launch a file browser
3061 string initpath = lyxrc.document_path;
3062 string const trypath = bv->buffer().filePath();
3063 // If directory is writeable, use this as default.
3064 if (FileName(trypath).isDirWritable())
3068 FileDialog dlg(qt_("Select LyX document to insert"));
3069 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3070 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3072 FileDialog::Result result = dlg.open(toqstr(initpath),
3073 QStringList(qt_("LyX Documents (*.lyx)")));
3075 if (result.first == FileDialog::Later)
3079 filename.set(fromqstr(result.second));
3081 // check selected filename
3082 if (filename.empty()) {
3083 // emit message signal.
3084 message(_("Canceled."));
3089 bv->insertLyXFile(filename, ignorelang);
3090 bv->buffer().errors("Parse");
3095 string const GuiView::getTemplatesPath(Buffer & b)
3097 // We start off with the user's templates path
3098 string result = addPath(package().user_support().absFileName(), "templates");
3099 // Check for the document language
3100 string const langcode = b.params().language->code();
3101 string const shortcode = langcode.substr(0, 2);
3102 if (!langcode.empty() && shortcode != "en") {
3103 string subpath = addPath(result, shortcode);
3104 string subpath_long = addPath(result, langcode);
3105 // If we have a subdirectory for the language already,
3107 FileName sp = FileName(subpath);
3108 if (sp.isDirectory())
3110 else if (FileName(subpath_long).isDirectory())
3111 result = subpath_long;
3113 // Ask whether we should create such a subdirectory
3114 docstring const text =
3115 bformat(_("It is suggested to save the template in a subdirectory\n"
3116 "appropriate to the document language (%1$s).\n"
3117 "This subdirectory does not exists yet.\n"
3118 "Do you want to create it?"),
3119 _(b.params().language->display()));
3120 if (Alert::prompt(_("Create Language Directory?"),
3121 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3122 // If the user agreed, we try to create it and report if this failed.
3123 if (!sp.createDirectory(0777))
3124 Alert::error(_("Subdirectory creation failed!"),
3125 _("Could not create subdirectory.\n"
3126 "The template will be saved in the parent directory."));
3132 // Do we have a layout category?
3133 string const cat = b.params().baseClass() ?
3134 b.params().baseClass()->category()
3137 string subpath = addPath(result, cat);
3138 // If we have a subdirectory for the category already,
3140 FileName sp = FileName(subpath);
3141 if (sp.isDirectory())
3144 // Ask whether we should create such a subdirectory
3145 docstring const text =
3146 bformat(_("It is suggested to save the template in a subdirectory\n"
3147 "appropriate to the layout category (%1$s).\n"
3148 "This subdirectory does not exists yet.\n"
3149 "Do you want to create it?"),
3151 if (Alert::prompt(_("Create Category Directory?"),
3152 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3153 // If the user agreed, we try to create it and report if this failed.
3154 if (!sp.createDirectory(0777))
3155 Alert::error(_("Subdirectory creation failed!"),
3156 _("Could not create subdirectory.\n"
3157 "The template will be saved in the parent directory."));
3167 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3169 FileName fname = b.fileName();
3170 FileName const oldname = fname;
3171 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3173 if (!newname.empty()) {
3176 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3178 fname = support::makeAbsPath(to_utf8(newname),
3179 oldname.onlyPath().absFileName());
3181 // Switch to this Buffer.
3184 // No argument? Ask user through dialog.
3186 QString const title = as_template ? qt_("Choose a filename to save template as")
3187 : qt_("Choose a filename to save document as");
3188 FileDialog dlg(title);
3189 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3190 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3192 fname.ensureExtension(".lyx");
3194 string const path = as_template ?
3196 : fname.onlyPath().absFileName();
3197 FileDialog::Result result =
3198 dlg.save(toqstr(path),
3199 QStringList(qt_("LyX Documents (*.lyx)")),
3200 toqstr(fname.onlyFileName()));
3202 if (result.first == FileDialog::Later)
3205 fname.set(fromqstr(result.second));
3210 fname.ensureExtension(".lyx");
3213 // fname is now the new Buffer location.
3215 // if there is already a Buffer open with this name, we do not want
3216 // to have another one. (the second test makes sure we're not just
3217 // trying to overwrite ourselves, which is fine.)
3218 if (theBufferList().exists(fname) && fname != oldname
3219 && theBufferList().getBuffer(fname) != &b) {
3220 docstring const text =
3221 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3222 "Please close it before attempting to overwrite it.\n"
3223 "Do you want to choose a new filename?"),
3224 from_utf8(fname.absFileName()));
3225 int const ret = Alert::prompt(_("Chosen File Already Open"),
3226 text, 0, 1, _("&Rename"), _("&Cancel"));
3228 case 0: return renameBuffer(b, docstring(), kind);
3229 case 1: return false;
3234 bool const existsLocal = fname.exists();
3235 bool const existsInVC = LyXVC::fileInVC(fname);
3236 if (existsLocal || existsInVC) {
3237 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3238 if (kind != LV_WRITE_AS && existsInVC) {
3239 // renaming to a name that is already in VC
3241 docstring text = bformat(_("The document %1$s "
3242 "is already registered.\n\n"
3243 "Do you want to choose a new name?"),
3245 docstring const title = (kind == LV_VC_RENAME) ?
3246 _("Rename document?") : _("Copy document?");
3247 docstring const button = (kind == LV_VC_RENAME) ?
3248 _("&Rename") : _("&Copy");
3249 int const ret = Alert::prompt(title, text, 0, 1,
3250 button, _("&Cancel"));
3252 case 0: return renameBuffer(b, docstring(), kind);
3253 case 1: return false;
3258 docstring text = bformat(_("The document %1$s "
3259 "already exists.\n\n"
3260 "Do you want to overwrite that document?"),
3262 int const ret = Alert::prompt(_("Overwrite document?"),
3263 text, 0, 2, _("&Overwrite"),
3264 _("&Rename"), _("&Cancel"));
3267 case 1: return renameBuffer(b, docstring(), kind);
3268 case 2: return false;
3274 case LV_VC_RENAME: {
3275 string msg = b.lyxvc().rename(fname);
3278 message(from_utf8(msg));
3282 string msg = b.lyxvc().copy(fname);
3285 message(from_utf8(msg));
3289 case LV_WRITE_AS_TEMPLATE:
3292 // LyXVC created the file already in case of LV_VC_RENAME or
3293 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3294 // relative paths of included stuff right if we moved e.g. from
3295 // /a/b.lyx to /a/c/b.lyx.
3297 bool const saved = saveBuffer(b, fname);
3304 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3306 FileName fname = b.fileName();
3308 FileDialog dlg(qt_("Choose a filename to export the document as"));
3309 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3312 QString const anyformat = qt_("Guess from extension (*.*)");
3315 vector<Format const *> export_formats;
3316 for (Format const & f : theFormats())
3317 if (f.documentFormat())
3318 export_formats.push_back(&f);
3319 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3320 map<QString, string> fmap;
3323 for (Format const * f : export_formats) {
3324 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3325 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3327 from_ascii(f->extension())));
3328 types << loc_filter;
3329 fmap[loc_filter] = f->name();
3330 if (from_ascii(f->name()) == iformat) {
3331 filter = loc_filter;
3332 ext = f->extension();
3335 string ofname = fname.onlyFileName();
3337 ofname = support::changeExtension(ofname, ext);
3338 FileDialog::Result result =
3339 dlg.save(toqstr(fname.onlyPath().absFileName()),
3343 if (result.first != FileDialog::Chosen)
3347 fname.set(fromqstr(result.second));
3348 if (filter == anyformat)
3349 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3351 fmt_name = fmap[filter];
3352 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3353 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3355 if (fmt_name.empty() || fname.empty())
3358 fname.ensureExtension(theFormats().extension(fmt_name));
3360 // fname is now the new Buffer location.
3361 if (fname.exists()) {
3362 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3363 docstring text = bformat(_("The document %1$s already "
3364 "exists.\n\nDo you want to "
3365 "overwrite that document?"),
3367 int const ret = Alert::prompt(_("Overwrite document?"),
3368 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3371 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3372 case 2: return false;
3376 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3379 return dr.dispatched();
3383 bool GuiView::saveBuffer(Buffer & b)
3385 return saveBuffer(b, FileName());
3389 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3391 if (workArea(b) && workArea(b)->inDialogMode())
3394 if (fn.empty() && b.isUnnamed())
3395 return renameBuffer(b, docstring());
3397 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3399 theSession().lastFiles().add(b.fileName());
3400 theSession().writeFile();
3404 // Switch to this Buffer.
3407 // FIXME: we don't tell the user *WHY* the save failed !!
3408 docstring const file = makeDisplayPath(b.absFileName(), 30);
3409 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3410 "Do you want to rename the document and "
3411 "try again?"), file);
3412 int const ret = Alert::prompt(_("Rename and save?"),
3413 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3416 if (!renameBuffer(b, docstring()))
3425 return saveBuffer(b, fn);
3429 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3431 return closeWorkArea(wa, false);
3435 // We only want to close the buffer if it is not visible in other workareas
3436 // of the same view, nor in other views, and if this is not a child
3437 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3439 Buffer & buf = wa->bufferView().buffer();
3441 bool last_wa = d.countWorkAreasOf(buf) == 1
3442 && !inOtherView(buf) && !buf.parent();
3444 bool close_buffer = last_wa;
3447 if (lyxrc.close_buffer_with_last_view == "yes")
3449 else if (lyxrc.close_buffer_with_last_view == "no")
3450 close_buffer = false;
3453 if (buf.isUnnamed())
3454 file = from_utf8(buf.fileName().onlyFileName());
3456 file = buf.fileName().displayName(30);
3457 docstring const text = bformat(
3458 _("Last view on document %1$s is being closed.\n"
3459 "Would you like to close or hide the document?\n"
3461 "Hidden documents can be displayed back through\n"
3462 "the menu: View->Hidden->...\n"
3464 "To remove this question, set your preference in:\n"
3465 " Tools->Preferences->Look&Feel->UserInterface\n"
3467 int ret = Alert::prompt(_("Close or hide document?"),
3468 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3471 close_buffer = (ret == 0);
3475 return closeWorkArea(wa, close_buffer);
3479 bool GuiView::closeBuffer()
3481 GuiWorkArea * wa = currentMainWorkArea();
3482 // coverity complained about this
3483 // it seems unnecessary, but perhaps is worth the check
3484 LASSERT(wa, return false);
3486 setCurrentWorkArea(wa);
3487 Buffer & buf = wa->bufferView().buffer();
3488 return closeWorkArea(wa, !buf.parent());
3492 void GuiView::writeSession() const {
3493 GuiWorkArea const * active_wa = currentMainWorkArea();
3494 for (int i = 0; i < d.splitter_->count(); ++i) {
3495 TabWorkArea * twa = d.tabWorkArea(i);
3496 for (int j = 0; j < twa->count(); ++j) {
3497 GuiWorkArea * wa = twa->workArea(j);
3498 Buffer & buf = wa->bufferView().buffer();
3499 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3505 bool GuiView::closeBufferAll()
3508 for (auto & buf : theBufferList()) {
3509 if (!saveBufferIfNeeded(*buf, false)) {
3510 // Closing has been cancelled, so abort.
3515 // Close the workareas in all other views
3516 QList<int> const ids = guiApp->viewIds();
3517 for (int i = 0; i != ids.size(); ++i) {
3518 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3522 // Close our own workareas
3523 if (!closeWorkAreaAll())
3530 bool GuiView::closeWorkAreaAll()
3532 setCurrentWorkArea(currentMainWorkArea());
3534 // We might be in a situation that there is still a tabWorkArea, but
3535 // there are no tabs anymore. This can happen when we get here after a
3536 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3537 // many TabWorkArea's have no documents anymore.
3540 // We have to call count() each time, because it can happen that
3541 // more than one splitter will disappear in one iteration (bug 5998).
3542 while (d.splitter_->count() > empty_twa) {
3543 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3545 if (twa->count() == 0)
3548 setCurrentWorkArea(twa->currentWorkArea());
3549 if (!closeTabWorkArea(twa))
3557 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3562 Buffer & buf = wa->bufferView().buffer();
3564 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3565 Alert::warning(_("Close document"),
3566 _("Document could not be closed because it is being processed by LyX."));
3571 return closeBuffer(buf);
3573 if (!inMultiTabs(wa))
3574 if (!saveBufferIfNeeded(buf, true))
3582 bool GuiView::closeBuffer(Buffer & buf)
3584 bool success = true;
3585 for (Buffer * child_buf : buf.getChildren()) {
3586 if (theBufferList().isOthersChild(&buf, child_buf)) {
3587 child_buf->setParent(nullptr);
3591 // FIXME: should we look in other tabworkareas?
3592 // ANSWER: I don't think so. I've tested, and if the child is
3593 // open in some other window, it closes without a problem.
3594 GuiWorkArea * child_wa = workArea(*child_buf);
3597 // If we are in a close_event all children will be closed in some time,
3598 // so no need to do it here. This will ensure that the children end up
3599 // in the session file in the correct order. If we close the master
3600 // buffer, we can close or release the child buffers here too.
3602 success = closeWorkArea(child_wa, true);
3606 // In this case the child buffer is open but hidden.
3607 // Even in this case, children can be dirty (e.g.,
3608 // after a label change in the master, see #11405).
3609 // Therefore, check this
3610 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3611 // If we are in a close_event all children will be closed in some time,
3612 // so no need to do it here. This will ensure that the children end up
3613 // in the session file in the correct order. If we close the master
3614 // buffer, we can close or release the child buffers here too.
3617 // Save dirty buffers also if closing_!
3618 if (saveBufferIfNeeded(*child_buf, false)) {
3619 child_buf->removeAutosaveFile();
3620 theBufferList().release(child_buf);
3622 // Saving of dirty children has been cancelled.
3623 // Cancel the whole process.
3630 // goto bookmark to update bookmark pit.
3631 // FIXME: we should update only the bookmarks related to this buffer!
3632 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3633 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3634 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3635 guiApp->gotoBookmark(i, false, false);
3637 if (saveBufferIfNeeded(buf, false)) {
3638 buf.removeAutosaveFile();
3639 theBufferList().release(&buf);
3643 // open all children again to avoid a crash because of dangling
3644 // pointers (bug 6603)
3650 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3652 while (twa == d.currentTabWorkArea()) {
3653 twa->setCurrentIndex(twa->count() - 1);
3655 GuiWorkArea * wa = twa->currentWorkArea();
3656 Buffer & b = wa->bufferView().buffer();
3658 // We only want to close the buffer if the same buffer is not visible
3659 // in another view, and if this is not a child and if we are closing
3660 // a view (not a tabgroup).
3661 bool const close_buffer =
3662 !inOtherView(b) && !b.parent() && closing_;
3664 if (!closeWorkArea(wa, close_buffer))
3671 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3673 if (buf.isClean() || buf.paragraphs().empty())
3676 // Switch to this Buffer.
3682 if (buf.isUnnamed()) {
3683 file = from_utf8(buf.fileName().onlyFileName());
3686 FileName filename = buf.fileName();
3688 file = filename.displayName(30);
3689 exists = filename.exists();
3692 // Bring this window to top before asking questions.
3697 if (hiding && buf.isUnnamed()) {
3698 docstring const text = bformat(_("The document %1$s has not been "
3699 "saved yet.\n\nDo you want to save "
3700 "the document?"), file);
3701 ret = Alert::prompt(_("Save new document?"),
3702 text, 0, 1, _("&Save"), _("&Cancel"));
3706 docstring const text = exists ?
3707 bformat(_("The document %1$s has unsaved changes."
3708 "\n\nDo you want to save the document or "
3709 "discard the changes?"), file) :
3710 bformat(_("The document %1$s has not been saved yet."
3711 "\n\nDo you want to save the document or "
3712 "discard it entirely?"), file);
3713 docstring const title = exists ?
3714 _("Save changed document?") : _("Save document?");
3715 ret = Alert::prompt(title, text, 0, 2,
3716 _("&Save"), _("&Discard"), _("&Cancel"));
3721 if (!saveBuffer(buf))
3725 // If we crash after this we could have no autosave file
3726 // but I guess this is really improbable (Jug).
3727 // Sometimes improbable things happen:
3728 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3729 // buf.removeAutosaveFile();
3731 // revert all changes
3742 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3744 Buffer & buf = wa->bufferView().buffer();
3746 for (int i = 0; i != d.splitter_->count(); ++i) {
3747 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3748 if (wa_ && wa_ != wa)
3751 return inOtherView(buf);
3755 bool GuiView::inOtherView(Buffer & buf)
3757 QList<int> const ids = guiApp->viewIds();
3759 for (int i = 0; i != ids.size(); ++i) {
3763 if (guiApp->view(ids[i]).workArea(buf))
3770 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3772 if (!documentBufferView())
3775 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3776 Buffer * const curbuf = &documentBufferView()->buffer();
3777 int nwa = twa->count();
3778 for (int i = 0; i < nwa; ++i) {
3779 if (&workArea(i)->bufferView().buffer() == curbuf) {
3781 if (np == NEXTBUFFER)
3782 next_index = (i == nwa - 1 ? 0 : i + 1);
3784 next_index = (i == 0 ? nwa - 1 : i - 1);
3786 twa->moveTab(i, next_index);
3788 setBuffer(&workArea(next_index)->bufferView().buffer());
3796 /// make sure the document is saved
3797 static bool ensureBufferClean(Buffer * buffer)
3799 LASSERT(buffer, return false);
3800 if (buffer->isClean() && !buffer->isUnnamed())
3803 docstring const file = buffer->fileName().displayName(30);
3806 if (!buffer->isUnnamed()) {
3807 text = bformat(_("The document %1$s has unsaved "
3808 "changes.\n\nDo you want to save "
3809 "the document?"), file);
3810 title = _("Save changed document?");
3813 text = bformat(_("The document %1$s has not been "
3814 "saved yet.\n\nDo you want to save "
3815 "the document?"), file);
3816 title = _("Save new document?");
3818 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3821 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3823 return buffer->isClean() && !buffer->isUnnamed();
3827 bool GuiView::reloadBuffer(Buffer & buf)
3829 currentBufferView()->cursor().reset();
3830 Buffer::ReadStatus status = buf.reload();
3831 return status == Buffer::ReadSuccess;
3835 void GuiView::checkExternallyModifiedBuffers()
3837 for (Buffer * buf : theBufferList()) {
3838 if (buf->fileName().exists() && buf->isChecksumModified()) {
3839 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3840 " Reload now? Any local changes will be lost."),
3841 from_utf8(buf->absFileName()));
3842 int const ret = Alert::prompt(_("Reload externally changed document?"),
3843 text, 0, 1, _("&Reload"), _("&Cancel"));
3851 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3853 Buffer * buffer = documentBufferView()
3854 ? &(documentBufferView()->buffer()) : nullptr;
3856 switch (cmd.action()) {
3857 case LFUN_VC_REGISTER:
3858 if (!buffer || !ensureBufferClean(buffer))
3860 if (!buffer->lyxvc().inUse()) {
3861 if (buffer->lyxvc().registrer()) {
3862 reloadBuffer(*buffer);
3863 dr.clearMessageUpdate();
3868 case LFUN_VC_RENAME:
3869 case LFUN_VC_COPY: {
3870 if (!buffer || !ensureBufferClean(buffer))
3872 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3873 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3874 // Some changes are not yet committed.
3875 // We test here and not in getStatus(), since
3876 // this test is expensive.
3878 LyXVC::CommandResult ret =
3879 buffer->lyxvc().checkIn(log);
3881 if (ret == LyXVC::ErrorCommand ||
3882 ret == LyXVC::VCSuccess)
3883 reloadBuffer(*buffer);
3884 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3885 frontend::Alert::error(
3886 _("Revision control error."),
3887 _("Document could not be checked in."));
3891 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3892 LV_VC_RENAME : LV_VC_COPY;
3893 renameBuffer(*buffer, cmd.argument(), kind);
3898 case LFUN_VC_CHECK_IN:
3899 if (!buffer || !ensureBufferClean(buffer))
3901 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3903 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3905 // Only skip reloading if the checkin was cancelled or
3906 // an error occurred before the real checkin VCS command
3907 // was executed, since the VCS might have changed the
3908 // file even if it could not checkin successfully.
3909 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3910 reloadBuffer(*buffer);
3914 case LFUN_VC_CHECK_OUT:
3915 if (!buffer || !ensureBufferClean(buffer))
3917 if (buffer->lyxvc().inUse()) {
3918 dr.setMessage(buffer->lyxvc().checkOut());
3919 reloadBuffer(*buffer);
3923 case LFUN_VC_LOCKING_TOGGLE:
3924 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3926 if (buffer->lyxvc().inUse()) {
3927 string res = buffer->lyxvc().lockingToggle();
3929 frontend::Alert::error(_("Revision control error."),
3930 _("Error when setting the locking property."));
3933 reloadBuffer(*buffer);
3938 case LFUN_VC_REVERT:
3941 if (buffer->lyxvc().revert()) {
3942 reloadBuffer(*buffer);
3943 dr.clearMessageUpdate();
3947 case LFUN_VC_UNDO_LAST:
3950 buffer->lyxvc().undoLast();
3951 reloadBuffer(*buffer);
3952 dr.clearMessageUpdate();
3955 case LFUN_VC_REPO_UPDATE:
3958 if (ensureBufferClean(buffer)) {
3959 dr.setMessage(buffer->lyxvc().repoUpdate());
3960 checkExternallyModifiedBuffers();
3964 case LFUN_VC_COMMAND: {
3965 string flag = cmd.getArg(0);
3966 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3969 if (contains(flag, 'M')) {
3970 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3973 string path = cmd.getArg(1);
3974 if (contains(path, "$$p") && buffer)
3975 path = subst(path, "$$p", buffer->filePath());
3976 LYXERR(Debug::LYXVC, "Directory: " << path);
3978 if (!pp.isReadableDirectory()) {
3979 lyxerr << _("Directory is not accessible.") << endl;
3982 support::PathChanger p(pp);
3984 string command = cmd.getArg(2);
3985 if (command.empty())
3988 command = subst(command, "$$i", buffer->absFileName());
3989 command = subst(command, "$$p", buffer->filePath());
3991 command = subst(command, "$$m", to_utf8(message));
3992 LYXERR(Debug::LYXVC, "Command: " << command);
3994 one.startscript(Systemcall::Wait, command);
3998 if (contains(flag, 'I'))
3999 buffer->markDirty();
4000 if (contains(flag, 'R'))
4001 reloadBuffer(*buffer);
4006 case LFUN_VC_COMPARE: {
4007 if (cmd.argument().empty()) {
4008 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4014 string rev1 = cmd.getArg(0);
4018 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4021 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4022 f2 = buffer->absFileName();
4024 string rev2 = cmd.getArg(1);
4028 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4032 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4033 f1 << "\n" << f2 << "\n" );
4034 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4035 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4045 void GuiView::openChildDocument(string const & fname)
4047 LASSERT(documentBufferView(), return);
4048 Buffer & buffer = documentBufferView()->buffer();
4049 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4050 documentBufferView()->saveBookmark(false);
4051 Buffer * child = nullptr;
4052 if (theBufferList().exists(filename)) {
4053 child = theBufferList().getBuffer(filename);
4056 message(bformat(_("Opening child document %1$s..."),
4057 makeDisplayPath(filename.absFileName())));
4058 child = loadDocument(filename, false);
4060 // Set the parent name of the child document.
4061 // This makes insertion of citations and references in the child work,
4062 // when the target is in the parent or another child document.
4064 child->setParent(&buffer);
4068 bool GuiView::goToFileRow(string const & argument)
4072 size_t i = argument.find_last_of(' ');
4073 if (i != string::npos) {
4074 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4075 istringstream is(argument.substr(i + 1));
4080 if (i == string::npos) {
4081 LYXERR0("Wrong argument: " << argument);
4084 Buffer * buf = nullptr;
4085 string const realtmp = package().temp_dir().realPath();
4086 // We have to use os::path_prefix_is() here, instead of
4087 // simply prefixIs(), because the file name comes from
4088 // an external application and may need case adjustment.
4089 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4090 buf = theBufferList().getBufferFromTmp(file_name, true);
4091 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4092 << (buf ? " success" : " failed"));
4094 // Must replace extension of the file to be .lyx
4095 // and get full path
4096 FileName const s = fileSearch(string(),
4097 support::changeExtension(file_name, ".lyx"), "lyx");
4098 // Either change buffer or load the file
4099 if (theBufferList().exists(s))
4100 buf = theBufferList().getBuffer(s);
4101 else if (s.exists()) {
4102 buf = loadDocument(s);
4107 _("File does not exist: %1$s"),
4108 makeDisplayPath(file_name)));
4114 _("No buffer for file: %1$s."),
4115 makeDisplayPath(file_name))
4120 bool success = documentBufferView()->setCursorFromRow(row);
4122 LYXERR(Debug::OUTFILE,
4123 "setCursorFromRow: invalid position for row " << row);
4124 frontend::Alert::error(_("Inverse Search Failed"),
4125 _("Invalid position requested by inverse search.\n"
4126 "You may need to update the viewed document."));
4132 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4134 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4135 menu->exec(QCursor::pos());
4140 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4141 Buffer const * orig, Buffer * clone, string const & format)
4143 Buffer::ExportStatus const status = func(format);
4145 // the cloning operation will have produced a clone of the entire set of
4146 // documents, starting from the master. so we must delete those.
4147 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4149 busyBuffers.remove(orig);
4154 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4155 Buffer const * orig, Buffer * clone, string const & format)
4157 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4159 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4163 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4164 Buffer const * orig, Buffer * clone, string const & format)
4166 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4168 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4172 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4173 Buffer const * orig, Buffer * clone, string const & format)
4175 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4177 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4181 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4182 Buffer const * used_buffer,
4183 docstring const & msg,
4184 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4185 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4186 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4187 bool allow_async, bool use_tmpdir)
4192 string format = argument;
4194 format = used_buffer->params().getDefaultOutputFormat();
4195 processing_format = format;
4197 progress_->clearMessages();
4200 #if EXPORT_in_THREAD
4202 GuiViewPrivate::busyBuffers.insert(used_buffer);
4203 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4204 if (!cloned_buffer) {
4205 Alert::error(_("Export Error"),
4206 _("Error cloning the Buffer."));
4209 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4214 setPreviewFuture(f);
4215 last_export_format = used_buffer->params().bufferFormat();
4218 // We are asynchronous, so we don't know here anything about the success
4221 Buffer::ExportStatus status;
4223 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4224 } else if (previewFunc) {
4225 status = (used_buffer->*previewFunc)(format);
4228 handleExportStatus(gv_, status, format);
4230 return (status == Buffer::ExportSuccess
4231 || status == Buffer::PreviewSuccess);
4235 Buffer::ExportStatus status;
4237 status = (used_buffer->*syncFunc)(format, true);
4238 } else if (previewFunc) {
4239 status = (used_buffer->*previewFunc)(format);
4242 handleExportStatus(gv_, status, format);
4244 return (status == Buffer::ExportSuccess
4245 || status == Buffer::PreviewSuccess);
4249 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4251 BufferView * bv = currentBufferView();
4252 LASSERT(bv, return);
4254 // Let the current BufferView dispatch its own actions.
4255 bv->dispatch(cmd, dr);
4256 if (dr.dispatched()) {
4257 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4258 updateDialog("document", "");
4262 // Try with the document BufferView dispatch if any.
4263 BufferView * doc_bv = documentBufferView();
4264 if (doc_bv && doc_bv != bv) {
4265 doc_bv->dispatch(cmd, dr);
4266 if (dr.dispatched()) {
4267 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4268 updateDialog("document", "");
4273 // Then let the current Cursor dispatch its own actions.
4274 bv->cursor().dispatch(cmd);
4276 // update completion. We do it here and not in
4277 // processKeySym to avoid another redraw just for a
4278 // changed inline completion
4279 if (cmd.origin() == FuncRequest::KEYBOARD) {
4280 if (cmd.action() == LFUN_SELF_INSERT
4281 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4282 updateCompletion(bv->cursor(), true, true);
4283 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4284 updateCompletion(bv->cursor(), false, true);
4286 updateCompletion(bv->cursor(), false, false);
4289 dr = bv->cursor().result();
4293 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4295 BufferView * bv = currentBufferView();
4296 // By default we won't need any update.
4297 dr.screenUpdate(Update::None);
4298 // assume cmd will be dispatched
4299 dr.dispatched(true);
4301 Buffer * doc_buffer = documentBufferView()
4302 ? &(documentBufferView()->buffer()) : nullptr;
4304 if (cmd.origin() == FuncRequest::TOC) {
4305 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4306 toc->doDispatch(bv->cursor(), cmd, dr);
4310 string const argument = to_utf8(cmd.argument());
4312 switch(cmd.action()) {
4313 case LFUN_BUFFER_CHILD_OPEN:
4314 openChildDocument(to_utf8(cmd.argument()));
4317 case LFUN_BUFFER_IMPORT:
4318 importDocument(to_utf8(cmd.argument()));
4321 case LFUN_MASTER_BUFFER_EXPORT:
4323 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4325 case LFUN_BUFFER_EXPORT: {
4328 // GCC only sees strfwd.h when building merged
4329 if (::lyx::operator==(cmd.argument(), "custom")) {
4330 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4331 // so the following test should not be needed.
4332 // In principle, we could try to switch to such a view...
4333 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4334 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4338 string const dest = cmd.getArg(1);
4339 FileName target_dir;
4340 if (!dest.empty() && FileName::isAbsolute(dest))
4341 target_dir = FileName(support::onlyPath(dest));
4343 target_dir = doc_buffer->fileName().onlyPath();
4345 string const format = (argument.empty() || argument == "default") ?
4346 doc_buffer->params().getDefaultOutputFormat() : argument;
4348 if ((dest.empty() && doc_buffer->isUnnamed())
4349 || !target_dir.isDirWritable()) {
4350 exportBufferAs(*doc_buffer, from_utf8(format));
4353 /* TODO/Review: Is it a problem to also export the children?
4354 See the update_unincluded flag */
4355 d.asyncBufferProcessing(format,
4358 &GuiViewPrivate::exportAndDestroy,
4360 nullptr, cmd.allowAsync());
4361 // TODO Inform user about success
4365 case LFUN_BUFFER_EXPORT_AS: {
4366 LASSERT(doc_buffer, break);
4367 docstring f = cmd.argument();
4369 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4370 exportBufferAs(*doc_buffer, f);
4374 case LFUN_BUFFER_UPDATE: {
4375 d.asyncBufferProcessing(argument,
4378 &GuiViewPrivate::compileAndDestroy,
4380 nullptr, cmd.allowAsync(), true);
4383 case LFUN_BUFFER_VIEW: {
4384 d.asyncBufferProcessing(argument,
4386 _("Previewing ..."),
4387 &GuiViewPrivate::previewAndDestroy,
4389 &Buffer::preview, cmd.allowAsync());
4392 case LFUN_MASTER_BUFFER_UPDATE: {
4393 d.asyncBufferProcessing(argument,
4394 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4396 &GuiViewPrivate::compileAndDestroy,
4398 nullptr, cmd.allowAsync(), true);
4401 case LFUN_MASTER_BUFFER_VIEW: {
4402 d.asyncBufferProcessing(argument,
4403 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4405 &GuiViewPrivate::previewAndDestroy,
4406 nullptr, &Buffer::preview, cmd.allowAsync());
4409 case LFUN_EXPORT_CANCEL: {
4410 Systemcall::killscript();
4413 case LFUN_BUFFER_SWITCH: {
4414 string const file_name = to_utf8(cmd.argument());
4415 if (!FileName::isAbsolute(file_name)) {
4417 dr.setMessage(_("Absolute filename expected."));
4421 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4424 dr.setMessage(_("Document not loaded"));
4428 // Do we open or switch to the buffer in this view ?
4429 if (workArea(*buffer)
4430 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4435 // Look for the buffer in other views
4436 QList<int> const ids = guiApp->viewIds();
4438 for (; i != ids.size(); ++i) {
4439 GuiView & gv = guiApp->view(ids[i]);
4440 if (gv.workArea(*buffer)) {
4442 gv.activateWindow();
4444 gv.setBuffer(buffer);
4449 // If necessary, open a new window as a last resort
4450 if (i == ids.size()) {
4451 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4457 case LFUN_BUFFER_NEXT:
4458 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4461 case LFUN_BUFFER_MOVE_NEXT:
4462 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4465 case LFUN_BUFFER_PREVIOUS:
4466 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4469 case LFUN_BUFFER_MOVE_PREVIOUS:
4470 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4473 case LFUN_BUFFER_CHKTEX:
4474 LASSERT(doc_buffer, break);
4475 doc_buffer->runChktex();
4478 case LFUN_COMMAND_EXECUTE: {
4479 command_execute_ = true;
4480 minibuffer_focus_ = true;
4483 case LFUN_DROP_LAYOUTS_CHOICE:
4484 d.layout_->showPopup();
4487 case LFUN_MENU_OPEN:
4488 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4489 menu->exec(QCursor::pos());
4492 case LFUN_FILE_INSERT: {
4493 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4494 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4495 dr.forceBufferUpdate();
4496 dr.screenUpdate(Update::Force);
4501 case LFUN_FILE_INSERT_PLAINTEXT:
4502 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4503 string const fname = to_utf8(cmd.argument());
4504 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4505 dr.setMessage(_("Absolute filename expected."));
4509 FileName filename(fname);
4510 if (fname.empty()) {
4511 FileDialog dlg(qt_("Select file to insert"));
4513 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4514 QStringList(qt_("All Files (*)")));
4516 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4517 dr.setMessage(_("Canceled."));
4521 filename.set(fromqstr(result.second));
4525 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4526 bv->dispatch(new_cmd, dr);
4531 case LFUN_BUFFER_RELOAD: {
4532 LASSERT(doc_buffer, break);
4535 bool drop = (cmd.argument() == "dump");
4538 if (!drop && !doc_buffer->isClean()) {
4539 docstring const file =
4540 makeDisplayPath(doc_buffer->absFileName(), 20);
4541 if (doc_buffer->notifiesExternalModification()) {
4542 docstring text = _("The current version will be lost. "
4543 "Are you sure you want to load the version on disk "
4544 "of the document %1$s?");
4545 ret = Alert::prompt(_("Reload saved document?"),
4546 bformat(text, file), 1, 1,
4547 _("&Reload"), _("&Cancel"));
4549 docstring text = _("Any changes will be lost. "
4550 "Are you sure you want to revert to the saved version "
4551 "of the document %1$s?");
4552 ret = Alert::prompt(_("Revert to saved document?"),
4553 bformat(text, file), 1, 1,
4554 _("&Revert"), _("&Cancel"));
4559 doc_buffer->markClean();
4560 reloadBuffer(*doc_buffer);
4561 dr.forceBufferUpdate();
4566 case LFUN_BUFFER_RESET_EXPORT:
4567 LASSERT(doc_buffer, break);
4568 doc_buffer->requireFreshStart(true);
4569 dr.setMessage(_("Buffer export reset."));
4572 case LFUN_BUFFER_WRITE:
4573 LASSERT(doc_buffer, break);
4574 saveBuffer(*doc_buffer);
4577 case LFUN_BUFFER_WRITE_AS:
4578 LASSERT(doc_buffer, break);
4579 renameBuffer(*doc_buffer, cmd.argument());
4582 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4583 LASSERT(doc_buffer, break);
4584 renameBuffer(*doc_buffer, cmd.argument(),
4585 LV_WRITE_AS_TEMPLATE);
4588 case LFUN_BUFFER_WRITE_ALL: {
4589 Buffer * first = theBufferList().first();
4592 message(_("Saving all documents..."));
4593 // We cannot use a for loop as the buffer list cycles.
4596 if (!b->isClean()) {
4598 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4600 b = theBufferList().next(b);
4601 } while (b != first);
4602 dr.setMessage(_("All documents saved."));
4606 case LFUN_MASTER_BUFFER_FORALL: {
4610 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4611 funcToRun.allowAsync(false);
4613 for (Buffer const * buf : doc_buffer->allRelatives()) {
4614 // Switch to other buffer view and resend cmd
4615 lyx::dispatch(FuncRequest(
4616 LFUN_BUFFER_SWITCH, buf->absFileName()));
4617 lyx::dispatch(funcToRun);
4620 lyx::dispatch(FuncRequest(
4621 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4625 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4626 LASSERT(doc_buffer, break);
4627 doc_buffer->clearExternalModification();
4630 case LFUN_BUFFER_CLOSE:
4634 case LFUN_BUFFER_CLOSE_ALL:
4638 case LFUN_DEVEL_MODE_TOGGLE:
4639 devel_mode_ = !devel_mode_;
4641 dr.setMessage(_("Developer mode is now enabled."));
4643 dr.setMessage(_("Developer mode is now disabled."));
4646 case LFUN_TOOLBAR_SET: {
4647 string const name = cmd.getArg(0);
4648 string const state = cmd.getArg(1);
4649 if (GuiToolbar * t = toolbar(name))
4654 case LFUN_TOOLBAR_TOGGLE: {
4655 string const name = cmd.getArg(0);
4656 if (GuiToolbar * t = toolbar(name))
4661 case LFUN_TOOLBAR_MOVABLE: {
4662 string const name = cmd.getArg(0);
4664 // toggle (all) toolbars movablility
4665 toolbarsMovable_ = !toolbarsMovable_;
4666 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4667 GuiToolbar * tb = toolbar(ti.name);
4668 if (tb && tb->isMovable() != toolbarsMovable_)
4669 // toggle toolbar movablity if it does not fit lock
4670 // (all) toolbars positions state silent = true, since
4671 // status bar notifications are slow
4674 if (toolbarsMovable_)
4675 dr.setMessage(_("Toolbars unlocked."));
4677 dr.setMessage(_("Toolbars locked."));
4678 } else if (GuiToolbar * tb = toolbar(name))
4679 // toggle current toolbar movablity
4681 // update lock (all) toolbars positions
4682 updateLockToolbars();
4686 case LFUN_ICON_SIZE: {
4687 QSize size = d.iconSize(cmd.argument());
4689 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4690 size.width(), size.height()));
4694 case LFUN_DIALOG_UPDATE: {
4695 string const name = to_utf8(cmd.argument());
4696 if (name == "prefs" || name == "document")
4697 updateDialog(name, string());
4698 else if (name == "paragraph")
4699 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4700 else if (currentBufferView()) {
4701 Inset * inset = currentBufferView()->editedInset(name);
4702 // Can only update a dialog connected to an existing inset
4704 // FIXME: get rid of this indirection; GuiView ask the inset
4705 // if he is kind enough to update itself...
4706 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4707 //FIXME: pass DispatchResult here?
4708 inset->dispatch(currentBufferView()->cursor(), fr);
4714 case LFUN_DIALOG_TOGGLE: {
4715 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4716 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4717 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4721 case LFUN_DIALOG_DISCONNECT_INSET:
4722 disconnectDialog(to_utf8(cmd.argument()));
4725 case LFUN_DIALOG_HIDE: {
4726 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4730 case LFUN_DIALOG_SHOW: {
4731 string const name = cmd.getArg(0);
4732 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4734 if (name == "latexlog") {
4735 // getStatus checks that
4736 LASSERT(doc_buffer, break);
4737 Buffer::LogType type;
4738 string const logfile = doc_buffer->logName(&type);
4740 case Buffer::latexlog:
4743 case Buffer::buildlog:
4744 sdata = "literate ";
4747 sdata += Lexer::quoteString(logfile);
4748 showDialog("log", sdata);
4749 } else if (name == "vclog") {
4750 // getStatus checks that
4751 LASSERT(doc_buffer, break);
4752 string const sdata2 = "vc " +
4753 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4754 showDialog("log", sdata2);
4755 } else if (name == "symbols") {
4756 sdata = bv->cursor().getEncoding()->name();
4758 showDialog("symbols", sdata);
4759 } else if (name == "findreplace") {
4760 sdata = to_utf8(bv->cursor().selectionAsString(false));
4761 showDialog(name, sdata);
4763 } else if (name == "prefs" && isFullScreen()) {
4764 lfunUiToggle("fullscreen");
4765 showDialog("prefs", sdata);
4767 showDialog(name, sdata);
4772 dr.setMessage(cmd.argument());
4775 case LFUN_UI_TOGGLE: {
4776 string arg = cmd.getArg(0);
4777 if (!lfunUiToggle(arg)) {
4778 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4779 dr.setMessage(bformat(msg, from_utf8(arg)));
4781 // Make sure the keyboard focus stays in the work area.
4786 case LFUN_VIEW_SPLIT: {
4787 LASSERT(doc_buffer, break);
4788 string const orientation = cmd.getArg(0);
4789 d.splitter_->setOrientation(orientation == "vertical"
4790 ? Qt::Vertical : Qt::Horizontal);
4791 TabWorkArea * twa = addTabWorkArea();
4792 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4793 setCurrentWorkArea(wa);
4796 case LFUN_TAB_GROUP_CLOSE:
4797 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4798 closeTabWorkArea(twa);
4799 d.current_work_area_ = nullptr;
4800 twa = d.currentTabWorkArea();
4801 // Switch to the next GuiWorkArea in the found TabWorkArea.
4803 // Make sure the work area is up to date.
4804 setCurrentWorkArea(twa->currentWorkArea());
4806 setCurrentWorkArea(nullptr);
4811 case LFUN_VIEW_CLOSE:
4812 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4813 closeWorkArea(twa->currentWorkArea());
4814 d.current_work_area_ = nullptr;
4815 twa = d.currentTabWorkArea();
4816 // Switch to the next GuiWorkArea in the found TabWorkArea.
4818 // Make sure the work area is up to date.
4819 setCurrentWorkArea(twa->currentWorkArea());
4821 setCurrentWorkArea(nullptr);
4826 case LFUN_COMPLETION_INLINE:
4827 if (d.current_work_area_)
4828 d.current_work_area_->completer().showInline();
4831 case LFUN_COMPLETION_POPUP:
4832 if (d.current_work_area_)
4833 d.current_work_area_->completer().showPopup();
4838 if (d.current_work_area_)
4839 d.current_work_area_->completer().tab();
4842 case LFUN_COMPLETION_CANCEL:
4843 if (d.current_work_area_) {
4844 if (d.current_work_area_->completer().popupVisible())
4845 d.current_work_area_->completer().hidePopup();
4847 d.current_work_area_->completer().hideInline();
4851 case LFUN_COMPLETION_ACCEPT:
4852 if (d.current_work_area_)
4853 d.current_work_area_->completer().activate();
4856 case LFUN_BUFFER_ZOOM_IN:
4857 case LFUN_BUFFER_ZOOM_OUT:
4858 case LFUN_BUFFER_ZOOM: {
4859 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4861 // Actual zoom value: default zoom + fractional extra value
4862 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4863 zoom = min(max(zoom, zoom_min_), zoom_max_);
4865 setCurrentZoom(zoom);
4867 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4868 lyxrc.currentZoom, lyxrc.defaultZoom));
4870 guiApp->fontLoader().update();
4871 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4875 case LFUN_VC_REGISTER:
4876 case LFUN_VC_RENAME:
4878 case LFUN_VC_CHECK_IN:
4879 case LFUN_VC_CHECK_OUT:
4880 case LFUN_VC_REPO_UPDATE:
4881 case LFUN_VC_LOCKING_TOGGLE:
4882 case LFUN_VC_REVERT:
4883 case LFUN_VC_UNDO_LAST:
4884 case LFUN_VC_COMMAND:
4885 case LFUN_VC_COMPARE:
4886 dispatchVC(cmd, dr);
4889 case LFUN_SERVER_GOTO_FILE_ROW:
4890 if(goToFileRow(to_utf8(cmd.argument())))
4891 dr.screenUpdate(Update::Force | Update::FitCursor);
4894 case LFUN_LYX_ACTIVATE:
4898 case LFUN_WINDOW_RAISE:
4904 case LFUN_FORWARD_SEARCH: {
4905 // it seems safe to assume we have a document buffer, since
4906 // getStatus wants one.
4907 LASSERT(doc_buffer, break);
4908 Buffer const * doc_master = doc_buffer->masterBuffer();
4909 FileName const path(doc_master->temppath());
4910 string const texname = doc_master->isChild(doc_buffer)
4911 ? DocFileName(changeExtension(
4912 doc_buffer->absFileName(),
4913 "tex")).mangledFileName()
4914 : doc_buffer->latexName();
4915 string const fulltexname =
4916 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4917 string const mastername =
4918 removeExtension(doc_master->latexName());
4919 FileName const dviname(addName(path.absFileName(),
4920 addExtension(mastername, "dvi")));
4921 FileName const pdfname(addName(path.absFileName(),
4922 addExtension(mastername, "pdf")));
4923 bool const have_dvi = dviname.exists();
4924 bool const have_pdf = pdfname.exists();
4925 if (!have_dvi && !have_pdf) {
4926 dr.setMessage(_("Please, preview the document first."));
4929 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4930 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4931 string outname = dviname.onlyFileName();
4932 string command = lyxrc.forward_search_dvi;
4933 if ((!goto_dvi || goto_pdf) &&
4934 pdfname.lastModified() > dviname.lastModified()) {
4935 outname = pdfname.onlyFileName();
4936 command = lyxrc.forward_search_pdf;
4939 DocIterator cur = bv->cursor();
4940 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4941 LYXERR(Debug::ACTION, "Forward search: row:" << row
4943 if (row == -1 || command.empty()) {
4944 dr.setMessage(_("Couldn't proceed."));
4947 string texrow = convert<string>(row);
4949 command = subst(command, "$$n", texrow);
4950 command = subst(command, "$$f", fulltexname);
4951 command = subst(command, "$$t", texname);
4952 command = subst(command, "$$o", outname);
4954 volatile PathChanger p(path);
4956 one.startscript(Systemcall::DontWait, command);
4960 case LFUN_SPELLING_CONTINUOUSLY:
4961 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4962 dr.screenUpdate(Update::Force);
4965 case LFUN_CITATION_OPEN: {
4967 if (theFormats().getFormat("pdf"))
4968 pdfv = theFormats().getFormat("pdf")->viewer();
4969 if (theFormats().getFormat("ps"))
4970 psv = theFormats().getFormat("ps")->viewer();
4971 frontend::showTarget(argument, pdfv, psv);
4976 // The LFUN must be for one of BufferView, Buffer or Cursor;
4978 dispatchToBufferView(cmd, dr);
4982 // Need to update bv because many LFUNs here might have destroyed it
4983 bv = currentBufferView();
4985 // Clear non-empty selections
4986 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4988 Cursor & cur = bv->cursor();
4989 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4990 cur.clearSelection();
4996 bool GuiView::lfunUiToggle(string const & ui_component)
4998 if (ui_component == "scrollbar") {
4999 // hide() is of no help
5000 if (d.current_work_area_->verticalScrollBarPolicy() ==
5001 Qt::ScrollBarAlwaysOff)
5003 d.current_work_area_->setVerticalScrollBarPolicy(
5004 Qt::ScrollBarAsNeeded);
5006 d.current_work_area_->setVerticalScrollBarPolicy(
5007 Qt::ScrollBarAlwaysOff);
5008 } else if (ui_component == "statusbar") {
5009 statusBar()->setVisible(!statusBar()->isVisible());
5010 } else if (ui_component == "menubar") {
5011 menuBar()->setVisible(!menuBar()->isVisible());
5012 } else if (ui_component == "zoomlevel") {
5013 zoom_value_->setVisible(!zoom_value_->isVisible());
5014 } else if (ui_component == "zoomslider") {
5015 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5016 zoom_in_->setVisible(zoom_slider_->isVisible());
5017 zoom_out_->setVisible(zoom_slider_->isVisible());
5018 } else if (ui_component == "statistics-w") {
5019 word_count_enabled_ = !word_count_enabled_;
5022 } else if (ui_component == "statistics-cb") {
5023 char_count_enabled_ = !char_count_enabled_;
5026 } else if (ui_component == "statistics-c") {
5027 char_nb_count_enabled_ = !char_nb_count_enabled_;
5030 } else if (ui_component == "frame") {
5031 int const l = contentsMargins().left();
5033 //are the frames in default state?
5034 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5036 #if QT_VERSION > 0x050903
5037 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5039 setContentsMargins(-2, -2, -2, -2);
5041 #if QT_VERSION > 0x050903
5042 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5044 setContentsMargins(0, 0, 0, 0);
5047 if (ui_component == "fullscreen") {
5051 stat_counts_->setVisible(statsEnabled());
5056 void GuiView::toggleFullScreen()
5058 setWindowState(windowState() ^ Qt::WindowFullScreen);
5062 Buffer const * GuiView::updateInset(Inset const * inset)
5067 Buffer const * inset_buffer = &(inset->buffer());
5069 for (int i = 0; i != d.splitter_->count(); ++i) {
5070 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5073 Buffer const * buffer = &(wa->bufferView().buffer());
5074 if (inset_buffer == buffer)
5075 wa->scheduleRedraw(true);
5077 return inset_buffer;
5081 void GuiView::restartCaret()
5083 /* When we move around, or type, it's nice to be able to see
5084 * the caret immediately after the keypress.
5086 if (d.current_work_area_)
5087 d.current_work_area_->startBlinkingCaret();
5089 // Take this occasion to update the other GUI elements.
5095 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5097 if (d.current_work_area_)
5098 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5103 // This list should be kept in sync with the list of insets in
5104 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5105 // dialog should have the same name as the inset.
5106 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5107 // docs in LyXAction.cpp.
5109 char const * const dialognames[] = {
5111 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5112 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5113 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5114 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5115 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5116 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5117 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5118 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5120 char const * const * const end_dialognames =
5121 dialognames + (sizeof(dialognames) / sizeof(char *));
5125 cmpCStr(char const * name) : name_(name) {}
5126 bool operator()(char const * other) {
5127 return strcmp(other, name_) == 0;
5134 bool isValidName(string const & name)
5136 return find_if(dialognames, end_dialognames,
5137 cmpCStr(name.c_str())) != end_dialognames;
5143 void GuiView::resetDialogs()
5145 // Make sure that no LFUN uses any GuiView.
5146 guiApp->setCurrentView(nullptr);
5150 constructToolbars();
5151 guiApp->menus().fillMenuBar(menuBar(), this, false);
5152 d.layout_->updateContents(true);
5153 // Now update controls with current buffer.
5154 guiApp->setCurrentView(this);
5160 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5162 for (QObject * child: widget->children()) {
5163 if (child->inherits("QGroupBox")) {
5164 QGroupBox * box = (QGroupBox*) child;
5167 flatGroupBoxes(child, flag);
5173 Dialog * GuiView::find(string const & name, bool hide_it) const
5175 if (!isValidName(name))
5178 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5180 if (it != d.dialogs_.end()) {
5182 it->second->hideView();
5183 return it->second.get();
5189 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5191 Dialog * dialog = find(name, hide_it);
5192 if (dialog != nullptr)
5195 dialog = build(name);
5196 d.dialogs_[name].reset(dialog);
5197 // Force a uniform style for group boxes
5198 // On Mac non-flat works better, on Linux flat is standard
5199 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5200 if (lyxrc.allow_geometry_session)
5201 dialog->restoreSession();
5208 void GuiView::showDialog(string const & name, string const & sdata,
5211 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5215 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5221 const string name = fromqstr(qname);
5222 const string sdata = fromqstr(qdata);
5226 Dialog * dialog = findOrBuild(name, false);
5228 bool const visible = dialog->isVisibleView();
5229 dialog->showData(sdata);
5230 if (currentBufferView())
5231 currentBufferView()->editInset(name, inset);
5232 // We only set the focus to the new dialog if it was not yet
5233 // visible in order not to change the existing previous behaviour
5235 // activateWindow is needed for floating dockviews
5236 dialog->asQWidget()->raise();
5237 dialog->asQWidget()->activateWindow();
5238 if (dialog->wantInitialFocus())
5239 dialog->asQWidget()->setFocus();
5243 catch (ExceptionMessage const &) {
5251 bool GuiView::isDialogVisible(string const & name) const
5253 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5254 if (it == d.dialogs_.end())
5256 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5260 void GuiView::hideDialog(string const & name, Inset * inset)
5262 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5263 if (it == d.dialogs_.end())
5267 if (!currentBufferView())
5269 if (inset != currentBufferView()->editedInset(name))
5273 Dialog * const dialog = it->second.get();
5274 if (dialog->isVisibleView())
5276 if (currentBufferView())
5277 currentBufferView()->editInset(name, nullptr);
5281 void GuiView::disconnectDialog(string const & name)
5283 if (!isValidName(name))
5285 if (currentBufferView())
5286 currentBufferView()->editInset(name, nullptr);
5290 void GuiView::hideAll() const
5292 for(auto const & dlg_p : d.dialogs_)
5293 dlg_p.second->hideView();
5297 void GuiView::updateDialogs()
5299 for(auto const & dlg_p : d.dialogs_) {
5300 Dialog * dialog = dlg_p.second.get();
5302 if (dialog->needBufferOpen() && !documentBufferView())
5303 hideDialog(fromqstr(dialog->name()), nullptr);
5304 else if (dialog->isVisibleView())
5305 dialog->checkStatus();
5313 Dialog * GuiView::build(string const & name)
5315 return createDialog(*this, name);
5319 SEMenu::SEMenu(QWidget * parent)
5321 QAction * action = addAction(qt_("Disable Shell Escape"));
5322 connect(action, SIGNAL(triggered()),
5323 parent, SLOT(disableShellEscape()));
5327 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5329 if (event->button() == Qt::LeftButton) {
5334 } // namespace frontend
5337 #include "moc_GuiView.cpp"