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.
1497 title += from_ascii(" - LyX");
1499 setWindowTitle(toqstr(title));
1500 // Sets the path for the window: this is used by OSX to
1501 // allow a context click on the title bar showing a menu
1502 // with the path up to the file
1503 setWindowFilePath(toqstr(buf.absFileName()));
1504 // Tell Qt whether the current document is changed
1505 setWindowModified(!buf.isClean());
1507 if (buf.params().shell_escape)
1508 shell_escape_->show();
1510 shell_escape_->hide();
1512 if (buf.hasReadonlyFlag())
1517 if (buf.lyxvc().inUse()) {
1518 version_control_->show();
1519 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1521 version_control_->hide();
1525 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1527 if (d.current_work_area_)
1528 // disconnect the current work area from all slots
1529 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1531 disconnectBufferView();
1532 connectBufferView(wa->bufferView());
1533 connectBuffer(wa->bufferView().buffer());
1534 d.current_work_area_ = wa;
1535 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1536 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1537 QObject::connect(wa, SIGNAL(busy(bool)),
1538 this, SLOT(setBusy(bool)));
1539 // connection of a signal to a signal
1540 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1541 this, SIGNAL(bufferViewChanged()));
1542 Q_EMIT updateWindowTitle(wa);
1543 Q_EMIT bufferViewChanged();
1547 void GuiView::onBufferViewChanged()
1550 // Buffer-dependent dialogs must be updated. This is done here because
1551 // some dialogs require buffer()->text.
1553 zoom_slider_->setEnabled(currentBufferView());
1554 zoom_value_->setEnabled(currentBufferView());
1555 zoom_in_->setEnabled(currentBufferView());
1556 zoom_out_->setEnabled(currentBufferView());
1560 void GuiView::on_lastWorkAreaRemoved()
1563 // We already are in a close event. Nothing more to do.
1566 if (d.splitter_->count() > 1)
1567 // We have a splitter so don't close anything.
1570 // Reset and updates the dialogs.
1571 Q_EMIT bufferViewChanged();
1576 if (lyxrc.open_buffers_in_tabs)
1577 // Nothing more to do, the window should stay open.
1580 if (guiApp->viewIds().size() > 1) {
1586 // On Mac we also close the last window because the application stay
1587 // resident in memory. On other platforms we don't close the last
1588 // window because this would quit the application.
1594 void GuiView::updateStatusBar()
1596 // let the user see the explicit message
1597 if (d.statusbar_timer_.isActive())
1604 void GuiView::showMessage()
1608 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1609 if (msg.isEmpty()) {
1610 BufferView const * bv = currentBufferView();
1612 msg = toqstr(bv->cursor().currentState(devel_mode_));
1614 msg = qt_("Welcome to LyX!");
1616 statusBar()->showMessage(msg);
1620 bool GuiView::statsEnabled() const
1622 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1626 bool GuiView::event(QEvent * e)
1630 // Useful debug code:
1631 //case QEvent::ActivationChange:
1632 //case QEvent::WindowDeactivate:
1633 //case QEvent::Paint:
1634 //case QEvent::Enter:
1635 //case QEvent::Leave:
1636 //case QEvent::HoverEnter:
1637 //case QEvent::HoverLeave:
1638 //case QEvent::HoverMove:
1639 //case QEvent::StatusTip:
1640 //case QEvent::DragEnter:
1641 //case QEvent::DragLeave:
1642 //case QEvent::Drop:
1645 case QEvent::WindowStateChange: {
1646 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1647 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1648 bool result = QMainWindow::event(e);
1649 bool nfstate = (windowState() & Qt::WindowFullScreen);
1650 if (!ofstate && nfstate) {
1651 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1652 // switch to full-screen state
1653 if (lyxrc.full_screen_statusbar)
1654 statusBar()->hide();
1655 if (lyxrc.full_screen_menubar)
1657 if (lyxrc.full_screen_toolbars) {
1658 for (auto const & tb_p : d.toolbars_)
1659 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1660 tb_p.second->hide();
1662 for (int i = 0; i != d.splitter_->count(); ++i)
1663 d.tabWorkArea(i)->setFullScreen(true);
1664 #if QT_VERSION > 0x050903
1665 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1666 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1668 setContentsMargins(-2, -2, -2, -2);
1670 hideDialogs("prefs", nullptr);
1671 } else if (ofstate && !nfstate) {
1672 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1673 // switch back from full-screen state
1674 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1675 statusBar()->show();
1676 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1678 if (lyxrc.full_screen_toolbars) {
1679 for (auto const & tb_p : d.toolbars_)
1680 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1681 tb_p.second->show();
1684 for (int i = 0; i != d.splitter_->count(); ++i)
1685 d.tabWorkArea(i)->setFullScreen(false);
1686 #if QT_VERSION > 0x050903
1687 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1689 setContentsMargins(0, 0, 0, 0);
1694 case QEvent::WindowActivate: {
1695 GuiView * old_view = guiApp->currentView();
1696 if (this == old_view) {
1698 return QMainWindow::event(e);
1700 if (old_view && old_view->currentBufferView()) {
1701 // save current selection to the selection buffer to allow
1702 // middle-button paste in this window.
1703 cap::saveSelection(old_view->currentBufferView()->cursor());
1705 guiApp->setCurrentView(this);
1706 if (d.current_work_area_)
1707 on_currentWorkAreaChanged(d.current_work_area_);
1711 return QMainWindow::event(e);
1714 case QEvent::ShortcutOverride: {
1716 if (isFullScreen() && menuBar()->isHidden()) {
1717 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1718 // FIXME: we should also try to detect special LyX shortcut such as
1719 // Alt-P and Alt-M. Right now there is a hack in
1720 // GuiWorkArea::processKeySym() that hides again the menubar for
1722 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1724 return QMainWindow::event(e);
1727 return QMainWindow::event(e);
1730 case QEvent::ApplicationPaletteChange: {
1731 // runtime switch from/to dark mode
1733 return QMainWindow::event(e);
1736 case QEvent::Gesture: {
1737 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1738 QGesture *gp = ge->gesture(Qt::PinchGesture);
1740 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1741 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1742 qreal totalScaleFactor = pinch->totalScaleFactor();
1743 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1744 if (pinch->state() == Qt::GestureStarted) {
1745 initialZoom_ = lyxrc.currentZoom;
1746 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1748 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1749 qreal factor = initialZoom_ * totalScaleFactor;
1750 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1751 zoomValueChanged(factor);
1754 return QMainWindow::event(e);
1758 return QMainWindow::event(e);
1762 void GuiView::resetWindowTitle()
1764 setWindowTitle(qt_("LyX"));
1767 bool GuiView::focusNextPrevChild(bool /*next*/)
1774 bool GuiView::busy() const
1780 void GuiView::setBusy(bool busy)
1782 bool const busy_before = busy_ > 0;
1783 busy ? ++busy_ : --busy_;
1784 if ((busy_ > 0) == busy_before)
1785 // busy state didn't change
1789 QApplication::setOverrideCursor(Qt::WaitCursor);
1792 QApplication::restoreOverrideCursor();
1797 void GuiView::resetCommandExecute()
1799 command_execute_ = false;
1804 double GuiView::pixelRatio() const
1806 return qt_scale_factor * devicePixelRatio();
1810 GuiWorkArea * GuiView::workArea(int index)
1812 if (TabWorkArea * twa = d.currentTabWorkArea())
1813 if (index < twa->count())
1814 return twa->workArea(index);
1819 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1821 if (currentWorkArea()
1822 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1823 return currentWorkArea();
1824 if (TabWorkArea * twa = d.currentTabWorkArea())
1825 return twa->workArea(buffer);
1830 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1832 // Automatically create a TabWorkArea if there are none yet.
1833 TabWorkArea * tab_widget = d.splitter_->count()
1834 ? d.currentTabWorkArea() : addTabWorkArea();
1835 return tab_widget->addWorkArea(buffer, *this);
1839 TabWorkArea * GuiView::addTabWorkArea()
1841 TabWorkArea * twa = new TabWorkArea;
1842 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1843 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1844 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1845 this, SLOT(on_lastWorkAreaRemoved()));
1847 d.splitter_->addWidget(twa);
1848 d.stack_widget_->setCurrentWidget(d.splitter_);
1853 GuiWorkArea const * GuiView::currentWorkArea() const
1855 return d.current_work_area_;
1859 GuiWorkArea * GuiView::currentWorkArea()
1861 return d.current_work_area_;
1865 GuiWorkArea const * GuiView::currentMainWorkArea() const
1867 if (!d.currentTabWorkArea())
1869 return d.currentTabWorkArea()->currentWorkArea();
1873 GuiWorkArea * GuiView::currentMainWorkArea()
1875 if (!d.currentTabWorkArea())
1877 return d.currentTabWorkArea()->currentWorkArea();
1881 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1883 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1885 d.current_work_area_ = nullptr;
1887 Q_EMIT bufferViewChanged();
1891 // FIXME: I've no clue why this is here and why it accesses
1892 // theGuiApp()->currentView, which might be 0 (bug 6464).
1893 // See also 27525 (vfr).
1894 if (theGuiApp()->currentView() == this
1895 && theGuiApp()->currentView()->currentWorkArea() == wa)
1898 if (currentBufferView())
1899 cap::saveSelection(currentBufferView()->cursor());
1901 theGuiApp()->setCurrentView(this);
1902 d.current_work_area_ = wa;
1904 // We need to reset this now, because it will need to be
1905 // right if the tabWorkArea gets reset in the for loop. We
1906 // will change it back if we aren't in that case.
1907 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1908 d.current_main_work_area_ = wa;
1910 for (int i = 0; i != d.splitter_->count(); ++i) {
1911 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1912 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1913 << ", Current main wa: " << currentMainWorkArea());
1918 d.current_main_work_area_ = old_cmwa;
1920 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1921 on_currentWorkAreaChanged(wa);
1922 BufferView & bv = wa->bufferView();
1923 bv.cursor().fixIfBroken();
1925 wa->setUpdatesEnabled(true);
1926 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1930 void GuiView::removeWorkArea(GuiWorkArea * wa)
1932 LASSERT(wa, return);
1933 if (wa == d.current_work_area_) {
1935 disconnectBufferView();
1936 d.current_work_area_ = nullptr;
1937 d.current_main_work_area_ = nullptr;
1940 bool found_twa = false;
1941 for (int i = 0; i != d.splitter_->count(); ++i) {
1942 TabWorkArea * twa = d.tabWorkArea(i);
1943 if (twa->removeWorkArea(wa)) {
1944 // Found in this tab group, and deleted the GuiWorkArea.
1946 if (twa->count() != 0) {
1947 if (d.current_work_area_ == nullptr)
1948 // This means that we are closing the current GuiWorkArea, so
1949 // switch to the next GuiWorkArea in the found TabWorkArea.
1950 setCurrentWorkArea(twa->currentWorkArea());
1952 // No more WorkAreas in this tab group, so delete it.
1959 // It is not a tabbed work area (i.e., the search work area), so it
1960 // should be deleted by other means.
1961 LASSERT(found_twa, return);
1963 if (d.current_work_area_ == nullptr) {
1964 if (d.splitter_->count() != 0) {
1965 TabWorkArea * twa = d.currentTabWorkArea();
1966 setCurrentWorkArea(twa->currentWorkArea());
1968 // No more work areas, switch to the background widget.
1969 setCurrentWorkArea(nullptr);
1975 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1977 for (int i = 0; i < d.splitter_->count(); ++i)
1978 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1981 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1982 return fr->isVisible() && fr->hasWorkArea(wa);
1986 LayoutBox * GuiView::getLayoutDialog() const
1992 void GuiView::updateLayoutList()
1995 d.layout_->updateContents(false);
1999 void GuiView::updateToolbars()
2001 if (d.current_work_area_) {
2003 if (d.current_work_area_->bufferView().cursor().inMathed()
2004 && !d.current_work_area_->bufferView().cursor().inRegexped())
2005 context |= Toolbars::MATH;
2006 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2007 context |= Toolbars::TABLE;
2008 if (currentBufferView()->buffer().areChangesPresent()
2009 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2010 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2011 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2012 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2013 context |= Toolbars::REVIEW;
2014 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2015 context |= Toolbars::MATHMACROTEMPLATE;
2016 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2017 context |= Toolbars::IPA;
2018 if (command_execute_)
2019 context |= Toolbars::MINIBUFFER;
2020 if (minibuffer_focus_) {
2021 context |= Toolbars::MINIBUFFER_FOCUS;
2022 minibuffer_focus_ = false;
2025 for (auto const & tb_p : d.toolbars_)
2026 tb_p.second->update(context);
2028 for (auto const & tb_p : d.toolbars_)
2029 tb_p.second->update();
2033 void GuiView::refillToolbars()
2035 DynamicMenuButton::resetIconCache();
2036 for (auto const & tb_p : d.toolbars_)
2037 tb_p.second->refill();
2041 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2043 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2044 LASSERT(newBuffer, return);
2046 GuiWorkArea * wa = workArea(*newBuffer);
2047 if (wa == nullptr) {
2049 newBuffer->masterBuffer()->updateBuffer();
2051 wa = addWorkArea(*newBuffer);
2052 // scroll to the position when the BufferView was last closed
2053 if (lyxrc.use_lastfilepos) {
2054 LastFilePosSection::FilePos filepos =
2055 theSession().lastFilePos().load(newBuffer->fileName());
2056 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2059 //Disconnect the old buffer...there's no new one.
2062 connectBuffer(*newBuffer);
2063 connectBufferView(wa->bufferView());
2065 setCurrentWorkArea(wa);
2069 void GuiView::connectBuffer(Buffer & buf)
2071 buf.setGuiDelegate(this);
2075 void GuiView::disconnectBuffer()
2077 if (d.current_work_area_)
2078 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2082 void GuiView::connectBufferView(BufferView & bv)
2084 bv.setGuiDelegate(this);
2088 void GuiView::disconnectBufferView()
2090 if (d.current_work_area_)
2091 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2095 void GuiView::errors(string const & error_type, bool from_master)
2097 BufferView const * const bv = currentBufferView();
2101 ErrorList const & el = from_master ?
2102 bv->buffer().masterBuffer()->errorList(error_type) :
2103 bv->buffer().errorList(error_type);
2108 string err = error_type;
2110 err = "from_master|" + error_type;
2111 showDialog("errorlist", err);
2115 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2117 d.toc_models_.updateItem(toqstr(type), dit);
2121 void GuiView::structureChanged()
2123 // This is called from the Buffer, which has no way to ensure that cursors
2124 // in BufferView remain valid.
2125 if (documentBufferView())
2126 documentBufferView()->cursor().sanitize();
2127 // FIXME: This is slightly expensive, though less than the tocBackend update
2128 // (#9880). This also resets the view in the Toc Widget (#6675).
2129 d.toc_models_.reset(documentBufferView());
2130 // Navigator needs more than a simple update in this case. It needs to be
2132 updateDialog("toc", "");
2136 void GuiView::updateDialog(string const & name, string const & sdata)
2138 if (!isDialogVisible(name))
2141 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2142 if (it == d.dialogs_.end())
2145 Dialog * const dialog = it->second.get();
2146 if (dialog->isVisibleView())
2147 dialog->initialiseParams(sdata);
2151 BufferView * GuiView::documentBufferView()
2153 return currentMainWorkArea()
2154 ? ¤tMainWorkArea()->bufferView()
2159 BufferView const * GuiView::documentBufferView() const
2161 return currentMainWorkArea()
2162 ? ¤tMainWorkArea()->bufferView()
2167 BufferView * GuiView::currentBufferView()
2169 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2173 BufferView const * GuiView::currentBufferView() const
2175 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2179 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2180 Buffer const * orig, Buffer * clone)
2182 bool const success = clone->autoSave();
2184 busyBuffers.remove(orig);
2186 ? _("Automatic save done.")
2187 : _("Automatic save failed!");
2191 void GuiView::autoSave()
2193 LYXERR(Debug::INFO, "Running autoSave()");
2195 Buffer * buffer = documentBufferView()
2196 ? &documentBufferView()->buffer() : nullptr;
2198 resetAutosaveTimers();
2202 GuiViewPrivate::busyBuffers.insert(buffer);
2203 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2204 buffer, buffer->cloneBufferOnly());
2205 d.autosave_watcher_.setFuture(f);
2206 resetAutosaveTimers();
2210 void GuiView::resetAutosaveTimers()
2213 d.autosave_timeout_.restart();
2219 double zoomRatio(FuncRequest const & cmd, double const zr)
2221 if (cmd.argument().empty()) {
2222 if (cmd.action() == LFUN_BUFFER_ZOOM)
2224 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2226 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2229 if (cmd.action() == LFUN_BUFFER_ZOOM)
2230 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2231 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2232 return zr + convert<int>(cmd.argument()) / 100.0;
2233 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2234 return zr - convert<int>(cmd.argument()) / 100.0;
2241 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2244 Buffer * buf = currentBufferView()
2245 ? ¤tBufferView()->buffer() : nullptr;
2246 Buffer * doc_buffer = documentBufferView()
2247 ? &(documentBufferView()->buffer()) : nullptr;
2250 /* In LyX/Mac, when a dialog is open, the menus of the
2251 application can still be accessed without giving focus to
2252 the main window. In this case, we want to disable the menu
2253 entries that are buffer-related.
2254 This code must not be used on Linux and Windows, since it
2255 would disable buffer-related entries when hovering over the
2256 menu (see bug #9574).
2258 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2264 // Check whether we need a buffer
2265 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2266 // no, exit directly
2267 flag.message(from_utf8(N_("Command not allowed with"
2268 "out any document open")));
2269 flag.setEnabled(false);
2273 if (cmd.origin() == FuncRequest::TOC) {
2274 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2275 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2276 flag.setEnabled(false);
2280 switch(cmd.action()) {
2281 case LFUN_BUFFER_IMPORT:
2284 case LFUN_MASTER_BUFFER_EXPORT:
2286 && (doc_buffer->parent() != nullptr
2287 || doc_buffer->hasChildren())
2288 && !d.processing_thread_watcher_.isRunning()
2289 // this launches a dialog, which would be in the wrong Buffer
2290 && !(::lyx::operator==(cmd.argument(), "custom"));
2293 case LFUN_MASTER_BUFFER_UPDATE:
2294 case LFUN_MASTER_BUFFER_VIEW:
2296 && (doc_buffer->parent() != nullptr
2297 || doc_buffer->hasChildren())
2298 && !d.processing_thread_watcher_.isRunning();
2301 case LFUN_BUFFER_UPDATE:
2302 case LFUN_BUFFER_VIEW: {
2303 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2307 string format = to_utf8(cmd.argument());
2308 if (cmd.argument().empty())
2309 format = doc_buffer->params().getDefaultOutputFormat();
2310 enable = doc_buffer->params().isExportable(format, true);
2314 case LFUN_BUFFER_RELOAD:
2315 enable = doc_buffer && !doc_buffer->isUnnamed()
2316 && doc_buffer->fileName().exists()
2317 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2320 case LFUN_BUFFER_RESET_EXPORT:
2321 enable = doc_buffer != nullptr;
2324 case LFUN_BUFFER_CHILD_OPEN:
2325 enable = doc_buffer != nullptr;
2328 case LFUN_MASTER_BUFFER_FORALL: {
2329 if (doc_buffer == nullptr) {
2330 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2334 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2335 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2336 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2341 for (Buffer * buf : doc_buffer->allRelatives()) {
2342 GuiWorkArea * wa = workArea(*buf);
2345 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2346 enable = flag.enabled();
2353 case LFUN_BUFFER_WRITE:
2354 enable = doc_buffer && (doc_buffer->isUnnamed()
2355 || (!doc_buffer->isClean()
2356 || cmd.argument() == "force"));
2359 //FIXME: This LFUN should be moved to GuiApplication.
2360 case LFUN_BUFFER_WRITE_ALL: {
2361 // We enable the command only if there are some modified buffers
2362 Buffer * first = theBufferList().first();
2367 // We cannot use a for loop as the buffer list is a cycle.
2369 if (!b->isClean()) {
2373 b = theBufferList().next(b);
2374 } while (b != first);
2378 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2379 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2382 case LFUN_BUFFER_EXPORT: {
2383 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2387 return doc_buffer->getStatus(cmd, flag);
2390 case LFUN_BUFFER_EXPORT_AS:
2391 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2396 case LFUN_BUFFER_WRITE_AS:
2397 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2398 enable = doc_buffer != nullptr;
2401 case LFUN_EXPORT_CANCEL:
2402 enable = d.processing_thread_watcher_.isRunning();
2405 case LFUN_BUFFER_CLOSE:
2406 case LFUN_VIEW_CLOSE:
2407 enable = doc_buffer != nullptr;
2410 case LFUN_BUFFER_CLOSE_ALL:
2411 enable = theBufferList().last() != theBufferList().first();
2414 case LFUN_BUFFER_CHKTEX: {
2415 // hide if we have no checktex command
2416 if (lyxrc.chktex_command.empty()) {
2417 flag.setUnknown(true);
2421 if (!doc_buffer || !doc_buffer->params().isLatex()
2422 || d.processing_thread_watcher_.isRunning()) {
2423 // grey out, don't hide
2431 case LFUN_VIEW_SPLIT:
2432 if (cmd.getArg(0) == "vertical")
2433 enable = doc_buffer && (d.splitter_->count() == 1 ||
2434 d.splitter_->orientation() == Qt::Vertical);
2436 enable = doc_buffer && (d.splitter_->count() == 1 ||
2437 d.splitter_->orientation() == Qt::Horizontal);
2440 case LFUN_TAB_GROUP_CLOSE:
2441 enable = d.tabWorkAreaCount() > 1;
2444 case LFUN_DEVEL_MODE_TOGGLE:
2445 flag.setOnOff(devel_mode_);
2448 case LFUN_TOOLBAR_SET: {
2449 string const name = cmd.getArg(0);
2450 string const state = cmd.getArg(1);
2451 if (name.empty() || state.empty()) {
2453 docstring const msg =
2454 _("Function toolbar-set requires two arguments!");
2458 if (state != "on" && state != "off" && state != "auto") {
2460 docstring const msg =
2461 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2466 if (GuiToolbar * t = toolbar(name)) {
2467 bool const autovis = t->visibility() & Toolbars::AUTO;
2469 flag.setOnOff(t->isVisible() && !autovis);
2470 else if (state == "off")
2471 flag.setOnOff(!t->isVisible() && !autovis);
2472 else if (state == "auto")
2473 flag.setOnOff(autovis);
2476 docstring const msg =
2477 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2483 case LFUN_TOOLBAR_TOGGLE: {
2484 string const name = cmd.getArg(0);
2485 if (GuiToolbar * t = toolbar(name))
2486 flag.setOnOff(t->isVisible());
2489 docstring const msg =
2490 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2496 case LFUN_TOOLBAR_MOVABLE: {
2497 string const name = cmd.getArg(0);
2498 // use negation since locked == !movable
2500 // toolbar name * locks all toolbars
2501 flag.setOnOff(!toolbarsMovable_);
2502 else if (GuiToolbar * t = toolbar(name))
2503 flag.setOnOff(!(t->isMovable()));
2506 docstring const msg =
2507 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2513 case LFUN_ICON_SIZE:
2514 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2517 case LFUN_DROP_LAYOUTS_CHOICE:
2518 enable = buf != nullptr;
2521 case LFUN_UI_TOGGLE:
2522 if (cmd.argument() == "zoomlevel") {
2523 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2524 } else if (cmd.argument() == "zoomslider") {
2525 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2526 } else if (cmd.argument() == "statistics-w") {
2527 flag.setOnOff(word_count_enabled_);
2528 } else if (cmd.argument() == "statistics-cb") {
2529 flag.setOnOff(char_count_enabled_);
2530 } else if (cmd.argument() == "statistics-c") {
2531 flag.setOnOff(char_nb_count_enabled_);
2533 flag.setOnOff(isFullScreen());
2536 case LFUN_DIALOG_DISCONNECT_INSET:
2539 case LFUN_DIALOG_HIDE:
2540 // FIXME: should we check if the dialog is shown?
2543 case LFUN_DIALOG_TOGGLE:
2544 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2547 case LFUN_DIALOG_SHOW: {
2548 string const name = cmd.getArg(0);
2550 enable = name == "aboutlyx"
2551 || name == "file" //FIXME: should be removed.
2552 || name == "lyxfiles"
2554 || name == "texinfo"
2555 || name == "progress"
2556 || name == "compare";
2557 else if (name == "character" || name == "symbols"
2558 || name == "mathdelimiter" || name == "mathmatrix") {
2559 if (!buf || buf->isReadonly())
2562 Cursor const & cur = currentBufferView()->cursor();
2563 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2566 else if (name == "latexlog")
2567 enable = FileName(doc_buffer->logName()).isReadableFile();
2568 else if (name == "spellchecker")
2569 enable = theSpellChecker()
2570 && !doc_buffer->text().empty();
2571 else if (name == "vclog")
2572 enable = doc_buffer->lyxvc().inUse();
2576 case LFUN_DIALOG_UPDATE: {
2577 string const name = cmd.getArg(0);
2579 enable = name == "prefs";
2583 case LFUN_COMMAND_EXECUTE:
2585 case LFUN_MENU_OPEN:
2586 // Nothing to check.
2589 case LFUN_COMPLETION_INLINE:
2590 if (!d.current_work_area_
2591 || !d.current_work_area_->completer().inlinePossible(
2592 currentBufferView()->cursor()))
2596 case LFUN_COMPLETION_POPUP:
2597 if (!d.current_work_area_
2598 || !d.current_work_area_->completer().popupPossible(
2599 currentBufferView()->cursor()))
2604 if (!d.current_work_area_
2605 || !d.current_work_area_->completer().inlinePossible(
2606 currentBufferView()->cursor()))
2610 case LFUN_COMPLETION_ACCEPT:
2611 if (!d.current_work_area_
2612 || (!d.current_work_area_->completer().popupVisible()
2613 && !d.current_work_area_->completer().inlineVisible()
2614 && !d.current_work_area_->completer().completionAvailable()))
2618 case LFUN_COMPLETION_CANCEL:
2619 if (!d.current_work_area_
2620 || (!d.current_work_area_->completer().popupVisible()
2621 && !d.current_work_area_->completer().inlineVisible()))
2625 case LFUN_BUFFER_ZOOM_OUT:
2626 case LFUN_BUFFER_ZOOM_IN:
2627 case LFUN_BUFFER_ZOOM: {
2628 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2629 if (zoom < zoom_min_) {
2630 docstring const msg =
2631 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2634 } else if (zoom > zoom_max_) {
2635 docstring const msg =
2636 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2640 enable = doc_buffer;
2645 case LFUN_BUFFER_MOVE_NEXT:
2646 case LFUN_BUFFER_MOVE_PREVIOUS:
2647 // we do not cycle when moving
2648 case LFUN_BUFFER_NEXT:
2649 case LFUN_BUFFER_PREVIOUS:
2650 // because we cycle, it doesn't matter whether on first or last
2651 enable = (d.currentTabWorkArea()->count() > 1);
2653 case LFUN_BUFFER_SWITCH:
2654 // toggle on the current buffer, but do not toggle off
2655 // the other ones (is that a good idea?)
2657 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2658 flag.setOnOff(true);
2661 case LFUN_VC_REGISTER:
2662 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2664 case LFUN_VC_RENAME:
2665 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2668 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2670 case LFUN_VC_CHECK_IN:
2671 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2673 case LFUN_VC_CHECK_OUT:
2674 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2676 case LFUN_VC_LOCKING_TOGGLE:
2677 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2678 && doc_buffer->lyxvc().lockingToggleEnabled();
2679 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2681 case LFUN_VC_REVERT:
2682 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2683 && !doc_buffer->hasReadonlyFlag();
2685 case LFUN_VC_UNDO_LAST:
2686 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2688 case LFUN_VC_REPO_UPDATE:
2689 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2691 case LFUN_VC_COMMAND: {
2692 if (cmd.argument().empty())
2694 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2698 case LFUN_VC_COMPARE:
2699 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2702 case LFUN_SERVER_GOTO_FILE_ROW:
2703 case LFUN_LYX_ACTIVATE:
2704 case LFUN_WINDOW_RAISE:
2706 case LFUN_FORWARD_SEARCH:
2707 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2708 doc_buffer && doc_buffer->isSyncTeXenabled();
2711 case LFUN_FILE_INSERT_PLAINTEXT:
2712 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2713 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2716 case LFUN_SPELLING_CONTINUOUSLY:
2717 flag.setOnOff(lyxrc.spellcheck_continuously);
2720 case LFUN_CITATION_OPEN:
2729 flag.setEnabled(false);
2735 static FileName selectTemplateFile()
2737 FileDialog dlg(qt_("Select template file"));
2738 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2739 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2741 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2742 QStringList(qt_("LyX Documents (*.lyx)")));
2744 if (result.first == FileDialog::Later)
2746 if (result.second.isEmpty())
2748 return FileName(fromqstr(result.second));
2752 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2756 Buffer * newBuffer = nullptr;
2758 newBuffer = checkAndLoadLyXFile(filename);
2759 } catch (ExceptionMessage const &) {
2766 message(_("Document not loaded."));
2770 setBuffer(newBuffer);
2771 newBuffer->errors("Parse");
2774 theSession().lastFiles().add(filename);
2775 theSession().writeFile();
2782 void GuiView::openDocument(string const & fname)
2784 string initpath = lyxrc.document_path;
2786 if (documentBufferView()) {
2787 string const trypath = documentBufferView()->buffer().filePath();
2788 // If directory is writeable, use this as default.
2789 if (FileName(trypath).isDirWritable())
2795 if (fname.empty()) {
2796 FileDialog dlg(qt_("Select document to open"));
2797 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2798 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2800 QStringList const filter({
2801 qt_("LyX Documents (*.lyx)"),
2802 qt_("LyX Document Backups (*.lyx~)"),
2803 qt_("All Files (*.*)")
2805 FileDialog::Result result =
2806 dlg.open(toqstr(initpath), filter);
2808 if (result.first == FileDialog::Later)
2811 filename = fromqstr(result.second);
2813 // check selected filename
2814 if (filename.empty()) {
2815 message(_("Canceled."));
2821 // get absolute path of file and add ".lyx" to the filename if
2823 FileName const fullname =
2824 fileSearch(string(), filename, "lyx", support::may_not_exist);
2825 if (!fullname.empty())
2826 filename = fullname.absFileName();
2828 if (!fullname.onlyPath().isDirectory()) {
2829 Alert::warning(_("Invalid filename"),
2830 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2831 from_utf8(fullname.absFileName())));
2835 // if the file doesn't exist and isn't already open (bug 6645),
2836 // let the user create one
2837 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2838 !LyXVC::file_not_found_hook(fullname)) {
2839 // the user specifically chose this name. Believe him.
2840 Buffer * const b = newFile(filename, string(), true);
2846 docstring const disp_fn = makeDisplayPath(filename);
2847 message(bformat(_("Opening document %1$s..."), disp_fn));
2850 Buffer * buf = loadDocument(fullname);
2852 str2 = bformat(_("Document %1$s opened."), disp_fn);
2853 if (buf->lyxvc().inUse())
2854 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2855 " " + _("Version control detected.");
2857 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2862 // FIXME: clean that
2863 static bool import(GuiView * lv, FileName const & filename,
2864 string const & format, ErrorList & errorList)
2866 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2868 string loader_format;
2869 vector<string> loaders = theConverters().loaders();
2870 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2871 for (string const & loader : loaders) {
2872 if (!theConverters().isReachable(format, loader))
2875 string const tofile =
2876 support::changeExtension(filename.absFileName(),
2877 theFormats().extension(loader));
2878 if (theConverters().convert(nullptr, filename, FileName(tofile),
2879 filename, format, loader, errorList) != Converters::SUCCESS)
2881 loader_format = loader;
2884 if (loader_format.empty()) {
2885 frontend::Alert::error(_("Couldn't import file"),
2886 bformat(_("No information for importing the format %1$s."),
2887 translateIfPossible(theFormats().prettyName(format))));
2891 loader_format = format;
2893 if (loader_format == "lyx") {
2894 Buffer * buf = lv->loadDocument(lyxfile);
2898 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2902 bool as_paragraphs = loader_format == "textparagraph";
2903 string filename2 = (loader_format == format) ? filename.absFileName()
2904 : support::changeExtension(filename.absFileName(),
2905 theFormats().extension(loader_format));
2906 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2908 guiApp->setCurrentView(lv);
2909 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2916 void GuiView::importDocument(string const & argument)
2919 string filename = split(argument, format, ' ');
2921 LYXERR(Debug::INFO, format << " file: " << filename);
2923 // need user interaction
2924 if (filename.empty()) {
2925 string initpath = lyxrc.document_path;
2926 if (documentBufferView()) {
2927 string const trypath = documentBufferView()->buffer().filePath();
2928 // If directory is writeable, use this as default.
2929 if (FileName(trypath).isDirWritable())
2933 docstring const text = bformat(_("Select %1$s file to import"),
2934 translateIfPossible(theFormats().prettyName(format)));
2936 FileDialog dlg(toqstr(text));
2937 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2938 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2940 docstring filter = translateIfPossible(theFormats().prettyName(format));
2943 filter += from_utf8(theFormats().extensions(format));
2946 FileDialog::Result result =
2947 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2949 if (result.first == FileDialog::Later)
2952 filename = fromqstr(result.second);
2954 // check selected filename
2955 if (filename.empty())
2956 message(_("Canceled."));
2959 if (filename.empty())
2962 // get absolute path of file
2963 FileName const fullname(support::makeAbsPath(filename));
2965 // Can happen if the user entered a path into the dialog
2967 if (fullname.onlyFileName().empty()) {
2968 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2969 "Aborting import."),
2970 from_utf8(fullname.absFileName()));
2971 frontend::Alert::error(_("File name error"), msg);
2972 message(_("Canceled."));
2977 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2979 // Check if the document already is open
2980 Buffer * buf = theBufferList().getBuffer(lyxfile);
2983 if (!closeBuffer()) {
2984 message(_("Canceled."));
2989 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2991 // if the file exists already, and we didn't do
2992 // -i lyx thefile.lyx, warn
2993 if (lyxfile.exists() && fullname != lyxfile) {
2995 docstring text = bformat(_("The document %1$s already exists.\n\n"
2996 "Do you want to overwrite that document?"), displaypath);
2997 int const ret = Alert::prompt(_("Overwrite document?"),
2998 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3001 message(_("Canceled."));
3006 message(bformat(_("Importing %1$s..."), displaypath));
3007 ErrorList errorList;
3008 if (import(this, fullname, format, errorList))
3009 message(_("imported."));
3011 message(_("file not imported!"));
3013 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3017 void GuiView::newDocument(string const & filename, string templatefile,
3020 FileName initpath(lyxrc.document_path);
3021 if (documentBufferView()) {
3022 FileName const trypath(documentBufferView()->buffer().filePath());
3023 // If directory is writeable, use this as default.
3024 if (trypath.isDirWritable())
3028 if (from_template) {
3029 if (templatefile.empty())
3030 templatefile = selectTemplateFile().absFileName();
3031 if (templatefile.empty())
3036 if (filename.empty())
3037 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3039 b = newFile(filename, templatefile, true);
3044 // If no new document could be created, it is unsure
3045 // whether there is a valid BufferView.
3046 if (currentBufferView())
3047 // Ensure the cursor is correctly positioned on screen.
3048 currentBufferView()->showCursor();
3052 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3054 BufferView * bv = documentBufferView();
3059 FileName filename(to_utf8(fname));
3060 if (filename.empty()) {
3061 // Launch a file browser
3063 string initpath = lyxrc.document_path;
3064 string const trypath = bv->buffer().filePath();
3065 // If directory is writeable, use this as default.
3066 if (FileName(trypath).isDirWritable())
3070 FileDialog dlg(qt_("Select LyX document to insert"));
3071 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3072 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3074 FileDialog::Result result = dlg.open(toqstr(initpath),
3075 QStringList(qt_("LyX Documents (*.lyx)")));
3077 if (result.first == FileDialog::Later)
3081 filename.set(fromqstr(result.second));
3083 // check selected filename
3084 if (filename.empty()) {
3085 // emit message signal.
3086 message(_("Canceled."));
3091 bv->insertLyXFile(filename, ignorelang);
3092 bv->buffer().errors("Parse");
3097 string const GuiView::getTemplatesPath(Buffer & b)
3099 // We start off with the user's templates path
3100 string result = addPath(package().user_support().absFileName(), "templates");
3101 // Check for the document language
3102 string const langcode = b.params().language->code();
3103 string const shortcode = langcode.substr(0, 2);
3104 if (!langcode.empty() && shortcode != "en") {
3105 string subpath = addPath(result, shortcode);
3106 string subpath_long = addPath(result, langcode);
3107 // If we have a subdirectory for the language already,
3109 FileName sp = FileName(subpath);
3110 if (sp.isDirectory())
3112 else if (FileName(subpath_long).isDirectory())
3113 result = subpath_long;
3115 // Ask whether we should create such a subdirectory
3116 docstring const text =
3117 bformat(_("It is suggested to save the template in a subdirectory\n"
3118 "appropriate to the document language (%1$s).\n"
3119 "This subdirectory does not exists yet.\n"
3120 "Do you want to create it?"),
3121 _(b.params().language->display()));
3122 if (Alert::prompt(_("Create Language Directory?"),
3123 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3124 // If the user agreed, we try to create it and report if this failed.
3125 if (!sp.createDirectory(0777))
3126 Alert::error(_("Subdirectory creation failed!"),
3127 _("Could not create subdirectory.\n"
3128 "The template will be saved in the parent directory."));
3134 // Do we have a layout category?
3135 string const cat = b.params().baseClass() ?
3136 b.params().baseClass()->category()
3139 string subpath = addPath(result, cat);
3140 // If we have a subdirectory for the category already,
3142 FileName sp = FileName(subpath);
3143 if (sp.isDirectory())
3146 // Ask whether we should create such a subdirectory
3147 docstring const text =
3148 bformat(_("It is suggested to save the template in a subdirectory\n"
3149 "appropriate to the layout category (%1$s).\n"
3150 "This subdirectory does not exists yet.\n"
3151 "Do you want to create it?"),
3153 if (Alert::prompt(_("Create Category Directory?"),
3154 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3155 // If the user agreed, we try to create it and report if this failed.
3156 if (!sp.createDirectory(0777))
3157 Alert::error(_("Subdirectory creation failed!"),
3158 _("Could not create subdirectory.\n"
3159 "The template will be saved in the parent directory."));
3169 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3171 FileName fname = b.fileName();
3172 FileName const oldname = fname;
3173 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3175 if (!newname.empty()) {
3178 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3180 fname = support::makeAbsPath(to_utf8(newname),
3181 oldname.onlyPath().absFileName());
3183 // Switch to this Buffer.
3186 // No argument? Ask user through dialog.
3188 QString const title = as_template ? qt_("Choose a filename to save template as")
3189 : qt_("Choose a filename to save document as");
3190 FileDialog dlg(title);
3191 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3192 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3194 fname.ensureExtension(".lyx");
3196 string const path = as_template ?
3198 : fname.onlyPath().absFileName();
3199 FileDialog::Result result =
3200 dlg.save(toqstr(path),
3201 QStringList(qt_("LyX Documents (*.lyx)")),
3202 toqstr(fname.onlyFileName()));
3204 if (result.first == FileDialog::Later)
3207 fname.set(fromqstr(result.second));
3212 fname.ensureExtension(".lyx");
3215 // fname is now the new Buffer location.
3217 // if there is already a Buffer open with this name, we do not want
3218 // to have another one. (the second test makes sure we're not just
3219 // trying to overwrite ourselves, which is fine.)
3220 if (theBufferList().exists(fname) && fname != oldname
3221 && theBufferList().getBuffer(fname) != &b) {
3222 docstring const text =
3223 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3224 "Please close it before attempting to overwrite it.\n"
3225 "Do you want to choose a new filename?"),
3226 from_utf8(fname.absFileName()));
3227 int const ret = Alert::prompt(_("Chosen File Already Open"),
3228 text, 0, 1, _("&Rename"), _("&Cancel"));
3230 case 0: return renameBuffer(b, docstring(), kind);
3231 case 1: return false;
3236 bool const existsLocal = fname.exists();
3237 bool const existsInVC = LyXVC::fileInVC(fname);
3238 if (existsLocal || existsInVC) {
3239 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3240 if (kind != LV_WRITE_AS && existsInVC) {
3241 // renaming to a name that is already in VC
3243 docstring text = bformat(_("The document %1$s "
3244 "is already registered.\n\n"
3245 "Do you want to choose a new name?"),
3247 docstring const title = (kind == LV_VC_RENAME) ?
3248 _("Rename document?") : _("Copy document?");
3249 docstring const button = (kind == LV_VC_RENAME) ?
3250 _("&Rename") : _("&Copy");
3251 int const ret = Alert::prompt(title, text, 0, 1,
3252 button, _("&Cancel"));
3254 case 0: return renameBuffer(b, docstring(), kind);
3255 case 1: return false;
3260 docstring text = bformat(_("The document %1$s "
3261 "already exists.\n\n"
3262 "Do you want to overwrite that document?"),
3264 int const ret = Alert::prompt(_("Overwrite document?"),
3265 text, 0, 2, _("&Overwrite"),
3266 _("&Rename"), _("&Cancel"));
3269 case 1: return renameBuffer(b, docstring(), kind);
3270 case 2: return false;
3276 case LV_VC_RENAME: {
3277 string msg = b.lyxvc().rename(fname);
3280 message(from_utf8(msg));
3284 string msg = b.lyxvc().copy(fname);
3287 message(from_utf8(msg));
3291 case LV_WRITE_AS_TEMPLATE:
3294 // LyXVC created the file already in case of LV_VC_RENAME or
3295 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3296 // relative paths of included stuff right if we moved e.g. from
3297 // /a/b.lyx to /a/c/b.lyx.
3299 bool const saved = saveBuffer(b, fname);
3306 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3308 FileName fname = b.fileName();
3310 FileDialog dlg(qt_("Choose a filename to export the document as"));
3311 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3314 QString const anyformat = qt_("Guess from extension (*.*)");
3317 vector<Format const *> export_formats;
3318 for (Format const & f : theFormats())
3319 if (f.documentFormat())
3320 export_formats.push_back(&f);
3321 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3322 map<QString, string> fmap;
3325 for (Format const * f : export_formats) {
3326 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3327 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3329 from_ascii(f->extension())));
3330 types << loc_filter;
3331 fmap[loc_filter] = f->name();
3332 if (from_ascii(f->name()) == iformat) {
3333 filter = loc_filter;
3334 ext = f->extension();
3337 string ofname = fname.onlyFileName();
3339 ofname = support::changeExtension(ofname, ext);
3340 FileDialog::Result result =
3341 dlg.save(toqstr(fname.onlyPath().absFileName()),
3345 if (result.first != FileDialog::Chosen)
3349 fname.set(fromqstr(result.second));
3350 if (filter == anyformat)
3351 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3353 fmt_name = fmap[filter];
3354 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3355 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3357 if (fmt_name.empty() || fname.empty())
3360 fname.ensureExtension(theFormats().extension(fmt_name));
3362 // fname is now the new Buffer location.
3363 if (fname.exists()) {
3364 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3365 docstring text = bformat(_("The document %1$s already "
3366 "exists.\n\nDo you want to "
3367 "overwrite that document?"),
3369 int const ret = Alert::prompt(_("Overwrite document?"),
3370 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3373 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3374 case 2: return false;
3378 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3381 return dr.dispatched();
3385 bool GuiView::saveBuffer(Buffer & b)
3387 return saveBuffer(b, FileName());
3391 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3393 if (workArea(b) && workArea(b)->inDialogMode())
3396 if (fn.empty() && b.isUnnamed())
3397 return renameBuffer(b, docstring());
3399 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3401 theSession().lastFiles().add(b.fileName());
3402 theSession().writeFile();
3406 // Switch to this Buffer.
3409 // FIXME: we don't tell the user *WHY* the save failed !!
3410 docstring const file = makeDisplayPath(b.absFileName(), 30);
3411 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3412 "Do you want to rename the document and "
3413 "try again?"), file);
3414 int const ret = Alert::prompt(_("Rename and save?"),
3415 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3418 if (!renameBuffer(b, docstring()))
3427 return saveBuffer(b, fn);
3431 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3433 return closeWorkArea(wa, false);
3437 // We only want to close the buffer if it is not visible in other workareas
3438 // of the same view, nor in other views, and if this is not a child
3439 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3441 Buffer & buf = wa->bufferView().buffer();
3443 bool last_wa = d.countWorkAreasOf(buf) == 1
3444 && !inOtherView(buf) && !buf.parent();
3446 bool close_buffer = last_wa;
3449 if (lyxrc.close_buffer_with_last_view == "yes")
3451 else if (lyxrc.close_buffer_with_last_view == "no")
3452 close_buffer = false;
3455 if (buf.isUnnamed())
3456 file = from_utf8(buf.fileName().onlyFileName());
3458 file = buf.fileName().displayName(30);
3459 docstring const text = bformat(
3460 _("Last view on document %1$s is being closed.\n"
3461 "Would you like to close or hide the document?\n"
3463 "Hidden documents can be displayed back through\n"
3464 "the menu: View->Hidden->...\n"
3466 "To remove this question, set your preference in:\n"
3467 " Tools->Preferences->Look&Feel->UserInterface\n"
3469 int ret = Alert::prompt(_("Close or hide document?"),
3470 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3473 close_buffer = (ret == 0);
3477 return closeWorkArea(wa, close_buffer);
3481 bool GuiView::closeBuffer()
3483 GuiWorkArea * wa = currentMainWorkArea();
3484 // coverity complained about this
3485 // it seems unnecessary, but perhaps is worth the check
3486 LASSERT(wa, return false);
3488 setCurrentWorkArea(wa);
3489 Buffer & buf = wa->bufferView().buffer();
3490 return closeWorkArea(wa, !buf.parent());
3494 void GuiView::writeSession() const {
3495 GuiWorkArea const * active_wa = currentMainWorkArea();
3496 for (int i = 0; i < d.splitter_->count(); ++i) {
3497 TabWorkArea * twa = d.tabWorkArea(i);
3498 for (int j = 0; j < twa->count(); ++j) {
3499 GuiWorkArea * wa = twa->workArea(j);
3500 Buffer & buf = wa->bufferView().buffer();
3501 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3507 bool GuiView::closeBufferAll()
3510 for (auto & buf : theBufferList()) {
3511 if (!saveBufferIfNeeded(*buf, false)) {
3512 // Closing has been cancelled, so abort.
3517 // Close the workareas in all other views
3518 QList<int> const ids = guiApp->viewIds();
3519 for (int i = 0; i != ids.size(); ++i) {
3520 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3524 // Close our own workareas
3525 if (!closeWorkAreaAll())
3532 bool GuiView::closeWorkAreaAll()
3534 setCurrentWorkArea(currentMainWorkArea());
3536 // We might be in a situation that there is still a tabWorkArea, but
3537 // there are no tabs anymore. This can happen when we get here after a
3538 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3539 // many TabWorkArea's have no documents anymore.
3542 // We have to call count() each time, because it can happen that
3543 // more than one splitter will disappear in one iteration (bug 5998).
3544 while (d.splitter_->count() > empty_twa) {
3545 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3547 if (twa->count() == 0)
3550 setCurrentWorkArea(twa->currentWorkArea());
3551 if (!closeTabWorkArea(twa))
3559 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3564 Buffer & buf = wa->bufferView().buffer();
3566 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3567 Alert::warning(_("Close document"),
3568 _("Document could not be closed because it is being processed by LyX."));
3573 return closeBuffer(buf);
3575 if (!inMultiTabs(wa))
3576 if (!saveBufferIfNeeded(buf, true))
3584 bool GuiView::closeBuffer(Buffer & buf)
3586 bool success = true;
3587 for (Buffer * child_buf : buf.getChildren()) {
3588 if (theBufferList().isOthersChild(&buf, child_buf)) {
3589 child_buf->setParent(nullptr);
3593 // FIXME: should we look in other tabworkareas?
3594 // ANSWER: I don't think so. I've tested, and if the child is
3595 // open in some other window, it closes without a problem.
3596 GuiWorkArea * child_wa = workArea(*child_buf);
3599 // If we are in a close_event all children will be closed in some time,
3600 // so no need to do it here. This will ensure that the children end up
3601 // in the session file in the correct order. If we close the master
3602 // buffer, we can close or release the child buffers here too.
3604 success = closeWorkArea(child_wa, true);
3608 // In this case the child buffer is open but hidden.
3609 // Even in this case, children can be dirty (e.g.,
3610 // after a label change in the master, see #11405).
3611 // Therefore, check this
3612 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3613 // If we are in a close_event all children will be closed in some time,
3614 // so no need to do it here. This will ensure that the children end up
3615 // in the session file in the correct order. If we close the master
3616 // buffer, we can close or release the child buffers here too.
3619 // Save dirty buffers also if closing_!
3620 if (saveBufferIfNeeded(*child_buf, false)) {
3621 child_buf->removeAutosaveFile();
3622 theBufferList().release(child_buf);
3624 // Saving of dirty children has been cancelled.
3625 // Cancel the whole process.
3632 // goto bookmark to update bookmark pit.
3633 // FIXME: we should update only the bookmarks related to this buffer!
3634 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3635 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3636 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3637 guiApp->gotoBookmark(i, false, false);
3639 if (saveBufferIfNeeded(buf, false)) {
3640 buf.removeAutosaveFile();
3641 theBufferList().release(&buf);
3645 // open all children again to avoid a crash because of dangling
3646 // pointers (bug 6603)
3652 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3654 while (twa == d.currentTabWorkArea()) {
3655 twa->setCurrentIndex(twa->count() - 1);
3657 GuiWorkArea * wa = twa->currentWorkArea();
3658 Buffer & b = wa->bufferView().buffer();
3660 // We only want to close the buffer if the same buffer is not visible
3661 // in another view, and if this is not a child and if we are closing
3662 // a view (not a tabgroup).
3663 bool const close_buffer =
3664 !inOtherView(b) && !b.parent() && closing_;
3666 if (!closeWorkArea(wa, close_buffer))
3673 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3675 if (buf.isClean() || buf.paragraphs().empty())
3678 // Switch to this Buffer.
3684 if (buf.isUnnamed()) {
3685 file = from_utf8(buf.fileName().onlyFileName());
3688 FileName filename = buf.fileName();
3690 file = filename.displayName(30);
3691 exists = filename.exists();
3694 // Bring this window to top before asking questions.
3699 if (hiding && buf.isUnnamed()) {
3700 docstring const text = bformat(_("The document %1$s has not been "
3701 "saved yet.\n\nDo you want to save "
3702 "the document?"), file);
3703 ret = Alert::prompt(_("Save new document?"),
3704 text, 0, 1, _("&Save"), _("&Cancel"));
3708 docstring const text = exists ?
3709 bformat(_("The document %1$s has unsaved changes."
3710 "\n\nDo you want to save the document or "
3711 "discard the changes?"), file) :
3712 bformat(_("The document %1$s has not been saved yet."
3713 "\n\nDo you want to save the document or "
3714 "discard it entirely?"), file);
3715 docstring const title = exists ?
3716 _("Save changed document?") : _("Save document?");
3717 ret = Alert::prompt(title, text, 0, 2,
3718 _("&Save"), _("&Discard"), _("&Cancel"));
3723 if (!saveBuffer(buf))
3727 // If we crash after this we could have no autosave file
3728 // but I guess this is really improbable (Jug).
3729 // Sometimes improbable things happen:
3730 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3731 // buf.removeAutosaveFile();
3733 // revert all changes
3744 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3746 Buffer & buf = wa->bufferView().buffer();
3748 for (int i = 0; i != d.splitter_->count(); ++i) {
3749 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3750 if (wa_ && wa_ != wa)
3753 return inOtherView(buf);
3757 bool GuiView::inOtherView(Buffer & buf)
3759 QList<int> const ids = guiApp->viewIds();
3761 for (int i = 0; i != ids.size(); ++i) {
3765 if (guiApp->view(ids[i]).workArea(buf))
3772 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3774 if (!documentBufferView())
3777 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3778 Buffer * const curbuf = &documentBufferView()->buffer();
3779 int nwa = twa->count();
3780 for (int i = 0; i < nwa; ++i) {
3781 if (&workArea(i)->bufferView().buffer() == curbuf) {
3783 if (np == NEXTBUFFER)
3784 next_index = (i == nwa - 1 ? 0 : i + 1);
3786 next_index = (i == 0 ? nwa - 1 : i - 1);
3788 twa->moveTab(i, next_index);
3790 setBuffer(&workArea(next_index)->bufferView().buffer());
3798 /// make sure the document is saved
3799 static bool ensureBufferClean(Buffer * buffer)
3801 LASSERT(buffer, return false);
3802 if (buffer->isClean() && !buffer->isUnnamed())
3805 docstring const file = buffer->fileName().displayName(30);
3808 if (!buffer->isUnnamed()) {
3809 text = bformat(_("The document %1$s has unsaved "
3810 "changes.\n\nDo you want to save "
3811 "the document?"), file);
3812 title = _("Save changed document?");
3815 text = bformat(_("The document %1$s has not been "
3816 "saved yet.\n\nDo you want to save "
3817 "the document?"), file);
3818 title = _("Save new document?");
3820 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3823 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3825 return buffer->isClean() && !buffer->isUnnamed();
3829 bool GuiView::reloadBuffer(Buffer & buf)
3831 currentBufferView()->cursor().reset();
3832 Buffer::ReadStatus status = buf.reload();
3833 return status == Buffer::ReadSuccess;
3837 void GuiView::checkExternallyModifiedBuffers()
3839 for (Buffer * buf : theBufferList()) {
3840 if (buf->fileName().exists() && buf->isChecksumModified()) {
3841 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3842 " Reload now? Any local changes will be lost."),
3843 from_utf8(buf->absFileName()));
3844 int const ret = Alert::prompt(_("Reload externally changed document?"),
3845 text, 0, 1, _("&Reload"), _("&Cancel"));
3853 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3855 Buffer * buffer = documentBufferView()
3856 ? &(documentBufferView()->buffer()) : nullptr;
3858 switch (cmd.action()) {
3859 case LFUN_VC_REGISTER:
3860 if (!buffer || !ensureBufferClean(buffer))
3862 if (!buffer->lyxvc().inUse()) {
3863 if (buffer->lyxvc().registrer()) {
3864 reloadBuffer(*buffer);
3865 dr.clearMessageUpdate();
3870 case LFUN_VC_RENAME:
3871 case LFUN_VC_COPY: {
3872 if (!buffer || !ensureBufferClean(buffer))
3874 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3875 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3876 // Some changes are not yet committed.
3877 // We test here and not in getStatus(), since
3878 // this test is expensive.
3880 LyXVC::CommandResult ret =
3881 buffer->lyxvc().checkIn(log);
3883 if (ret == LyXVC::ErrorCommand ||
3884 ret == LyXVC::VCSuccess)
3885 reloadBuffer(*buffer);
3886 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3887 frontend::Alert::error(
3888 _("Revision control error."),
3889 _("Document could not be checked in."));
3893 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3894 LV_VC_RENAME : LV_VC_COPY;
3895 renameBuffer(*buffer, cmd.argument(), kind);
3900 case LFUN_VC_CHECK_IN:
3901 if (!buffer || !ensureBufferClean(buffer))
3903 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3905 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3907 // Only skip reloading if the checkin was cancelled or
3908 // an error occurred before the real checkin VCS command
3909 // was executed, since the VCS might have changed the
3910 // file even if it could not checkin successfully.
3911 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3912 reloadBuffer(*buffer);
3916 case LFUN_VC_CHECK_OUT:
3917 if (!buffer || !ensureBufferClean(buffer))
3919 if (buffer->lyxvc().inUse()) {
3920 dr.setMessage(buffer->lyxvc().checkOut());
3921 reloadBuffer(*buffer);
3925 case LFUN_VC_LOCKING_TOGGLE:
3926 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3928 if (buffer->lyxvc().inUse()) {
3929 string res = buffer->lyxvc().lockingToggle();
3931 frontend::Alert::error(_("Revision control error."),
3932 _("Error when setting the locking property."));
3935 reloadBuffer(*buffer);
3940 case LFUN_VC_REVERT:
3943 if (buffer->lyxvc().revert()) {
3944 reloadBuffer(*buffer);
3945 dr.clearMessageUpdate();
3949 case LFUN_VC_UNDO_LAST:
3952 buffer->lyxvc().undoLast();
3953 reloadBuffer(*buffer);
3954 dr.clearMessageUpdate();
3957 case LFUN_VC_REPO_UPDATE:
3960 if (ensureBufferClean(buffer)) {
3961 dr.setMessage(buffer->lyxvc().repoUpdate());
3962 checkExternallyModifiedBuffers();
3966 case LFUN_VC_COMMAND: {
3967 string flag = cmd.getArg(0);
3968 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3971 if (contains(flag, 'M')) {
3972 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3975 string path = cmd.getArg(1);
3976 if (contains(path, "$$p") && buffer)
3977 path = subst(path, "$$p", buffer->filePath());
3978 LYXERR(Debug::LYXVC, "Directory: " << path);
3980 if (!pp.isReadableDirectory()) {
3981 lyxerr << _("Directory is not accessible.") << endl;
3984 support::PathChanger p(pp);
3986 string command = cmd.getArg(2);
3987 if (command.empty())
3990 command = subst(command, "$$i", buffer->absFileName());
3991 command = subst(command, "$$p", buffer->filePath());
3993 command = subst(command, "$$m", to_utf8(message));
3994 LYXERR(Debug::LYXVC, "Command: " << command);
3996 one.startscript(Systemcall::Wait, command);
4000 if (contains(flag, 'I'))
4001 buffer->markDirty();
4002 if (contains(flag, 'R'))
4003 reloadBuffer(*buffer);
4008 case LFUN_VC_COMPARE: {
4009 if (cmd.argument().empty()) {
4010 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4016 string rev1 = cmd.getArg(0);
4020 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4023 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4024 f2 = buffer->absFileName();
4026 string rev2 = cmd.getArg(1);
4030 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4034 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4035 f1 << "\n" << f2 << "\n" );
4036 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4037 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4047 void GuiView::openChildDocument(string const & fname)
4049 LASSERT(documentBufferView(), return);
4050 Buffer & buffer = documentBufferView()->buffer();
4051 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4052 documentBufferView()->saveBookmark(false);
4053 Buffer * child = nullptr;
4054 if (theBufferList().exists(filename)) {
4055 child = theBufferList().getBuffer(filename);
4058 message(bformat(_("Opening child document %1$s..."),
4059 makeDisplayPath(filename.absFileName())));
4060 child = loadDocument(filename, false);
4062 // Set the parent name of the child document.
4063 // This makes insertion of citations and references in the child work,
4064 // when the target is in the parent or another child document.
4066 child->setParent(&buffer);
4070 bool GuiView::goToFileRow(string const & argument)
4074 size_t i = argument.find_last_of(' ');
4075 if (i != string::npos) {
4076 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4077 istringstream is(argument.substr(i + 1));
4082 if (i == string::npos) {
4083 LYXERR0("Wrong argument: " << argument);
4086 Buffer * buf = nullptr;
4087 string const realtmp = package().temp_dir().realPath();
4088 // We have to use os::path_prefix_is() here, instead of
4089 // simply prefixIs(), because the file name comes from
4090 // an external application and may need case adjustment.
4091 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4092 buf = theBufferList().getBufferFromTmp(file_name, true);
4093 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4094 << (buf ? " success" : " failed"));
4096 // Must replace extension of the file to be .lyx
4097 // and get full path
4098 FileName const s = fileSearch(string(),
4099 support::changeExtension(file_name, ".lyx"), "lyx");
4100 // Either change buffer or load the file
4101 if (theBufferList().exists(s))
4102 buf = theBufferList().getBuffer(s);
4103 else if (s.exists()) {
4104 buf = loadDocument(s);
4109 _("File does not exist: %1$s"),
4110 makeDisplayPath(file_name)));
4116 _("No buffer for file: %1$s."),
4117 makeDisplayPath(file_name))
4122 bool success = documentBufferView()->setCursorFromRow(row);
4124 LYXERR(Debug::OUTFILE,
4125 "setCursorFromRow: invalid position for row " << row);
4126 frontend::Alert::error(_("Inverse Search Failed"),
4127 _("Invalid position requested by inverse search.\n"
4128 "You may need to update the viewed document."));
4134 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4136 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4137 menu->exec(QCursor::pos());
4142 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4143 Buffer const * orig, Buffer * clone, string const & format)
4145 Buffer::ExportStatus const status = func(format);
4147 // the cloning operation will have produced a clone of the entire set of
4148 // documents, starting from the master. so we must delete those.
4149 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4151 busyBuffers.remove(orig);
4156 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4157 Buffer const * orig, Buffer * clone, string const & format)
4159 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4161 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4165 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4166 Buffer const * orig, Buffer * clone, string const & format)
4168 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4170 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4174 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4175 Buffer const * orig, Buffer * clone, string const & format)
4177 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4179 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4183 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4184 Buffer const * used_buffer,
4185 docstring const & msg,
4186 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4187 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4188 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4189 bool allow_async, bool use_tmpdir)
4194 string format = argument;
4196 format = used_buffer->params().getDefaultOutputFormat();
4197 processing_format = format;
4199 progress_->clearMessages();
4202 #if EXPORT_in_THREAD
4204 GuiViewPrivate::busyBuffers.insert(used_buffer);
4205 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4206 if (!cloned_buffer) {
4207 Alert::error(_("Export Error"),
4208 _("Error cloning the Buffer."));
4211 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4216 setPreviewFuture(f);
4217 last_export_format = used_buffer->params().bufferFormat();
4220 // We are asynchronous, so we don't know here anything about the success
4223 Buffer::ExportStatus status;
4225 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4226 } else if (previewFunc) {
4227 status = (used_buffer->*previewFunc)(format);
4230 handleExportStatus(gv_, status, format);
4232 return (status == Buffer::ExportSuccess
4233 || status == Buffer::PreviewSuccess);
4237 Buffer::ExportStatus status;
4239 status = (used_buffer->*syncFunc)(format, true);
4240 } else if (previewFunc) {
4241 status = (used_buffer->*previewFunc)(format);
4244 handleExportStatus(gv_, status, format);
4246 return (status == Buffer::ExportSuccess
4247 || status == Buffer::PreviewSuccess);
4251 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4253 BufferView * bv = currentBufferView();
4254 LASSERT(bv, return);
4256 // Let the current BufferView dispatch its own actions.
4257 bv->dispatch(cmd, dr);
4258 if (dr.dispatched()) {
4259 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4260 updateDialog("document", "");
4264 // Try with the document BufferView dispatch if any.
4265 BufferView * doc_bv = documentBufferView();
4266 if (doc_bv && doc_bv != bv) {
4267 doc_bv->dispatch(cmd, dr);
4268 if (dr.dispatched()) {
4269 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4270 updateDialog("document", "");
4275 // Then let the current Cursor dispatch its own actions.
4276 bv->cursor().dispatch(cmd);
4278 // update completion. We do it here and not in
4279 // processKeySym to avoid another redraw just for a
4280 // changed inline completion
4281 if (cmd.origin() == FuncRequest::KEYBOARD) {
4282 if (cmd.action() == LFUN_SELF_INSERT
4283 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4284 updateCompletion(bv->cursor(), true, true);
4285 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4286 updateCompletion(bv->cursor(), false, true);
4288 updateCompletion(bv->cursor(), false, false);
4291 dr = bv->cursor().result();
4295 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4297 BufferView * bv = currentBufferView();
4298 // By default we won't need any update.
4299 dr.screenUpdate(Update::None);
4300 // assume cmd will be dispatched
4301 dr.dispatched(true);
4303 Buffer * doc_buffer = documentBufferView()
4304 ? &(documentBufferView()->buffer()) : nullptr;
4306 if (cmd.origin() == FuncRequest::TOC) {
4307 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4308 toc->doDispatch(bv->cursor(), cmd, dr);
4312 string const argument = to_utf8(cmd.argument());
4314 switch(cmd.action()) {
4315 case LFUN_BUFFER_CHILD_OPEN:
4316 openChildDocument(to_utf8(cmd.argument()));
4319 case LFUN_BUFFER_IMPORT:
4320 importDocument(to_utf8(cmd.argument()));
4323 case LFUN_MASTER_BUFFER_EXPORT:
4325 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4327 case LFUN_BUFFER_EXPORT: {
4330 // GCC only sees strfwd.h when building merged
4331 if (::lyx::operator==(cmd.argument(), "custom")) {
4332 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4333 // so the following test should not be needed.
4334 // In principle, we could try to switch to such a view...
4335 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4336 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4340 string const dest = cmd.getArg(1);
4341 FileName target_dir;
4342 if (!dest.empty() && FileName::isAbsolute(dest))
4343 target_dir = FileName(support::onlyPath(dest));
4345 target_dir = doc_buffer->fileName().onlyPath();
4347 string const format = (argument.empty() || argument == "default") ?
4348 doc_buffer->params().getDefaultOutputFormat() : argument;
4350 if ((dest.empty() && doc_buffer->isUnnamed())
4351 || !target_dir.isDirWritable()) {
4352 exportBufferAs(*doc_buffer, from_utf8(format));
4355 /* TODO/Review: Is it a problem to also export the children?
4356 See the update_unincluded flag */
4357 d.asyncBufferProcessing(format,
4360 &GuiViewPrivate::exportAndDestroy,
4362 nullptr, cmd.allowAsync());
4363 // TODO Inform user about success
4367 case LFUN_BUFFER_EXPORT_AS: {
4368 LASSERT(doc_buffer, break);
4369 docstring f = cmd.argument();
4371 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4372 exportBufferAs(*doc_buffer, f);
4376 case LFUN_BUFFER_UPDATE: {
4377 d.asyncBufferProcessing(argument,
4380 &GuiViewPrivate::compileAndDestroy,
4382 nullptr, cmd.allowAsync(), true);
4385 case LFUN_BUFFER_VIEW: {
4386 d.asyncBufferProcessing(argument,
4388 _("Previewing ..."),
4389 &GuiViewPrivate::previewAndDestroy,
4391 &Buffer::preview, cmd.allowAsync());
4394 case LFUN_MASTER_BUFFER_UPDATE: {
4395 d.asyncBufferProcessing(argument,
4396 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4398 &GuiViewPrivate::compileAndDestroy,
4400 nullptr, cmd.allowAsync(), true);
4403 case LFUN_MASTER_BUFFER_VIEW: {
4404 d.asyncBufferProcessing(argument,
4405 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4407 &GuiViewPrivate::previewAndDestroy,
4408 nullptr, &Buffer::preview, cmd.allowAsync());
4411 case LFUN_EXPORT_CANCEL: {
4412 Systemcall::killscript();
4415 case LFUN_BUFFER_SWITCH: {
4416 string const file_name = to_utf8(cmd.argument());
4417 if (!FileName::isAbsolute(file_name)) {
4419 dr.setMessage(_("Absolute filename expected."));
4423 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4426 dr.setMessage(_("Document not loaded"));
4430 // Do we open or switch to the buffer in this view ?
4431 if (workArea(*buffer)
4432 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4437 // Look for the buffer in other views
4438 QList<int> const ids = guiApp->viewIds();
4440 for (; i != ids.size(); ++i) {
4441 GuiView & gv = guiApp->view(ids[i]);
4442 if (gv.workArea(*buffer)) {
4444 gv.activateWindow();
4446 gv.setBuffer(buffer);
4451 // If necessary, open a new window as a last resort
4452 if (i == ids.size()) {
4453 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4459 case LFUN_BUFFER_NEXT:
4460 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4463 case LFUN_BUFFER_MOVE_NEXT:
4464 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4467 case LFUN_BUFFER_PREVIOUS:
4468 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4471 case LFUN_BUFFER_MOVE_PREVIOUS:
4472 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4475 case LFUN_BUFFER_CHKTEX:
4476 LASSERT(doc_buffer, break);
4477 doc_buffer->runChktex();
4480 case LFUN_COMMAND_EXECUTE: {
4481 command_execute_ = true;
4482 minibuffer_focus_ = true;
4485 case LFUN_DROP_LAYOUTS_CHOICE:
4486 d.layout_->showPopup();
4489 case LFUN_MENU_OPEN:
4490 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4491 menu->exec(QCursor::pos());
4494 case LFUN_FILE_INSERT: {
4495 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4496 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4497 dr.forceBufferUpdate();
4498 dr.screenUpdate(Update::Force);
4503 case LFUN_FILE_INSERT_PLAINTEXT:
4504 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4505 string const fname = to_utf8(cmd.argument());
4506 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4507 dr.setMessage(_("Absolute filename expected."));
4511 FileName filename(fname);
4512 if (fname.empty()) {
4513 FileDialog dlg(qt_("Select file to insert"));
4515 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4516 QStringList(qt_("All Files (*)")));
4518 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4519 dr.setMessage(_("Canceled."));
4523 filename.set(fromqstr(result.second));
4527 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4528 bv->dispatch(new_cmd, dr);
4533 case LFUN_BUFFER_RELOAD: {
4534 LASSERT(doc_buffer, break);
4537 bool drop = (cmd.argument() == "dump");
4540 if (!drop && !doc_buffer->isClean()) {
4541 docstring const file =
4542 makeDisplayPath(doc_buffer->absFileName(), 20);
4543 if (doc_buffer->notifiesExternalModification()) {
4544 docstring text = _("The current version will be lost. "
4545 "Are you sure you want to load the version on disk "
4546 "of the document %1$s?");
4547 ret = Alert::prompt(_("Reload saved document?"),
4548 bformat(text, file), 1, 1,
4549 _("&Reload"), _("&Cancel"));
4551 docstring text = _("Any changes will be lost. "
4552 "Are you sure you want to revert to the saved version "
4553 "of the document %1$s?");
4554 ret = Alert::prompt(_("Revert to saved document?"),
4555 bformat(text, file), 1, 1,
4556 _("&Revert"), _("&Cancel"));
4561 doc_buffer->markClean();
4562 reloadBuffer(*doc_buffer);
4563 dr.forceBufferUpdate();
4568 case LFUN_BUFFER_RESET_EXPORT:
4569 LASSERT(doc_buffer, break);
4570 doc_buffer->requireFreshStart(true);
4571 dr.setMessage(_("Buffer export reset."));
4574 case LFUN_BUFFER_WRITE:
4575 LASSERT(doc_buffer, break);
4576 saveBuffer(*doc_buffer);
4579 case LFUN_BUFFER_WRITE_AS:
4580 LASSERT(doc_buffer, break);
4581 renameBuffer(*doc_buffer, cmd.argument());
4584 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4585 LASSERT(doc_buffer, break);
4586 renameBuffer(*doc_buffer, cmd.argument(),
4587 LV_WRITE_AS_TEMPLATE);
4590 case LFUN_BUFFER_WRITE_ALL: {
4591 Buffer * first = theBufferList().first();
4594 message(_("Saving all documents..."));
4595 // We cannot use a for loop as the buffer list cycles.
4598 if (!b->isClean()) {
4600 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4602 b = theBufferList().next(b);
4603 } while (b != first);
4604 dr.setMessage(_("All documents saved."));
4608 case LFUN_MASTER_BUFFER_FORALL: {
4612 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4613 funcToRun.allowAsync(false);
4615 for (Buffer const * buf : doc_buffer->allRelatives()) {
4616 // Switch to other buffer view and resend cmd
4617 lyx::dispatch(FuncRequest(
4618 LFUN_BUFFER_SWITCH, buf->absFileName()));
4619 lyx::dispatch(funcToRun);
4622 lyx::dispatch(FuncRequest(
4623 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4627 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4628 LASSERT(doc_buffer, break);
4629 doc_buffer->clearExternalModification();
4632 case LFUN_BUFFER_CLOSE:
4636 case LFUN_BUFFER_CLOSE_ALL:
4640 case LFUN_DEVEL_MODE_TOGGLE:
4641 devel_mode_ = !devel_mode_;
4643 dr.setMessage(_("Developer mode is now enabled."));
4645 dr.setMessage(_("Developer mode is now disabled."));
4648 case LFUN_TOOLBAR_SET: {
4649 string const name = cmd.getArg(0);
4650 string const state = cmd.getArg(1);
4651 if (GuiToolbar * t = toolbar(name))
4656 case LFUN_TOOLBAR_TOGGLE: {
4657 string const name = cmd.getArg(0);
4658 if (GuiToolbar * t = toolbar(name))
4663 case LFUN_TOOLBAR_MOVABLE: {
4664 string const name = cmd.getArg(0);
4666 // toggle (all) toolbars movablility
4667 toolbarsMovable_ = !toolbarsMovable_;
4668 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4669 GuiToolbar * tb = toolbar(ti.name);
4670 if (tb && tb->isMovable() != toolbarsMovable_)
4671 // toggle toolbar movablity if it does not fit lock
4672 // (all) toolbars positions state silent = true, since
4673 // status bar notifications are slow
4676 if (toolbarsMovable_)
4677 dr.setMessage(_("Toolbars unlocked."));
4679 dr.setMessage(_("Toolbars locked."));
4680 } else if (GuiToolbar * tb = toolbar(name))
4681 // toggle current toolbar movablity
4683 // update lock (all) toolbars positions
4684 updateLockToolbars();
4688 case LFUN_ICON_SIZE: {
4689 QSize size = d.iconSize(cmd.argument());
4691 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4692 size.width(), size.height()));
4696 case LFUN_DIALOG_UPDATE: {
4697 string const name = to_utf8(cmd.argument());
4698 if (name == "prefs" || name == "document")
4699 updateDialog(name, string());
4700 else if (name == "paragraph")
4701 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4702 else if (currentBufferView()) {
4703 Inset * inset = currentBufferView()->editedInset(name);
4704 // Can only update a dialog connected to an existing inset
4706 // FIXME: get rid of this indirection; GuiView ask the inset
4707 // if he is kind enough to update itself...
4708 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4709 //FIXME: pass DispatchResult here?
4710 inset->dispatch(currentBufferView()->cursor(), fr);
4716 case LFUN_DIALOG_TOGGLE: {
4717 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4718 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4719 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4723 case LFUN_DIALOG_DISCONNECT_INSET:
4724 disconnectDialog(to_utf8(cmd.argument()));
4727 case LFUN_DIALOG_HIDE: {
4728 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4732 case LFUN_DIALOG_SHOW: {
4733 string const name = cmd.getArg(0);
4734 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4736 if (name == "latexlog") {
4737 // getStatus checks that
4738 LASSERT(doc_buffer, break);
4739 Buffer::LogType type;
4740 string const logfile = doc_buffer->logName(&type);
4742 case Buffer::latexlog:
4745 case Buffer::buildlog:
4746 sdata = "literate ";
4749 sdata += Lexer::quoteString(logfile);
4750 showDialog("log", sdata);
4751 } else if (name == "vclog") {
4752 // getStatus checks that
4753 LASSERT(doc_buffer, break);
4754 string const sdata2 = "vc " +
4755 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4756 showDialog("log", sdata2);
4757 } else if (name == "symbols") {
4758 sdata = bv->cursor().getEncoding()->name();
4760 showDialog("symbols", sdata);
4761 } else if (name == "findreplace") {
4762 sdata = to_utf8(bv->cursor().selectionAsString(false));
4763 showDialog(name, sdata);
4765 } else if (name == "prefs" && isFullScreen()) {
4766 lfunUiToggle("fullscreen");
4767 showDialog("prefs", sdata);
4769 showDialog(name, sdata);
4774 dr.setMessage(cmd.argument());
4777 case LFUN_UI_TOGGLE: {
4778 string arg = cmd.getArg(0);
4779 if (!lfunUiToggle(arg)) {
4780 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4781 dr.setMessage(bformat(msg, from_utf8(arg)));
4783 // Make sure the keyboard focus stays in the work area.
4788 case LFUN_VIEW_SPLIT: {
4789 LASSERT(doc_buffer, break);
4790 string const orientation = cmd.getArg(0);
4791 d.splitter_->setOrientation(orientation == "vertical"
4792 ? Qt::Vertical : Qt::Horizontal);
4793 TabWorkArea * twa = addTabWorkArea();
4794 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4795 setCurrentWorkArea(wa);
4798 case LFUN_TAB_GROUP_CLOSE:
4799 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4800 closeTabWorkArea(twa);
4801 d.current_work_area_ = nullptr;
4802 twa = d.currentTabWorkArea();
4803 // Switch to the next GuiWorkArea in the found TabWorkArea.
4805 // Make sure the work area is up to date.
4806 setCurrentWorkArea(twa->currentWorkArea());
4808 setCurrentWorkArea(nullptr);
4813 case LFUN_VIEW_CLOSE:
4814 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4815 closeWorkArea(twa->currentWorkArea());
4816 d.current_work_area_ = nullptr;
4817 twa = d.currentTabWorkArea();
4818 // Switch to the next GuiWorkArea in the found TabWorkArea.
4820 // Make sure the work area is up to date.
4821 setCurrentWorkArea(twa->currentWorkArea());
4823 setCurrentWorkArea(nullptr);
4828 case LFUN_COMPLETION_INLINE:
4829 if (d.current_work_area_)
4830 d.current_work_area_->completer().showInline();
4833 case LFUN_COMPLETION_POPUP:
4834 if (d.current_work_area_)
4835 d.current_work_area_->completer().showPopup();
4840 if (d.current_work_area_)
4841 d.current_work_area_->completer().tab();
4844 case LFUN_COMPLETION_CANCEL:
4845 if (d.current_work_area_) {
4846 if (d.current_work_area_->completer().popupVisible())
4847 d.current_work_area_->completer().hidePopup();
4849 d.current_work_area_->completer().hideInline();
4853 case LFUN_COMPLETION_ACCEPT:
4854 if (d.current_work_area_)
4855 d.current_work_area_->completer().activate();
4858 case LFUN_BUFFER_ZOOM_IN:
4859 case LFUN_BUFFER_ZOOM_OUT:
4860 case LFUN_BUFFER_ZOOM: {
4861 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4863 // Actual zoom value: default zoom + fractional extra value
4864 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4865 zoom = min(max(zoom, zoom_min_), zoom_max_);
4867 setCurrentZoom(zoom);
4869 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4870 lyxrc.currentZoom, lyxrc.defaultZoom));
4872 guiApp->fontLoader().update();
4873 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4877 case LFUN_VC_REGISTER:
4878 case LFUN_VC_RENAME:
4880 case LFUN_VC_CHECK_IN:
4881 case LFUN_VC_CHECK_OUT:
4882 case LFUN_VC_REPO_UPDATE:
4883 case LFUN_VC_LOCKING_TOGGLE:
4884 case LFUN_VC_REVERT:
4885 case LFUN_VC_UNDO_LAST:
4886 case LFUN_VC_COMMAND:
4887 case LFUN_VC_COMPARE:
4888 dispatchVC(cmd, dr);
4891 case LFUN_SERVER_GOTO_FILE_ROW:
4892 if(goToFileRow(to_utf8(cmd.argument())))
4893 dr.screenUpdate(Update::Force | Update::FitCursor);
4896 case LFUN_LYX_ACTIVATE:
4900 case LFUN_WINDOW_RAISE:
4906 case LFUN_FORWARD_SEARCH: {
4907 // it seems safe to assume we have a document buffer, since
4908 // getStatus wants one.
4909 LASSERT(doc_buffer, break);
4910 Buffer const * doc_master = doc_buffer->masterBuffer();
4911 FileName const path(doc_master->temppath());
4912 string const texname = doc_master->isChild(doc_buffer)
4913 ? DocFileName(changeExtension(
4914 doc_buffer->absFileName(),
4915 "tex")).mangledFileName()
4916 : doc_buffer->latexName();
4917 string const fulltexname =
4918 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4919 string const mastername =
4920 removeExtension(doc_master->latexName());
4921 FileName const dviname(addName(path.absFileName(),
4922 addExtension(mastername, "dvi")));
4923 FileName const pdfname(addName(path.absFileName(),
4924 addExtension(mastername, "pdf")));
4925 bool const have_dvi = dviname.exists();
4926 bool const have_pdf = pdfname.exists();
4927 if (!have_dvi && !have_pdf) {
4928 dr.setMessage(_("Please, preview the document first."));
4931 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4932 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4933 string outname = dviname.onlyFileName();
4934 string command = lyxrc.forward_search_dvi;
4935 if ((!goto_dvi || goto_pdf) &&
4936 pdfname.lastModified() > dviname.lastModified()) {
4937 outname = pdfname.onlyFileName();
4938 command = lyxrc.forward_search_pdf;
4941 DocIterator cur = bv->cursor();
4942 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4943 LYXERR(Debug::ACTION, "Forward search: row:" << row
4945 if (row == -1 || command.empty()) {
4946 dr.setMessage(_("Couldn't proceed."));
4949 string texrow = convert<string>(row);
4951 command = subst(command, "$$n", texrow);
4952 command = subst(command, "$$f", fulltexname);
4953 command = subst(command, "$$t", texname);
4954 command = subst(command, "$$o", outname);
4956 volatile PathChanger p(path);
4958 one.startscript(Systemcall::DontWait, command);
4962 case LFUN_SPELLING_CONTINUOUSLY:
4963 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4964 dr.screenUpdate(Update::Force);
4967 case LFUN_CITATION_OPEN: {
4969 if (theFormats().getFormat("pdf"))
4970 pdfv = theFormats().getFormat("pdf")->viewer();
4971 if (theFormats().getFormat("ps"))
4972 psv = theFormats().getFormat("ps")->viewer();
4973 frontend::showTarget(argument, pdfv, psv);
4978 // The LFUN must be for one of BufferView, Buffer or Cursor;
4980 dispatchToBufferView(cmd, dr);
4984 // Need to update bv because many LFUNs here might have destroyed it
4985 bv = currentBufferView();
4987 // Clear non-empty selections
4988 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4990 Cursor & cur = bv->cursor();
4991 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4992 cur.clearSelection();
4998 bool GuiView::lfunUiToggle(string const & ui_component)
5000 if (ui_component == "scrollbar") {
5001 // hide() is of no help
5002 if (d.current_work_area_->verticalScrollBarPolicy() ==
5003 Qt::ScrollBarAlwaysOff)
5005 d.current_work_area_->setVerticalScrollBarPolicy(
5006 Qt::ScrollBarAsNeeded);
5008 d.current_work_area_->setVerticalScrollBarPolicy(
5009 Qt::ScrollBarAlwaysOff);
5010 } else if (ui_component == "statusbar") {
5011 statusBar()->setVisible(!statusBar()->isVisible());
5012 } else if (ui_component == "menubar") {
5013 menuBar()->setVisible(!menuBar()->isVisible());
5014 } else if (ui_component == "zoomlevel") {
5015 zoom_value_->setVisible(!zoom_value_->isVisible());
5016 } else if (ui_component == "zoomslider") {
5017 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5018 zoom_in_->setVisible(zoom_slider_->isVisible());
5019 zoom_out_->setVisible(zoom_slider_->isVisible());
5020 } else if (ui_component == "statistics-w") {
5021 word_count_enabled_ = !word_count_enabled_;
5024 } else if (ui_component == "statistics-cb") {
5025 char_count_enabled_ = !char_count_enabled_;
5028 } else if (ui_component == "statistics-c") {
5029 char_nb_count_enabled_ = !char_nb_count_enabled_;
5032 } else if (ui_component == "frame") {
5033 int const l = contentsMargins().left();
5035 //are the frames in default state?
5036 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5038 #if QT_VERSION > 0x050903
5039 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5041 setContentsMargins(-2, -2, -2, -2);
5043 #if QT_VERSION > 0x050903
5044 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5046 setContentsMargins(0, 0, 0, 0);
5049 if (ui_component == "fullscreen") {
5053 stat_counts_->setVisible(statsEnabled());
5058 void GuiView::toggleFullScreen()
5060 setWindowState(windowState() ^ Qt::WindowFullScreen);
5064 Buffer const * GuiView::updateInset(Inset const * inset)
5069 Buffer const * inset_buffer = &(inset->buffer());
5071 for (int i = 0; i != d.splitter_->count(); ++i) {
5072 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5075 Buffer const * buffer = &(wa->bufferView().buffer());
5076 if (inset_buffer == buffer)
5077 wa->scheduleRedraw(true);
5079 return inset_buffer;
5083 void GuiView::restartCaret()
5085 /* When we move around, or type, it's nice to be able to see
5086 * the caret immediately after the keypress.
5088 if (d.current_work_area_)
5089 d.current_work_area_->startBlinkingCaret();
5091 // Take this occasion to update the other GUI elements.
5097 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5099 if (d.current_work_area_)
5100 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5105 // This list should be kept in sync with the list of insets in
5106 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5107 // dialog should have the same name as the inset.
5108 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5109 // docs in LyXAction.cpp.
5111 char const * const dialognames[] = {
5113 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5114 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5115 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5116 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5117 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5118 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5119 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5120 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5122 char const * const * const end_dialognames =
5123 dialognames + (sizeof(dialognames) / sizeof(char *));
5127 cmpCStr(char const * name) : name_(name) {}
5128 bool operator()(char const * other) {
5129 return strcmp(other, name_) == 0;
5136 bool isValidName(string const & name)
5138 return find_if(dialognames, end_dialognames,
5139 cmpCStr(name.c_str())) != end_dialognames;
5145 void GuiView::resetDialogs()
5147 // Make sure that no LFUN uses any GuiView.
5148 guiApp->setCurrentView(nullptr);
5152 constructToolbars();
5153 guiApp->menus().fillMenuBar(menuBar(), this, false);
5154 d.layout_->updateContents(true);
5155 // Now update controls with current buffer.
5156 guiApp->setCurrentView(this);
5162 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5164 for (QObject * child: widget->children()) {
5165 if (child->inherits("QGroupBox")) {
5166 QGroupBox * box = (QGroupBox*) child;
5169 flatGroupBoxes(child, flag);
5175 Dialog * GuiView::find(string const & name, bool hide_it) const
5177 if (!isValidName(name))
5180 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5182 if (it != d.dialogs_.end()) {
5184 it->second->hideView();
5185 return it->second.get();
5191 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5193 Dialog * dialog = find(name, hide_it);
5194 if (dialog != nullptr)
5197 dialog = build(name);
5198 d.dialogs_[name].reset(dialog);
5199 // Force a uniform style for group boxes
5200 // On Mac non-flat works better, on Linux flat is standard
5201 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5202 if (lyxrc.allow_geometry_session)
5203 dialog->restoreSession();
5210 void GuiView::showDialog(string const & name, string const & sdata,
5213 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5217 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5223 const string name = fromqstr(qname);
5224 const string sdata = fromqstr(qdata);
5228 Dialog * dialog = findOrBuild(name, false);
5230 bool const visible = dialog->isVisibleView();
5231 dialog->showData(sdata);
5232 if (currentBufferView())
5233 currentBufferView()->editInset(name, inset);
5234 // We only set the focus to the new dialog if it was not yet
5235 // visible in order not to change the existing previous behaviour
5237 // activateWindow is needed for floating dockviews
5238 dialog->asQWidget()->raise();
5239 dialog->asQWidget()->activateWindow();
5240 if (dialog->wantInitialFocus())
5241 dialog->asQWidget()->setFocus();
5245 catch (ExceptionMessage const &) {
5253 bool GuiView::isDialogVisible(string const & name) const
5255 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5256 if (it == d.dialogs_.end())
5258 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5262 void GuiView::hideDialog(string const & name, Inset * inset)
5264 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5265 if (it == d.dialogs_.end())
5269 if (!currentBufferView())
5271 if (inset != currentBufferView()->editedInset(name))
5275 Dialog * const dialog = it->second.get();
5276 if (dialog->isVisibleView())
5278 if (currentBufferView())
5279 currentBufferView()->editInset(name, nullptr);
5283 void GuiView::disconnectDialog(string const & name)
5285 if (!isValidName(name))
5287 if (currentBufferView())
5288 currentBufferView()->editInset(name, nullptr);
5292 void GuiView::hideAll() const
5294 for(auto const & dlg_p : d.dialogs_)
5295 dlg_p.second->hideView();
5299 void GuiView::updateDialogs()
5301 for(auto const & dlg_p : d.dialogs_) {
5302 Dialog * dialog = dlg_p.second.get();
5304 if (dialog->needBufferOpen() && !documentBufferView())
5305 hideDialog(fromqstr(dialog->name()), nullptr);
5306 else if (dialog->isVisibleView())
5307 dialog->checkStatus();
5315 Dialog * GuiView::build(string const & name)
5317 return createDialog(*this, name);
5321 SEMenu::SEMenu(QWidget * parent)
5323 QAction * action = addAction(qt_("Disable Shell Escape"));
5324 connect(action, SIGNAL(triggered()),
5325 parent, SLOT(disableShellEscape()));
5329 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5331 if (event->button() == Qt::LeftButton) {
5336 } // namespace frontend
5339 #include "moc_GuiView.cpp"