]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiView.cpp
Fix bug #12061.
[lyx.git] / src / frontends / qt / GuiView.cpp
1 /**
2  * \file GuiView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
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"
28 #include "GuiToc.h"
29 #include "GuiToolbar.h"
30 #include "GuiWorkArea.h"
31 #include "GuiProgress.h"
32 #include "LayoutBox.h"
33 #include "Menus.h"
34 #include "TocModel.h"
35
36 #include "qt_helpers.h"
37 #include "support/filetools.h"
38
39 #include "frontends/alert.h"
40 #include "frontends/KeySymbol.h"
41
42 #include "buffer_funcs.h"
43 #include "Buffer.h"
44 #include "BufferList.h"
45 #include "BufferParams.h"
46 #include "BufferView.h"
47 #include "Compare.h"
48 #include "Converter.h"
49 #include "Cursor.h"
50 #include "CutAndPaste.h"
51 #include "Encoding.h"
52 #include "ErrorList.h"
53 #include "Format.h"
54 #include "FuncStatus.h"
55 #include "FuncRequest.h"
56 #include "KeySymbol.h"
57 #include "Language.h"
58 #include "LayoutFile.h"
59 #include "Lexer.h"
60 #include "LyXAction.h"
61 #include "LyX.h"
62 #include "LyXRC.h"
63 #include "LyXVC.h"
64 #include "Paragraph.h"
65 #include "SpellChecker.h"
66 #include "Session.h"
67 #include "TexRow.h"
68 #include "Text.h"
69 #include "Toolbars.h"
70 #include "version.h"
71
72 #include "graphics/PreviewLoader.h"
73
74 #include "support/convert.h"
75 #include "support/debug.h"
76 #include "support/ExceptionMessage.h"
77 #include "support/FileName.h"
78 #include "support/gettext.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
88
89 #include <QAction>
90 #include <QApplication>
91 #include <QCloseEvent>
92 #include <QDragEnterEvent>
93 #include <QDropEvent>
94 #include <QFuture>
95 #include <QFutureWatcher>
96 #include <QGroupBox>
97 #include <QLabel>
98 #include <QList>
99 #include <QMenu>
100 #include <QMenuBar>
101 #include <QMimeData>
102 #include <QPainter>
103 #include <QPixmap>
104 #include <QPoint>
105 #include <QSettings>
106 #include <QShowEvent>
107 #include <QSlider>
108 #include <QSplitter>
109 #include <QStackedWidget>
110 #include <QStatusBar>
111 #include <QSvgRenderer>
112 #include <QtConcurrentRun>
113 #include <QTimer>
114 #include <QUrl>
115 #include <QWindowStateChangeEvent>
116 #include <QGestureEvent>
117 #include <QPinchGesture>
118
119
120 // sync with GuiAlert.cpp
121 #define EXPORT_in_THREAD 1
122
123
124 #include "support/bind.h"
125
126 #include <sstream>
127
128 #ifdef HAVE_SYS_TIME_H
129 # include <sys/time.h>
130 #endif
131 #ifdef HAVE_UNISTD_H
132 # include <unistd.h>
133 #endif
134
135
136 using namespace std;
137 using namespace lyx::support;
138
139 namespace lyx {
140
141 using support::addExtension;
142 using support::changeExtension;
143 using support::removeExtension;
144
145 namespace frontend {
146
147 namespace {
148
149 class BackgroundWidget : public QWidget
150 {
151 public:
152         BackgroundWidget(int width, int height)
153                 : width_(width), height_(height)
154         {
155                 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
156                 if (!lyxrc.show_banner)
157                         return;
158                 /// The text to be written on top of the pixmap
159                 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
160                 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
161                 /// The text to be written on top of the pixmap
162                 QString const text = lyx_version ?
163                         qt_("version ") + lyx_version : qt_("unknown version");
164                 QString imagedir = "images/";
165                 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
166                 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
167                 if (svgRenderer.isValid()) {
168                         splash_ = QPixmap(splashSize());
169                         QPainter painter(&splash_);
170                         svgRenderer.render(&painter);
171                         splash_.setDevicePixelRatio(pixelRatio());
172                 } else {
173                         splash_ = getPixmap("images/", "banner", "png");
174                 }
175
176                 QPainter pain(&splash_);
177                 pain.setPen(QColor(0, 0, 0));
178                 qreal const fsize = fontSize();
179                 bool ok;
180                 int hfsize = 20;
181                 qreal locscale = htextsize.toFloat(&ok);
182                 if (!ok)
183                         locscale = 1.0;
184                 QPointF const position = textPosition(false);
185                 QPointF const hposition = textPosition(true);
186                 QRectF const hrect(hposition, splashSize());
187                 LYXERR(Debug::GUI,
188                         "widget pixel ratio: " << pixelRatio() <<
189                         " splash pixel ratio: " << splashPixelRatio() <<
190                         " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
191                 QFont font;
192                 // The font used to display the version info
193                 font.setStyleHint(QFont::SansSerif);
194                 font.setWeight(QFont::Bold);
195                 font.setPointSizeF(fsize);
196                 pain.setFont(font);
197                 pain.drawText(position, text);
198                 // The font used to display the version info
199                 font.setStyleHint(QFont::SansSerif);
200                 font.setWeight(QFont::Normal);
201                 font.setPointSizeF(hfsize);
202                 // Check how long the logo gets with the current font
203                 // and adapt if the font is running wider than what
204                 // we assume
205                 GuiFontMetrics fm(font);
206                 // Split the title into lines to measure the longest line
207                 // in the current l7n.
208                 QStringList titlesegs = htext.split('\n');
209                 int wline = 0;
210                 int hline = fm.maxHeight();
211                 for (QString const & seg : titlesegs) {
212                         if (fm.width(seg) > wline)
213                                 wline = fm.width(seg);
214                 }
215                 // The longest line in the reference font (for English)
216                 // is 180. Calculate scale factor from that.
217                 double const wscale = wline > 0 ? (180.0 / wline) : 1;
218                 // Now do the same for the height (necessary for condensed fonts)
219                 double const hscale = (34.0 / hline);
220                 // take the lower of the two scale factors.
221                 double const scale = min(wscale, hscale);
222                 // Now rescale. Also consider l7n's offset factor.
223                 font.setPointSizeF(hfsize * scale * locscale);
224
225                 pain.setFont(font);
226                 pain.drawText(hrect, Qt::AlignLeft, htext);
227                 setFocusPolicy(Qt::StrongFocus);
228         }
229
230         void paintEvent(QPaintEvent *) override
231         {
232                 int const w = width_;
233                 int const h = height_;
234                 int const x = (width() - w) / 2;
235                 int const y = (height() - h) / 2;
236                 LYXERR(Debug::GUI,
237                         "widget pixel ratio: " << pixelRatio() <<
238                         " splash pixel ratio: " << splashPixelRatio() <<
239                         " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
240                 QPainter pain(this);
241                 pain.drawPixmap(x, y, w, h, splash_);
242         }
243
244         void keyPressEvent(QKeyEvent * ev) override
245         {
246                 KeySymbol sym;
247                 setKeySymbol(&sym, ev);
248                 if (sym.isOK()) {
249                         guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
250                         ev->accept();
251                 } else {
252                         ev->ignore();
253                 }
254         }
255
256 private:
257         QPixmap splash_;
258         int const width_;
259         int const height_;
260
261         /// Current ratio between physical pixels and device-independent pixels
262         double pixelRatio() const {
263                 return qt_scale_factor * devicePixelRatio();
264         }
265
266         qreal fontSize() const {
267                 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
268         }
269
270         QPointF textPosition(bool const heading) const {
271                 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
272                                : QPointF(width_/2 - 18, height_/2 + 45);
273         }
274
275         QSize splashSize() const {
276                 return QSize(
277                         static_cast<unsigned int>(width_ * pixelRatio()),
278                         static_cast<unsigned int>(height_ * pixelRatio()));
279         }
280
281         /// Ratio between physical pixels and device-independent pixels of splash image
282         double splashPixelRatio() const {
283                 return splash_.devicePixelRatio();
284         }
285 };
286
287
288 /// Toolbar store providing access to individual toolbars by name.
289 typedef map<string, GuiToolbar *> ToolbarMap;
290
291 typedef shared_ptr<Dialog> DialogPtr;
292
293 } // namespace
294
295
296 class GuiView::GuiViewPrivate
297 {
298         /// noncopyable
299         GuiViewPrivate(GuiViewPrivate const &);
300         void operator=(GuiViewPrivate const &);
301 public:
302         GuiViewPrivate(GuiView * gv)
303                 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
304                 layout_(nullptr), autosave_timeout_(5000),
305                 in_show_(false)
306         {
307                 // hardcode here the platform specific icon size
308                 smallIconSize = 16;  // scaling problems
309                 normalIconSize = 20; // ok, default if iconsize.png is missing
310                 bigIconSize = 26;       // better for some math icons
311                 hugeIconSize = 32;      // better for hires displays
312                 giantIconSize = 48;
313
314                 // if it exists, use width of iconsize.png as normal size
315                 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
316                 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
317                 if (!fn.empty()) {
318                         QImage image(toqstr(fn.absFileName()));
319                         if (image.width() < int(smallIconSize))
320                                 normalIconSize = smallIconSize;
321                         else if (image.width() > int(giantIconSize))
322                                 normalIconSize = giantIconSize;
323                         else
324                                 normalIconSize = image.width();
325                 }
326
327                 splitter_ = new QSplitter;
328                 bg_widget_ = new BackgroundWidget(400, 250);
329                 stack_widget_ = new QStackedWidget;
330                 stack_widget_->addWidget(bg_widget_);
331                 stack_widget_->addWidget(splitter_);
332                 setBackground();
333
334                 // TODO cleanup, remove the singleton, handle multiple Windows?
335                 progress_ = ProgressInterface::instance();
336                 if (!dynamic_cast<GuiProgress*>(progress_)) {
337                         progress_ = new GuiProgress;  // TODO who deletes it
338                         ProgressInterface::setInstance(progress_);
339                 }
340                 QObject::connect(
341                                 dynamic_cast<GuiProgress*>(progress_),
342                                 SIGNAL(updateStatusBarMessage(QString const&)),
343                                 gv, SLOT(updateStatusBarMessage(QString const&)));
344                 QObject::connect(
345                                 dynamic_cast<GuiProgress*>(progress_),
346                                 SIGNAL(clearMessageText()),
347                                 gv, SLOT(clearMessageText()));
348         }
349
350         ~GuiViewPrivate()
351         {
352                 delete splitter_;
353                 delete bg_widget_;
354                 delete stack_widget_;
355         }
356
357         void setBackground()
358         {
359                 stack_widget_->setCurrentWidget(bg_widget_);
360                 bg_widget_->setUpdatesEnabled(true);
361                 bg_widget_->setFocus();
362         }
363
364         int tabWorkAreaCount()
365         {
366                 return splitter_->count();
367         }
368
369         TabWorkArea * tabWorkArea(int i)
370         {
371                 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
372         }
373
374         TabWorkArea * currentTabWorkArea()
375         {
376                 int areas = tabWorkAreaCount();
377                 if (areas == 1)
378                         // The first TabWorkArea is always the first one, if any.
379                         return tabWorkArea(0);
380
381                 for (int i = 0; i != areas;  ++i) {
382                         TabWorkArea * twa = tabWorkArea(i);
383                         if (current_main_work_area_ == twa->currentWorkArea())
384                                 return twa;
385                 }
386
387                 // None has the focus so we just take the first one.
388                 return tabWorkArea(0);
389         }
390
391         int countWorkAreasOf(Buffer & buf)
392         {
393                 int areas = tabWorkAreaCount();
394                 int count = 0;
395                 for (int i = 0; i != areas;  ++i) {
396                         TabWorkArea * twa = tabWorkArea(i);
397                         if (twa->workArea(buf))
398                                 ++count;
399                 }
400                 return count;
401         }
402
403         void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
404         {
405                 if (processing_thread_watcher_.isRunning()) {
406                         // we prefer to cancel this preview in order to keep a snappy
407                         // interface.
408                         return;
409                 }
410                 processing_thread_watcher_.setFuture(f);
411         }
412
413         QSize iconSize(docstring const & icon_size)
414         {
415                 unsigned int size;
416                 if (icon_size == "small")
417                         size = smallIconSize;
418                 else if (icon_size == "normal")
419                         size = normalIconSize;
420                 else if (icon_size == "big")
421                         size = bigIconSize;
422                 else if (icon_size == "huge")
423                         size = hugeIconSize;
424                 else if (icon_size == "giant")
425                         size = giantIconSize;
426                 else
427                         size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
428
429                 if (size < smallIconSize)
430                         size = smallIconSize;
431
432                 return QSize(size, size);
433         }
434
435         QSize iconSize(QString const & icon_size)
436         {
437                 return iconSize(qstring_to_ucs4(icon_size));
438         }
439
440         string & iconSize(QSize const & qsize)
441         {
442                 LATTEST(qsize.width() == qsize.height());
443
444                 static string icon_size;
445
446                 unsigned int size = qsize.width();
447
448                 if (size < smallIconSize)
449                         size = smallIconSize;
450
451                 if (size == smallIconSize)
452                         icon_size = "small";
453                 else if (size == normalIconSize)
454                         icon_size = "normal";
455                 else if (size == bigIconSize)
456                         icon_size = "big";
457                 else if (size == hugeIconSize)
458                         icon_size = "huge";
459                 else if (size == giantIconSize)
460                         icon_size = "giant";
461                 else
462                         icon_size = convert<string>(size);
463
464                 return icon_size;
465         }
466
467         static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
468                         Buffer * buffer, string const & format);
469         static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
470                         Buffer * buffer, string const & format);
471         static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
472                         Buffer * buffer, string const & format);
473         static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
474
475         template<class T>
476         static Buffer::ExportStatus runAndDestroy(const T& func,
477                         Buffer const * orig, Buffer * buffer, string const & format);
478
479         // TODO syncFunc/previewFunc: use bind
480         bool asyncBufferProcessing(string const & argument,
481                                    Buffer const * used_buffer,
482                                    docstring const & msg,
483                                    Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
484                                    Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
485                                    Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
486                                    bool allow_async, bool use_tmpdir = false);
487
488         QVector<GuiWorkArea*> guiWorkAreas();
489
490 public:
491         GuiView * gv_;
492         GuiWorkArea * current_work_area_;
493         GuiWorkArea * current_main_work_area_;
494         QSplitter * splitter_;
495         QStackedWidget * stack_widget_;
496         BackgroundWidget * bg_widget_;
497         /// view's toolbars
498         ToolbarMap toolbars_;
499         ProgressInterface* progress_;
500         /// The main layout box.
501         /**
502          * \warning Don't Delete! The layout box is actually owned by
503          * whichever toolbar contains it. All the GuiView class needs is a
504          * means of accessing it.
505          *
506          * FIXME: replace that with a proper model so that we are not limited
507          * to only one dialog.
508          */
509         LayoutBox * layout_;
510
511         ///
512         map<string, DialogPtr> dialogs_;
513
514         ///
515         QTimer statusbar_timer_;
516         QTimer statusbar_stats_timer_;
517         /// auto-saving of buffers
518         Timeout autosave_timeout_;
519
520         ///
521         TocModels toc_models_;
522
523         ///
524         QFutureWatcher<docstring> autosave_watcher_;
525         QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
526         ///
527         string last_export_format;
528         string processing_format;
529
530         static QSet<Buffer const *> busyBuffers;
531
532         unsigned int smallIconSize;
533         unsigned int normalIconSize;
534         unsigned int bigIconSize;
535         unsigned int hugeIconSize;
536         unsigned int giantIconSize;
537
538         /// flag against a race condition due to multiclicks, see bug #1119
539         bool in_show_;
540
541         // Timers for statistic updates in buffer
542         /// Current time left to the nearest info update
543         int time_to_update = 1000;
544         ///Basic step for timer in ms. Basically reaction time for short selections
545         int const timer_rate = 500;
546         /// Real stats updates infrequently. First they take long time for big buffers, second
547         /// they are visible for fast-repeat keyboards even for mid documents.
548         int const default_stats_rate = 5000;
549         /// Detection of new selection, so we can react fast
550         bool already_in_selection_ = false;
551         /// Maximum size of "short" selection for which we can update with faster timer_rate
552         int const max_sel_chars = 5000;
553
554 };
555
556 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
557
558
559 GuiView::GuiView(int id)
560         : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
561           command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
562           char_count_enabled_(true),  char_nb_count_enabled_(false),
563           toolbarsMovable_(true), devel_mode_(false)
564 {
565         connect(this, SIGNAL(bufferViewChanged()),
566                 this, SLOT(onBufferViewChanged()));
567
568         // GuiToolbars *must* be initialised before the menu bar.
569         setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
570         constructToolbars();
571
572         // set ourself as the current view. This is needed for the menu bar
573         // filling, at least for the static special menu item on Mac. Otherwise
574         // they are greyed out.
575         guiApp->setCurrentView(this);
576
577         // Fill up the menu bar.
578         guiApp->menus().fillMenuBar(menuBar(), this, true);
579
580         setCentralWidget(d.stack_widget_);
581
582         // Start autosave timer
583         if (lyxrc.autosave) {
584                 // The connection is closed when this is destroyed.
585                 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
586                 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
587                 d.autosave_timeout_.start();
588         }
589         connect(&d.statusbar_timer_, SIGNAL(timeout()),
590                 this, SLOT(clearMessage()));
591         connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
592                 this, SLOT(showStats()));
593         d.statusbar_stats_timer_.start(d.timer_rate);
594
595         // We don't want to keep the window in memory if it is closed.
596         setAttribute(Qt::WA_DeleteOnClose, true);
597
598 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
599         // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
600         // since the icon is provided in the application bundle. We use a themed
601         // version when available and use the bundled one as fallback.
602         setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
603
604 #endif
605         resetWindowTitle();
606
607         // use tabbed dock area for multiple docks
608         // (such as "source" and "messages")
609         setDockOptions(QMainWindow::ForceTabbedDocks);
610
611 #ifdef Q_OS_MAC
612         // use document mode tabs on docks
613         setDocumentMode(true);
614 #endif
615
616         // For Drag&Drop.
617         setAcceptDrops(true);
618
619         QFontMetrics const fm(statusBar()->fontMetrics());
620         int const iconheight = max(int(d.normalIconSize), fm.height());
621         QSize const iconsize(iconheight, iconheight);
622
623         // add busy indicator to statusbar
624         search_mode mode = theGuiApp()->imageSearchMode();
625         QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
626         PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
627         statusBar()->addPermanentWidget(busySVG);
628         // make busy indicator square with 5px margins
629         busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
630         busySVG->hide();
631         // Add cancel button
632         QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize);
633         GuiClickableLabel * processStop = new GuiClickableLabel(statusBar());
634         processStop->setPixmap(ps);
635         processStop->setToolTip(qt_("Click here to stop export/output process"));
636         processStop->hide();
637         statusBar()->addPermanentWidget(processStop);
638
639         connect(&d.processing_thread_watcher_, SIGNAL(started()),
640                 busySVG, SLOT(show()));
641         connect(&d.processing_thread_watcher_, SIGNAL(finished()),
642                 busySVG, SLOT(hide()));
643         connect(&d.processing_thread_watcher_, SIGNAL(started()),
644                 processStop, SLOT(show()));
645         connect(&d.processing_thread_watcher_, SIGNAL(finished()),
646                 processStop, SLOT(hide()));
647         connect(processStop, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
648
649         connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
650         connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
651
652         stat_counts_ = new GuiClickableLabel(statusBar());
653         stat_counts_->setAlignment(Qt::AlignCenter);
654         stat_counts_->setFrameStyle(QFrame::StyledPanel);
655         stat_counts_->hide();
656         statusBar()->addPermanentWidget(stat_counts_);
657
658         connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
659
660         zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
661         // Small size slider for macOS to prevent the status bar from enlarging
662         zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
663 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
664         zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
665 #else
666         zoom_slider_->setFixedWidth(fm.width('x') * 15);
667 #endif
668         // Make the defaultZoom center
669         zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
670         // Initialize proper zoom value
671         QSettings settings;
672         zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
673         // Actual zoom value: default zoom + fractional offset
674         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
675         zoom = min(max(zoom, zoom_min_), zoom_max_);
676         zoom_slider_->setValue(zoom);
677         zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
678
679         // Buttons to change zoom stepwise
680 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
681         QSize s(fm.horizontalAdvance('+'), fm.height());
682 #else
683         QSize s(fm.width('+'), fm.height());
684 #endif
685         zoom_in_ = new GuiClickableLabel(statusBar());
686         zoom_in_->setText("+");
687         zoom_in_->setFixedSize(s);
688         zoom_in_->setAlignment(Qt::AlignCenter);
689         zoom_out_ = new GuiClickableLabel(statusBar());
690         zoom_out_->setText(QString(QChar(0x2212)));
691         zoom_out_->setFixedSize(s);
692         zoom_out_->setAlignment(Qt::AlignCenter);
693
694         statusBar()->addPermanentWidget(zoom_out_);
695         zoom_out_->setEnabled(currentBufferView());
696         statusBar()->addPermanentWidget(zoom_slider_);
697         zoom_slider_->setEnabled(currentBufferView());
698         zoom_in_->setEnabled(currentBufferView());
699         statusBar()->addPermanentWidget(zoom_in_);
700
701         connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
702         connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
703         connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
704         connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
705         connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
706
707         // QPalette palette = statusBar()->palette();
708
709         zoom_value_ = new GuiClickableLabel(statusBar());
710         connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
711         // zoom_value_->setPalette(palette);
712         zoom_value_->setForegroundRole(statusBar()->foregroundRole());
713         zoom_value_->setFixedHeight(fm.height());
714 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
715         zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
716 #else
717         zoom_value_->setMinimumWidth(fm.width("444\%"));
718 #endif
719         zoom_value_->setAlignment(Qt::AlignCenter);
720         zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
721         statusBar()->addPermanentWidget(zoom_value_);
722         zoom_value_->setEnabled(currentBufferView());
723
724         statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
725         connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
726                 this, SLOT(showStatusBarContextMenu()));
727
728         // enable pinch to zoom
729         grabGesture(Qt::PinchGesture);
730
731         QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
732         shell_escape_ = new QLabel(statusBar());
733         shell_escape_->setPixmap(shellescape);
734         shell_escape_->setAlignment(Qt::AlignCenter);
735         shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
736         shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
737                                       "external commands for this document. "
738                                       "Right click to change."));
739         SEMenu * menu = new SEMenu(this);
740         connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
741                 menu, SLOT(showMenu(QPoint)));
742         shell_escape_->hide();
743         statusBar()->addPermanentWidget(shell_escape_);
744
745         QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
746         read_only_ = new QLabel(statusBar());
747         read_only_->setPixmap(readonly);
748         read_only_->setAlignment(Qt::AlignCenter);
749         read_only_->hide();
750         statusBar()->addPermanentWidget(read_only_);
751
752         version_control_ = new QLabel(statusBar());
753         version_control_->setAlignment(Qt::AlignCenter);
754         version_control_->setFrameStyle(QFrame::StyledPanel);
755         version_control_->hide();
756         statusBar()->addPermanentWidget(version_control_);
757
758         statusBar()->setSizeGripEnabled(true);
759         updateStatusBar();
760
761         connect(&d.autosave_watcher_, SIGNAL(finished()), this,
762                 SLOT(autoSaveThreadFinished()));
763
764         connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
765                 SLOT(processingThreadStarted()));
766         connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
767                 SLOT(processingThreadFinished()));
768
769         connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
770                 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
771
772         // Forbid too small unresizable window because it can happen
773         // with some window manager under X11.
774         setMinimumSize(300, 200);
775
776         if (lyxrc.allow_geometry_session) {
777                 // Now take care of session management.
778                 if (restoreLayout())
779                         return;
780         }
781
782         // no session handling, default to a sane size.
783         setGeometry(50, 50, 690, 510);
784         initToolbars();
785
786         // clear session data if any.
787         settings.remove("views");
788 }
789
790
791 GuiView::~GuiView()
792 {
793         delete &d;
794 }
795
796
797 void GuiView::disableShellEscape()
798 {
799         BufferView * bv = documentBufferView();
800         if (!bv)
801                 return;
802         theSession().shellescapeFiles().remove(bv->buffer().absFileName());
803         bv->buffer().params().shell_escape = false;
804         bv->processUpdateFlags(Update::Force);
805 }
806
807
808 void GuiView::checkCancelBackground()
809 {
810         docstring const ttl = _("Cancel Export?");
811         docstring const msg = _("Do you want to cancel the background export process?");
812         int const ret =
813                 Alert::prompt(ttl, msg, 1, 1,
814                         _("&Cancel export"), _("Co&ntinue"));
815         if (ret == 0) {
816                 cancelExport();
817         }
818 }
819
820 void GuiView::statsPressed()
821 {
822         DispatchResult dr;
823         dispatch(FuncRequest(LFUN_STATISTICS), dr);
824 }
825
826 void GuiView::zoomSliderMoved(int value)
827 {
828         DispatchResult dr;
829         dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
830         scheduleRedrawWorkAreas();
831         zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
832 }
833
834
835 void GuiView::zoomValueChanged(int value)
836 {
837         if (value != lyxrc.currentZoom)
838                 zoomSliderMoved(value);
839 }
840
841
842 void GuiView::zoomInPressed()
843 {
844         DispatchResult dr;
845         dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
846         scheduleRedrawWorkAreas();
847 }
848
849
850 void GuiView::zoomOutPressed()
851 {
852         DispatchResult dr;
853         dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
854         scheduleRedrawWorkAreas();
855 }
856
857
858 void GuiView::showZoomContextMenu()
859 {
860         QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
861         if (!menu)
862                 return;
863         menu->exec(QCursor::pos());
864 }
865
866
867 void GuiView::showStatusBarContextMenu()
868 {
869         QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
870         if (!menu)
871                 return;
872         menu->exec(QCursor::pos());
873 }
874
875
876 void GuiView::scheduleRedrawWorkAreas()
877 {
878         for (int i = 0; i < d.tabWorkAreaCount(); i++) {
879                 TabWorkArea* ta = d.tabWorkArea(i);
880                 for (int u = 0; u < ta->count(); u++) {
881                         ta->workArea(u)->scheduleRedraw(true);
882                 }
883         }
884 }
885
886
887 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
888 {
889         QVector<GuiWorkArea*> areas;
890         for (int i = 0; i < tabWorkAreaCount(); i++) {
891                 TabWorkArea* ta = tabWorkArea(i);
892                 for (int u = 0; u < ta->count(); u++) {
893                         areas << ta->workArea(u);
894                 }
895         }
896         return areas;
897 }
898
899 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
900         string const & format)
901 {
902         docstring const fmt = translateIfPossible(theFormats().prettyName(format));
903         docstring msg;
904         switch (status) {
905         case Buffer::ExportSuccess:
906                 msg = bformat(_("Successful export to format: %1$s"), fmt);
907                 break;
908         case Buffer::ExportCancel:
909                 msg = _("Document export cancelled.");
910                 break;
911         case Buffer::ExportError:
912         case Buffer::ExportNoPathToFormat:
913         case Buffer::ExportTexPathHasSpaces:
914         case Buffer::ExportConverterError:
915                 msg = bformat(_("Error while exporting format: %1$s"), fmt);
916                 break;
917         case Buffer::PreviewSuccess:
918                 msg = bformat(_("Successful preview of format: %1$s"), fmt);
919                 break;
920         case Buffer::PreviewError:
921                 msg = bformat(_("Error while previewing format: %1$s"), fmt);
922                 break;
923         case Buffer::ExportKilled:
924                 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
925                 break;
926         }
927         view->message(msg);
928 }
929
930
931 void GuiView::processingThreadStarted()
932 {
933 }
934
935
936 void GuiView::processingThreadFinished()
937 {
938         QFutureWatcher<Buffer::ExportStatus> const * watcher =
939                 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
940
941         Buffer::ExportStatus const status = watcher->result();
942         handleExportStatus(this, status, d.processing_format);
943
944         updateToolbars();
945         BufferView const * const bv = currentBufferView();
946         if (bv && !bv->buffer().errorList("Export").empty()) {
947                 errors("Export");
948                 return;
949         }
950
951         bool const error = (status != Buffer::ExportSuccess &&
952                             status != Buffer::PreviewSuccess &&
953                             status != Buffer::ExportCancel);
954         if (error && bv) {
955                 ErrorList & el = bv->buffer().errorList(d.last_export_format);
956                 // at this point, we do not know if buffer-view or
957                 // master-buffer-view was called. If there was an export error,
958                 // and the current buffer's error log is empty, we guess that
959                 // it must be master-buffer-view that was called so we set
960                 // from_master=true.
961                 errors(d.last_export_format, el.empty());
962         }
963 }
964
965
966 void GuiView::autoSaveThreadFinished()
967 {
968         QFutureWatcher<docstring> const * watcher =
969                 static_cast<QFutureWatcher<docstring> const *>(sender());
970         message(watcher->result());
971         updateToolbars();
972 }
973
974
975 void GuiView::saveLayout() const
976 {
977         QSettings settings;
978         settings.setValue("zoom_ratio", zoom_ratio_);
979         settings.setValue("devel_mode", devel_mode_);
980         settings.beginGroup("views");
981         settings.beginGroup(QString::number(id_));
982         if (guiApp->platformName() == "xcb") {
983                 settings.setValue("pos", pos());
984                 settings.setValue("size", size());
985         } else
986                 settings.setValue("geometry", saveGeometry());
987         settings.setValue("layout", saveState(0));
988         settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
989         settings.setValue("zoom_value_visible", zoom_value_->isVisible());
990         settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
991         settings.setValue("word_count_enabled", word_count_enabled_);
992         settings.setValue("char_count_enabled", char_count_enabled_);
993         settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
994 }
995
996
997 void GuiView::saveUISettings() const
998 {
999         QSettings settings;
1000
1001         // Save the toolbar private states
1002         for (auto const & tb_p : d.toolbars_)
1003                 tb_p.second->saveSession(settings);
1004         // Now take care of all other dialogs
1005         for (auto const & dlg_p : d.dialogs_)
1006                 dlg_p.second->saveSession(settings);
1007 }
1008
1009
1010 void GuiView::setCurrentZoom(const int v)
1011 {
1012         lyxrc.currentZoom = v;
1013         zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1014         Q_EMIT currentZoomChanged(v);
1015 }
1016
1017
1018 bool GuiView::restoreLayout()
1019 {
1020         QSettings settings;
1021         zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1022         // Actual zoom value: default zoom + fractional offset
1023         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1024         zoom = min(max(zoom, zoom_min_), zoom_max_);
1025         setCurrentZoom(zoom);
1026         devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1027         settings.beginGroup("views");
1028         settings.beginGroup(QString::number(id_));
1029         QString const icon_key = "icon_size";
1030         if (!settings.contains(icon_key))
1031                 return false;
1032
1033         //code below is skipped when when ~/.config/LyX is (re)created
1034         setIconSize(d.iconSize(settings.value(icon_key).toString()));
1035
1036         zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1037
1038         bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1039         zoom_slider_->setVisible(show_zoom_slider);
1040         zoom_in_->setVisible(show_zoom_slider);
1041         zoom_out_->setVisible(show_zoom_slider);
1042
1043         word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1044         char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1045         char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1046         stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1047
1048         if (guiApp->platformName() == "xcb") {
1049                 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1050                 QSize size = settings.value("size", QSize(690, 510)).toSize();
1051                 resize(size);
1052                 move(pos);
1053         } else {
1054                 // Work-around for bug #6034: the window ends up in an undetermined
1055                 // state when trying to restore a maximized window when it is
1056                 // already maximized.
1057                 if (!(windowState() & Qt::WindowMaximized))
1058                         if (!restoreGeometry(settings.value("geometry").toByteArray()))
1059                                 setGeometry(50, 50, 690, 510);
1060         }
1061
1062         // Make sure layout is correctly oriented.
1063         setLayoutDirection(qApp->layoutDirection());
1064
1065         // Allow the toc and view-source dock widget to be restored if needed.
1066         Dialog * dialog;
1067         if ((dialog = findOrBuild("toc", true)))
1068                 // see bug 5082. At least setup title and enabled state.
1069                 // Visibility will be adjusted by restoreState below.
1070                 dialog->prepareView();
1071         if ((dialog = findOrBuild("view-source", true)))
1072                 dialog->prepareView();
1073         if ((dialog = findOrBuild("progress", true)))
1074                 dialog->prepareView();
1075
1076         if (!restoreState(settings.value("layout").toByteArray(), 0))
1077                 initToolbars();
1078
1079         // init the toolbars that have not been restored
1080         for (auto const & tb_p : guiApp->toolbars()) {
1081                 GuiToolbar * tb = toolbar(tb_p.name);
1082                 if (tb && !tb->isRestored())
1083                         initToolbar(tb_p.name);
1084         }
1085
1086         // update lock (all) toolbars positions
1087         updateLockToolbars();
1088
1089         updateDialogs();
1090         return true;
1091 }
1092
1093
1094 GuiToolbar * GuiView::toolbar(string const & name)
1095 {
1096         ToolbarMap::iterator it = d.toolbars_.find(name);
1097         if (it != d.toolbars_.end())
1098                 return it->second;
1099
1100         LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1101         return nullptr;
1102 }
1103
1104
1105 void GuiView::updateLockToolbars()
1106 {
1107         toolbarsMovable_ = false;
1108         for (ToolbarInfo const & info : guiApp->toolbars()) {
1109                 GuiToolbar * tb = toolbar(info.name);
1110                 if (tb && tb->isMovable())
1111                         toolbarsMovable_ = true;
1112         }
1113 #if QT_VERSION >= 0x050200
1114         // set unified mac toolbars only when not movable as recommended:
1115         // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1116         setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1117 #endif
1118 }
1119
1120
1121 void GuiView::constructToolbars()
1122 {
1123         for (auto const & tb_p : d.toolbars_)
1124                 delete tb_p.second;
1125         d.toolbars_.clear();
1126
1127         // I don't like doing this here, but the standard toolbar
1128         // destroys this object when it's destroyed itself (vfr)
1129         d.layout_ = new LayoutBox(*this);
1130         d.stack_widget_->addWidget(d.layout_);
1131         d.layout_->move(0,0);
1132
1133         // extracts the toolbars from the backend
1134         for (ToolbarInfo const & inf : guiApp->toolbars())
1135                 d.toolbars_[inf.name] =  new GuiToolbar(inf, *this);
1136
1137         DynamicMenuButton::resetIconCache();
1138 }
1139
1140
1141 void GuiView::initToolbars()
1142 {
1143         // extracts the toolbars from the backend
1144         for (ToolbarInfo const & inf : guiApp->toolbars())
1145                 initToolbar(inf.name);
1146 }
1147
1148
1149 void GuiView::initToolbar(string const & name)
1150 {
1151         GuiToolbar * tb = toolbar(name);
1152         if (!tb)
1153                 return;
1154         int const visibility = guiApp->toolbars().defaultVisibility(name);
1155         bool newline = !(visibility & Toolbars::SAMEROW);
1156         tb->setVisible(false);
1157         tb->setVisibility(visibility);
1158
1159         if (visibility & Toolbars::TOP) {
1160                 if (newline)
1161                         addToolBarBreak(Qt::TopToolBarArea);
1162                 addToolBar(Qt::TopToolBarArea, tb);
1163         }
1164
1165         if (visibility & Toolbars::BOTTOM) {
1166                 if (newline)
1167                         addToolBarBreak(Qt::BottomToolBarArea);
1168                 addToolBar(Qt::BottomToolBarArea, tb);
1169         }
1170
1171         if (visibility & Toolbars::LEFT) {
1172                 if (newline)
1173                         addToolBarBreak(Qt::LeftToolBarArea);
1174                 addToolBar(Qt::LeftToolBarArea, tb);
1175         }
1176
1177         if (visibility & Toolbars::RIGHT) {
1178                 if (newline)
1179                         addToolBarBreak(Qt::RightToolBarArea);
1180                 addToolBar(Qt::RightToolBarArea, tb);
1181         }
1182
1183         if (visibility & Toolbars::ON)
1184                 tb->setVisible(true);
1185
1186         tb->setMovable(true);
1187 }
1188
1189
1190 TocModels & GuiView::tocModels()
1191 {
1192         return d.toc_models_;
1193 }
1194
1195
1196 void GuiView::setFocus()
1197 {
1198         LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1199         QMainWindow::setFocus();
1200 }
1201
1202
1203 bool GuiView::hasFocus() const
1204 {
1205         if (currentWorkArea())
1206                 return currentWorkArea()->hasFocus();
1207         if (currentMainWorkArea())
1208                 return currentMainWorkArea()->hasFocus();
1209         return d.bg_widget_->hasFocus();
1210 }
1211
1212
1213 void GuiView::focusInEvent(QFocusEvent * e)
1214 {
1215         LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1216         QMainWindow::focusInEvent(e);
1217         // Make sure guiApp points to the correct view.
1218         guiApp->setCurrentView(this);
1219         if (currentWorkArea())
1220                 currentWorkArea()->setFocus();
1221         else if (currentMainWorkArea())
1222                 currentMainWorkArea()->setFocus();
1223         else
1224                 d.bg_widget_->setFocus();
1225 }
1226
1227
1228 void GuiView::showEvent(QShowEvent * e)
1229 {
1230         LYXERR(Debug::GUI, "Passed Geometry "
1231                 << size().height() << "x" << size().width()
1232                 << "+" << pos().x() << "+" << pos().y());
1233
1234         if (d.splitter_->count() == 0)
1235                 // No work area, switch to the background widget.
1236                 d.setBackground();
1237
1238         updateToolbars();
1239         QMainWindow::showEvent(e);
1240 }
1241
1242
1243 bool GuiView::closeScheduled()
1244 {
1245         closing_ = true;
1246         return close();
1247 }
1248
1249
1250 bool GuiView::prepareAllBuffersForLogout()
1251 {
1252         Buffer * first = theBufferList().first();
1253         if (!first)
1254                 return true;
1255
1256         // First, iterate over all buffers and ask the users if unsaved
1257         // changes should be saved.
1258         // We cannot use a for loop as the buffer list cycles.
1259         Buffer * b = first;
1260         do {
1261                 if (!saveBufferIfNeeded(*b, false))
1262                         return false;
1263                 b = theBufferList().next(b);
1264         } while (b != first);
1265
1266         // Next, save session state
1267         // When a view/window was closed before without quitting LyX, there
1268         // are already entries in the lastOpened list.
1269         theSession().lastOpened().clear();
1270         writeSession();
1271
1272         return true;
1273 }
1274
1275
1276 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1277  ** is responsibility of the container (e.g., dialog)
1278  **/
1279 void GuiView::closeEvent(QCloseEvent * close_event)
1280 {
1281         LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1282
1283         if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1284                 Alert::warning(_("Exit LyX"),
1285                         _("LyX could not be closed because documents are being processed by LyX."));
1286                 close_event->setAccepted(false);
1287                 return;
1288         }
1289
1290         // If the user pressed the x (so we didn't call closeView
1291         // programmatically), we want to clear all existing entries.
1292         if (!closing_)
1293                 theSession().lastOpened().clear();
1294         closing_ = true;
1295
1296         writeSession();
1297
1298         // it can happen that this event arrives without selecting the view,
1299         // e.g. when clicking the close button on a background window.
1300         setFocus();
1301         if (!closeWorkAreaAll()) {
1302                 closing_ = false;
1303                 close_event->ignore();
1304                 return;
1305         }
1306
1307         // Make sure that nothing will use this to be closed View.
1308         guiApp->unregisterView(this);
1309
1310         if (isFullScreen()) {
1311                 // Switch off fullscreen before closing.
1312                 toggleFullScreen();
1313                 updateDialogs();
1314         }
1315
1316         // Make sure the timer time out will not trigger a statusbar update.
1317         d.statusbar_timer_.stop();
1318         d.statusbar_stats_timer_.stop();
1319
1320         // Saving fullscreen requires additional tweaks in the toolbar code.
1321         // It wouldn't also work under linux natively.
1322         if (lyxrc.allow_geometry_session) {
1323                 saveLayout();
1324                 saveUISettings();
1325         }
1326
1327         close_event->accept();
1328 }
1329
1330
1331 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1332 {
1333         if (event->mimeData()->hasUrls())
1334                 event->accept();
1335         /// \todo Ask lyx-devel is this is enough:
1336         /// if (event->mimeData()->hasFormat("text/plain"))
1337         ///     event->acceptProposedAction();
1338 }
1339
1340
1341 void GuiView::dropEvent(QDropEvent * event)
1342 {
1343         QList<QUrl> files = event->mimeData()->urls();
1344         if (files.isEmpty())
1345                 return;
1346
1347         LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1348         for (int i = 0; i != files.size(); ++i) {
1349                 string const file = os::internal_path(fromqstr(
1350                         files.at(i).toLocalFile()));
1351                 if (file.empty())
1352                         continue;
1353
1354                 string const ext = support::getExtension(file);
1355                 vector<const Format *> found_formats;
1356
1357                 // Find all formats that have the correct extension.
1358                 for (const Format * fmt : theConverters().importableFormats())
1359                         if (fmt->hasExtension(ext))
1360                                 found_formats.push_back(fmt);
1361
1362                 FuncRequest cmd;
1363                 if (!found_formats.empty()) {
1364                         if (found_formats.size() > 1) {
1365                                 //FIXME: show a dialog to choose the correct importable format
1366                                 LYXERR(Debug::FILES,
1367                                         "Multiple importable formats found, selecting first");
1368                         }
1369                         string const arg = found_formats[0]->name() + " " + file;
1370                         cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1371                 }
1372                 else {
1373                         //FIXME: do we have to explicitly check whether it's a lyx file?
1374                         LYXERR(Debug::FILES,
1375                                 "No formats found, trying to open it as a lyx file");
1376                         cmd = FuncRequest(LFUN_FILE_OPEN, file);
1377                 }
1378                 // add the functions to the queue
1379                 guiApp->addToFuncRequestQueue(cmd);
1380                 event->accept();
1381         }
1382         // now process the collected functions. We perform the events
1383         // asynchronously. This prevents potential problems in case the
1384         // BufferView is closed within an event.
1385         guiApp->processFuncRequestQueueAsync();
1386 }
1387
1388
1389 void GuiView::message(docstring const & str)
1390 {
1391         if (ForkedProcess::iAmAChild())
1392                 return;
1393
1394         // call is moved to GUI-thread by GuiProgress
1395         d.progress_->appendMessage(toqstr(str));
1396 }
1397
1398
1399 void GuiView::clearMessageText()
1400 {
1401         message(docstring());
1402 }
1403
1404
1405 void GuiView::updateStatusBarMessage(QString const & str)
1406 {
1407         statusBar()->showMessage(str);
1408         d.statusbar_timer_.stop();
1409         d.statusbar_timer_.start(3000);
1410 }
1411
1412
1413 void GuiView::clearMessage()
1414 {
1415         // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1416         // the hasFocus function mostly returns false, even if the focus is on
1417         // a workarea in this view.
1418         //if (!hasFocus())
1419         //      return;
1420         showMessage();
1421         d.statusbar_timer_.stop();
1422 }
1423
1424 void GuiView::showStats()
1425 {
1426         if (!statsEnabled())
1427                 return;
1428
1429         d.time_to_update -= d.timer_rate;
1430
1431         BufferView * bv = currentBufferView();
1432         Buffer * buf = bv ? &bv->buffer() : nullptr;
1433         if (!buf) {
1434                 stat_counts_->hide();
1435                 return;
1436         }
1437
1438         Cursor const & cur = bv->cursor();
1439
1440         // we start new selection and need faster update
1441         if (!d.already_in_selection_ && cur.selection())
1442                 d.time_to_update = 0;
1443
1444         if (d.time_to_update > 0)
1445                 return;
1446
1447         DocIterator from, to;
1448         if (cur.selection()) {
1449                 from = cur.selectionBegin();
1450                 to = cur.selectionEnd();
1451                 d.already_in_selection_ = true;
1452         } else {
1453                 from = doc_iterator_begin(buf);
1454                 to = doc_iterator_end(buf);
1455                 d.already_in_selection_ = false;
1456         }
1457
1458         buf->updateStatistics(from, to);
1459
1460         QStringList stats;
1461         if (word_count_enabled_) {
1462                 int const words = buf->wordCount();
1463                 if (words == 1)
1464                         stats << toqstr(bformat(_("%1$d Word"), words));
1465                 else
1466                         stats << toqstr(bformat(_("%1$d Words"), words));
1467         }
1468         int const chars_with_blanks = buf->charCount(true);
1469         if (char_count_enabled_) {
1470                 if (chars_with_blanks == 1)
1471                         stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1472                 else
1473                         stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1474         }
1475         if (char_nb_count_enabled_) {
1476                 int const chars = buf->charCount(false);
1477                 if (chars == 1)
1478                         stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1479                 else
1480                         stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1481         }
1482         stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1483         stat_counts_->show();
1484
1485         d.time_to_update = d.default_stats_rate;
1486         // fast updates for small selections
1487         if (chars_with_blanks < d.max_sel_chars && cur.selection())
1488                 d.time_to_update = d.timer_rate;
1489 }
1490
1491
1492 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1493 {
1494         if (wa != d.current_work_area_
1495                 || wa->bufferView().buffer().isInternal())
1496                 return;
1497         Buffer const & buf = wa->bufferView().buffer();
1498         // Set the windows title
1499         docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1500         if (buf.notifiesExternalModification()) {
1501                 title = bformat(_("%1$s (modified externally)"), title);
1502                 // If the external modification status has changed, then maybe the status of
1503                 // buffer-save has changed too.
1504                 updateToolbars();
1505         }
1506         title += from_ascii(" - LyX");
1507         setWindowTitle(toqstr(title));
1508         // Sets the path for the window: this is used by OSX to
1509         // allow a context click on the title bar showing a menu
1510         // with the path up to the file
1511         setWindowFilePath(toqstr(buf.absFileName()));
1512         // Tell Qt whether the current document is changed
1513         setWindowModified(!buf.isClean());
1514
1515         if (buf.params().shell_escape)
1516                 shell_escape_->show();
1517         else
1518                 shell_escape_->hide();
1519
1520         if (buf.hasReadonlyFlag())
1521                 read_only_->show();
1522         else
1523                 read_only_->hide();
1524
1525         if (buf.lyxvc().inUse()) {
1526                 version_control_->show();
1527                 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1528         } else
1529                 version_control_->hide();
1530 }
1531
1532
1533 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1534 {
1535         if (d.current_work_area_)
1536                 // disconnect the current work area from all slots
1537                 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1538         disconnectBuffer();
1539         disconnectBufferView();
1540         connectBufferView(wa->bufferView());
1541         connectBuffer(wa->bufferView().buffer());
1542         d.current_work_area_ = wa;
1543         QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1544                          this, SLOT(updateWindowTitle(GuiWorkArea *)));
1545         QObject::connect(wa, SIGNAL(busy(bool)),
1546                          this, SLOT(setBusy(bool)));
1547         // connection of a signal to a signal
1548         QObject::connect(wa, SIGNAL(bufferViewChanged()),
1549                          this, SIGNAL(bufferViewChanged()));
1550         Q_EMIT updateWindowTitle(wa);
1551         Q_EMIT bufferViewChanged();
1552 }
1553
1554
1555 void GuiView::onBufferViewChanged()
1556 {
1557         structureChanged();
1558         // Buffer-dependent dialogs must be updated. This is done here because
1559         // some dialogs require buffer()->text.
1560         updateDialogs();
1561         zoom_slider_->setEnabled(currentBufferView());
1562         zoom_value_->setEnabled(currentBufferView());
1563         zoom_in_->setEnabled(currentBufferView());
1564         zoom_out_->setEnabled(currentBufferView());
1565 }
1566
1567
1568 void GuiView::on_lastWorkAreaRemoved()
1569 {
1570         if (closing_)
1571                 // We already are in a close event. Nothing more to do.
1572                 return;
1573
1574         if (d.splitter_->count() > 1)
1575                 // We have a splitter so don't close anything.
1576                 return;
1577
1578         // Reset and updates the dialogs.
1579         Q_EMIT bufferViewChanged();
1580
1581         resetWindowTitle();
1582         updateStatusBar();
1583
1584         if (lyxrc.open_buffers_in_tabs)
1585                 // Nothing more to do, the window should stay open.
1586                 return;
1587
1588         if (guiApp->viewIds().size() > 1) {
1589                 close();
1590                 return;
1591         }
1592
1593 #ifdef Q_OS_MAC
1594         // On Mac we also close the last window because the application stay
1595         // resident in memory. On other platforms we don't close the last
1596         // window because this would quit the application.
1597         close();
1598 #endif
1599 }
1600
1601
1602 void GuiView::updateStatusBar()
1603 {
1604         // let the user see the explicit message
1605         if (d.statusbar_timer_.isActive())
1606                 return;
1607
1608         showMessage();
1609 }
1610
1611
1612 void GuiView::showMessage()
1613 {
1614         if (busy_)
1615                 return;
1616         QString msg = toqstr(theGuiApp()->viewStatusMessage());
1617         if (msg.isEmpty()) {
1618                 BufferView const * bv = currentBufferView();
1619                 if (bv)
1620                         msg = toqstr(bv->cursor().currentState(devel_mode_));
1621                 else
1622                         msg = qt_("Welcome to LyX!");
1623         }
1624         statusBar()->showMessage(msg);
1625 }
1626
1627
1628 bool GuiView::statsEnabled() const
1629 {
1630         return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1631 }
1632
1633
1634 bool GuiView::event(QEvent * e)
1635 {
1636         switch (e->type())
1637         {
1638         // Useful debug code:
1639         //case QEvent::ActivationChange:
1640         //case QEvent::WindowDeactivate:
1641         //case QEvent::Paint:
1642         //case QEvent::Enter:
1643         //case QEvent::Leave:
1644         //case QEvent::HoverEnter:
1645         //case QEvent::HoverLeave:
1646         //case QEvent::HoverMove:
1647         //case QEvent::StatusTip:
1648         //case QEvent::DragEnter:
1649         //case QEvent::DragLeave:
1650         //case QEvent::Drop:
1651         //      break;
1652
1653         case QEvent::WindowStateChange: {
1654                 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1655                 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1656                 bool result = QMainWindow::event(e);
1657                 bool nfstate = (windowState() & Qt::WindowFullScreen);
1658                 if (!ofstate && nfstate) {
1659                         LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1660                         // switch to full-screen state
1661                         if (lyxrc.full_screen_statusbar)
1662                                 statusBar()->hide();
1663                         if (lyxrc.full_screen_menubar)
1664                                 menuBar()->hide();
1665                         if (lyxrc.full_screen_toolbars) {
1666                                 for (auto const & tb_p  : d.toolbars_)
1667                                         if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1668                                                 tb_p.second->hide();
1669                         }
1670                         for (int i = 0; i != d.splitter_->count(); ++i)
1671                                 d.tabWorkArea(i)->setFullScreen(true);
1672 #if QT_VERSION > 0x050903
1673                         //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1674                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1675 #endif
1676                         setContentsMargins(-2, -2, -2, -2);
1677                         // bug 5274
1678                         hideDialogs("prefs", nullptr);
1679                 } else if (ofstate && !nfstate) {
1680                         LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1681                         // switch back from full-screen state
1682                         if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1683                                 statusBar()->show();
1684                         if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1685                                 menuBar()->show();
1686                         if (lyxrc.full_screen_toolbars) {
1687                                 for (auto const & tb_p  : d.toolbars_)
1688                                         if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1689                                                 tb_p.second->show();
1690                                 //updateToolbars();
1691                         }
1692                         for (int i = 0; i != d.splitter_->count(); ++i)
1693                                 d.tabWorkArea(i)->setFullScreen(false);
1694 #if QT_VERSION > 0x050903
1695                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1696 #endif
1697                         setContentsMargins(0, 0, 0, 0);
1698                 }
1699                 return result;
1700         }
1701
1702         case QEvent::WindowActivate: {
1703                 GuiView * old_view = guiApp->currentView();
1704                 if (this == old_view) {
1705                         setFocus();
1706                         return QMainWindow::event(e);
1707                 }
1708                 if (old_view && old_view->currentBufferView()) {
1709                         // save current selection to the selection buffer to allow
1710                         // middle-button paste in this window.
1711                         cap::saveSelection(old_view->currentBufferView()->cursor());
1712                 }
1713                 guiApp->setCurrentView(this);
1714                 if (d.current_work_area_)
1715                         on_currentWorkAreaChanged(d.current_work_area_);
1716                 else
1717                         resetWindowTitle();
1718                 setFocus();
1719                 return QMainWindow::event(e);
1720         }
1721
1722         case QEvent::ShortcutOverride: {
1723                 // See bug 4888
1724                 if (isFullScreen() && menuBar()->isHidden()) {
1725                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1726                         // FIXME: we should also try to detect special LyX shortcut such as
1727                         // Alt-P and Alt-M. Right now there is a hack in
1728                         // GuiWorkArea::processKeySym() that hides again the menubar for
1729                         // those cases.
1730                         if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1731                                 menuBar()->show();
1732                                 return QMainWindow::event(e);
1733                         }
1734                 }
1735                 return QMainWindow::event(e);
1736         }
1737
1738         case QEvent::ApplicationPaletteChange: {
1739                 // runtime switch from/to dark mode
1740                 refillToolbars();
1741                 return QMainWindow::event(e);
1742         }
1743
1744         case QEvent::Gesture: {
1745                 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1746                 QGesture *gp = ge->gesture(Qt::PinchGesture);
1747                 if (gp) {
1748                         QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1749                         QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1750                         qreal totalScaleFactor = pinch->totalScaleFactor();
1751                         LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1752                         if (pinch->state() == Qt::GestureStarted) {
1753                                 initialZoom_ = lyxrc.currentZoom;
1754                                 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1755                         }
1756                         if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1757                                 qreal factor = initialZoom_ * totalScaleFactor;
1758                                 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1759                                 zoomValueChanged(factor);
1760                         }
1761                 }
1762                 return QMainWindow::event(e);
1763         }
1764
1765         default:
1766                 return QMainWindow::event(e);
1767         }
1768 }
1769
1770 void GuiView::resetWindowTitle()
1771 {
1772         setWindowTitle(qt_("LyX"));
1773 }
1774
1775 bool GuiView::focusNextPrevChild(bool /*next*/)
1776 {
1777         setFocus();
1778         return true;
1779 }
1780
1781
1782 bool GuiView::busy() const
1783 {
1784         return busy_ > 0;
1785 }
1786
1787
1788 void GuiView::setBusy(bool busy)
1789 {
1790         bool const busy_before = busy_ > 0;
1791         busy ? ++busy_ : --busy_;
1792         if ((busy_ > 0) == busy_before)
1793                 // busy state didn't change
1794                 return;
1795
1796         if (busy) {
1797                 QApplication::setOverrideCursor(Qt::WaitCursor);
1798                 return;
1799         }
1800         QApplication::restoreOverrideCursor();
1801         updateLayoutList();
1802 }
1803
1804
1805 void GuiView::resetCommandExecute()
1806 {
1807         command_execute_ = false;
1808         updateToolbars();
1809 }
1810
1811
1812 double GuiView::pixelRatio() const
1813 {
1814         return qt_scale_factor * devicePixelRatio();
1815 }
1816
1817
1818 GuiWorkArea * GuiView::workArea(int index)
1819 {
1820         if (TabWorkArea * twa = d.currentTabWorkArea())
1821                 if (index < twa->count())
1822                         return twa->workArea(index);
1823         return nullptr;
1824 }
1825
1826
1827 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1828 {
1829         if (currentWorkArea()
1830                 && &currentWorkArea()->bufferView().buffer() == &buffer)
1831                 return currentWorkArea();
1832         if (TabWorkArea * twa = d.currentTabWorkArea())
1833                 return twa->workArea(buffer);
1834         return nullptr;
1835 }
1836
1837
1838 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1839 {
1840         // Automatically create a TabWorkArea if there are none yet.
1841         TabWorkArea * tab_widget = d.splitter_->count()
1842                 ? d.currentTabWorkArea() : addTabWorkArea();
1843         return tab_widget->addWorkArea(buffer, *this);
1844 }
1845
1846
1847 TabWorkArea * GuiView::addTabWorkArea()
1848 {
1849         TabWorkArea * twa = new TabWorkArea;
1850         QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1851                 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1852         QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1853                          this, SLOT(on_lastWorkAreaRemoved()));
1854
1855         d.splitter_->addWidget(twa);
1856         d.stack_widget_->setCurrentWidget(d.splitter_);
1857         return twa;
1858 }
1859
1860
1861 GuiWorkArea const * GuiView::currentWorkArea() const
1862 {
1863         return d.current_work_area_;
1864 }
1865
1866
1867 GuiWorkArea * GuiView::currentWorkArea()
1868 {
1869         return d.current_work_area_;
1870 }
1871
1872
1873 GuiWorkArea const * GuiView::currentMainWorkArea() const
1874 {
1875         if (!d.currentTabWorkArea())
1876                 return nullptr;
1877         return d.currentTabWorkArea()->currentWorkArea();
1878 }
1879
1880
1881 GuiWorkArea * GuiView::currentMainWorkArea()
1882 {
1883         if (!d.currentTabWorkArea())
1884                 return nullptr;
1885         return d.currentTabWorkArea()->currentWorkArea();
1886 }
1887
1888
1889 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1890 {
1891         LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1892         if (!wa) {
1893                 d.current_work_area_ = nullptr;
1894                 d.setBackground();
1895                 Q_EMIT bufferViewChanged();
1896                 return;
1897         }
1898
1899         // FIXME: I've no clue why this is here and why it accesses
1900         //  theGuiApp()->currentView, which might be 0 (bug 6464).
1901         //  See also 27525 (vfr).
1902         if (theGuiApp()->currentView() == this
1903                   && theGuiApp()->currentView()->currentWorkArea() == wa)
1904                 return;
1905
1906         if (currentBufferView())
1907                 cap::saveSelection(currentBufferView()->cursor());
1908
1909         theGuiApp()->setCurrentView(this);
1910         d.current_work_area_ = wa;
1911
1912         // We need to reset this now, because it will need to be
1913         // right if the tabWorkArea gets reset in the for loop. We
1914         // will change it back if we aren't in that case.
1915         GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1916         d.current_main_work_area_ = wa;
1917
1918         for (int i = 0; i != d.splitter_->count(); ++i) {
1919                 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1920                         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1921                                 << ", Current main wa: " << currentMainWorkArea());
1922                         return;
1923                 }
1924         }
1925
1926         d.current_main_work_area_ = old_cmwa;
1927
1928         LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1929         on_currentWorkAreaChanged(wa);
1930         BufferView & bv = wa->bufferView();
1931         bv.cursor().fixIfBroken();
1932         bv.updateMetrics();
1933         wa->setUpdatesEnabled(true);
1934         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1935 }
1936
1937
1938 void GuiView::removeWorkArea(GuiWorkArea * wa)
1939 {
1940         LASSERT(wa, return);
1941         if (wa == d.current_work_area_) {
1942                 disconnectBuffer();
1943                 disconnectBufferView();
1944                 d.current_work_area_ = nullptr;
1945                 d.current_main_work_area_ = nullptr;
1946         }
1947
1948         bool found_twa = false;
1949         for (int i = 0; i != d.splitter_->count(); ++i) {
1950                 TabWorkArea * twa = d.tabWorkArea(i);
1951                 if (twa->removeWorkArea(wa)) {
1952                         // Found in this tab group, and deleted the GuiWorkArea.
1953                         found_twa = true;
1954                         if (twa->count() != 0) {
1955                                 if (d.current_work_area_ == nullptr)
1956                                         // This means that we are closing the current GuiWorkArea, so
1957                                         // switch to the next GuiWorkArea in the found TabWorkArea.
1958                                         setCurrentWorkArea(twa->currentWorkArea());
1959                         } else {
1960                                 // No more WorkAreas in this tab group, so delete it.
1961                                 delete twa;
1962                         }
1963                         break;
1964                 }
1965         }
1966
1967         // It is not a tabbed work area (i.e., the search work area), so it
1968         // should be deleted by other means.
1969         LASSERT(found_twa, return);
1970
1971         if (d.current_work_area_ == nullptr) {
1972                 if (d.splitter_->count() != 0) {
1973                         TabWorkArea * twa = d.currentTabWorkArea();
1974                         setCurrentWorkArea(twa->currentWorkArea());
1975                 } else {
1976                         // No more work areas, switch to the background widget.
1977                         setCurrentWorkArea(nullptr);
1978                 }
1979         }
1980 }
1981
1982
1983 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1984 {
1985         for (int i = 0; i < d.splitter_->count(); ++i)
1986                 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1987                         return true;
1988
1989         FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1990         return fr->isVisible() && fr->hasWorkArea(wa);
1991 }
1992
1993
1994 LayoutBox * GuiView::getLayoutDialog() const
1995 {
1996         return d.layout_;
1997 }
1998
1999
2000 void GuiView::updateLayoutList()
2001 {
2002         if (d.layout_)
2003                 d.layout_->updateContents(false);
2004 }
2005
2006
2007 void GuiView::updateToolbars()
2008 {
2009         if (d.current_work_area_) {
2010                 int context = 0;
2011                 if (d.current_work_area_->bufferView().cursor().inMathed()
2012                         && !d.current_work_area_->bufferView().cursor().inRegexped())
2013                         context |= Toolbars::MATH;
2014                 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2015                         context |= Toolbars::TABLE;
2016                 if (currentBufferView()->buffer().areChangesPresent()
2017                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2018                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2019                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2020                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2021                         context |= Toolbars::REVIEW;
2022                 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2023                         context |= Toolbars::MATHMACROTEMPLATE;
2024                 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2025                         context |= Toolbars::IPA;
2026                 if (command_execute_)
2027                         context |= Toolbars::MINIBUFFER;
2028                 if (minibuffer_focus_) {
2029                         context |= Toolbars::MINIBUFFER_FOCUS;
2030                         minibuffer_focus_ = false;
2031                 }
2032
2033                 for (auto const & tb_p : d.toolbars_)
2034                         tb_p.second->update(context);
2035         } else
2036                 for (auto const & tb_p : d.toolbars_)
2037                         tb_p.second->update();
2038 }
2039
2040
2041 void GuiView::refillToolbars()
2042 {
2043         DynamicMenuButton::resetIconCache();
2044         for (auto const & tb_p : d.toolbars_)
2045                 tb_p.second->refill();
2046 }
2047
2048
2049 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2050 {
2051         LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2052         LASSERT(newBuffer, return);
2053
2054         GuiWorkArea * wa = workArea(*newBuffer);
2055         if (wa == nullptr) {
2056                 setBusy(true);
2057                 newBuffer->masterBuffer()->updateBuffer();
2058                 setBusy(false);
2059                 wa = addWorkArea(*newBuffer);
2060                 // scroll to the position when the BufferView was last closed
2061                 if (lyxrc.use_lastfilepos) {
2062                         LastFilePosSection::FilePos filepos =
2063                                 theSession().lastFilePos().load(newBuffer->fileName());
2064                         wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2065                 }
2066         } else {
2067                 //Disconnect the old buffer...there's no new one.
2068                 disconnectBuffer();
2069         }
2070         connectBuffer(*newBuffer);
2071         connectBufferView(wa->bufferView());
2072         if (switch_to)
2073                 setCurrentWorkArea(wa);
2074 }
2075
2076
2077 void GuiView::connectBuffer(Buffer & buf)
2078 {
2079         buf.setGuiDelegate(this);
2080 }
2081
2082
2083 void GuiView::disconnectBuffer()
2084 {
2085         if (d.current_work_area_)
2086                 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2087 }
2088
2089
2090 void GuiView::connectBufferView(BufferView & bv)
2091 {
2092         bv.setGuiDelegate(this);
2093 }
2094
2095
2096 void GuiView::disconnectBufferView()
2097 {
2098         if (d.current_work_area_)
2099                 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2100 }
2101
2102
2103 void GuiView::errors(string const & error_type, bool from_master)
2104 {
2105         BufferView const * const bv = currentBufferView();
2106         if (!bv)
2107                 return;
2108
2109         ErrorList const & el = from_master ?
2110                 bv->buffer().masterBuffer()->errorList(error_type) :
2111                 bv->buffer().errorList(error_type);
2112
2113         if (el.empty())
2114                 return;
2115
2116         string err = error_type;
2117         if (from_master)
2118                 err = "from_master|" + error_type;
2119         showDialog("errorlist", err);
2120 }
2121
2122
2123 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2124 {
2125         d.toc_models_.updateItem(toqstr(type), dit);
2126 }
2127
2128
2129 void GuiView::structureChanged()
2130 {
2131         // This is called from the Buffer, which has no way to ensure that cursors
2132         // in BufferView remain valid.
2133         if (documentBufferView())
2134                 documentBufferView()->cursor().sanitize();
2135         // FIXME: This is slightly expensive, though less than the tocBackend update
2136         // (#9880). This also resets the view in the Toc Widget (#6675).
2137         d.toc_models_.reset(documentBufferView());
2138         // Navigator needs more than a simple update in this case. It needs to be
2139         // rebuilt.
2140         updateDialog("toc", "");
2141 }
2142
2143
2144 void GuiView::updateDialog(string const & name, string const & sdata)
2145 {
2146         if (!isDialogVisible(name))
2147                 return;
2148
2149         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2150         if (it == d.dialogs_.end())
2151                 return;
2152
2153         Dialog * const dialog = it->second.get();
2154         if (dialog->isVisibleView())
2155                 dialog->initialiseParams(sdata);
2156 }
2157
2158
2159 BufferView * GuiView::documentBufferView()
2160 {
2161         return currentMainWorkArea()
2162                 ? &currentMainWorkArea()->bufferView()
2163                 : nullptr;
2164 }
2165
2166
2167 BufferView const * GuiView::documentBufferView() const
2168 {
2169         return currentMainWorkArea()
2170                 ? &currentMainWorkArea()->bufferView()
2171                 : nullptr;
2172 }
2173
2174
2175 BufferView * GuiView::currentBufferView()
2176 {
2177         return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2178 }
2179
2180
2181 BufferView const * GuiView::currentBufferView() const
2182 {
2183         return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2184 }
2185
2186
2187 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2188         Buffer const * orig, Buffer * clone)
2189 {
2190         bool const success = clone->autoSave();
2191         delete clone;
2192         busyBuffers.remove(orig);
2193         return success
2194                 ? _("Automatic save done.")
2195                 : _("Automatic save failed!");
2196 }
2197
2198
2199 void GuiView::autoSave()
2200 {
2201         LYXERR(Debug::INFO, "Running autoSave()");
2202
2203         Buffer * buffer = documentBufferView()
2204                 ? &documentBufferView()->buffer() : nullptr;
2205         if (!buffer) {
2206                 resetAutosaveTimers();
2207                 return;
2208         }
2209
2210         GuiViewPrivate::busyBuffers.insert(buffer);
2211         QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2212                 buffer, buffer->cloneBufferOnly());
2213         d.autosave_watcher_.setFuture(f);
2214         resetAutosaveTimers();
2215 }
2216
2217
2218 void GuiView::resetAutosaveTimers()
2219 {
2220         if (lyxrc.autosave)
2221                 d.autosave_timeout_.restart();
2222 }
2223
2224
2225 namespace {
2226
2227 double zoomRatio(FuncRequest const & cmd, double const zr)
2228 {
2229         if (cmd.argument().empty()) {
2230                 if (cmd.action() == LFUN_BUFFER_ZOOM)
2231                         return 1.0;
2232                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2233                         return zr + 0.1;
2234                 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2235                         return zr - 0.1;
2236         } else {
2237                 if (cmd.action() == LFUN_BUFFER_ZOOM)
2238                         return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2239                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2240                         return zr + convert<int>(cmd.argument()) / 100.0;
2241                 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2242                         return zr - convert<int>(cmd.argument()) / 100.0;
2243         }
2244 }
2245
2246 }
2247
2248
2249 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2250 {
2251         bool enable = true;
2252         Buffer * buf = currentBufferView()
2253                 ? &currentBufferView()->buffer() : nullptr;
2254         Buffer * doc_buffer = documentBufferView()
2255                 ? &(documentBufferView()->buffer()) : nullptr;
2256
2257 #ifdef Q_OS_MAC
2258         /* In LyX/Mac, when a dialog is open, the menus of the
2259            application can still be accessed without giving focus to
2260            the main window. In this case, we want to disable the menu
2261            entries that are buffer-related.
2262            This code must not be used on Linux and Windows, since it
2263            would disable buffer-related entries when hovering over the
2264            menu (see bug #9574).
2265          */
2266         if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2267                 buf = 0;
2268                 doc_buffer = 0;
2269         }
2270 #endif
2271
2272         // Check whether we need a buffer
2273         if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2274                 // no, exit directly
2275                 flag.message(from_utf8(N_("Command not allowed with"
2276                                         "out any document open")));
2277                 flag.setEnabled(false);
2278                 return true;
2279         }
2280
2281         if (cmd.origin() == FuncRequest::TOC) {
2282                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2283                 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2284                         flag.setEnabled(false);
2285                 return true;
2286         }
2287
2288         switch(cmd.action()) {
2289         case LFUN_BUFFER_IMPORT:
2290                 break;
2291
2292         case LFUN_MASTER_BUFFER_EXPORT:
2293                 enable = doc_buffer
2294                         && (doc_buffer->parent() != nullptr
2295                             || doc_buffer->hasChildren())
2296                         && !d.processing_thread_watcher_.isRunning()
2297                         // this launches a dialog, which would be in the wrong Buffer
2298                         && !(::lyx::operator==(cmd.argument(), "custom"));
2299                 break;
2300
2301         case LFUN_MASTER_BUFFER_UPDATE:
2302         case LFUN_MASTER_BUFFER_VIEW:
2303                 enable = doc_buffer
2304                         && (doc_buffer->parent() != nullptr
2305                             || doc_buffer->hasChildren())
2306                         && !d.processing_thread_watcher_.isRunning();
2307                 break;
2308
2309         case LFUN_BUFFER_UPDATE:
2310         case LFUN_BUFFER_VIEW: {
2311                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2312                         enable = false;
2313                         break;
2314                 }
2315                 string format = to_utf8(cmd.argument());
2316                 if (cmd.argument().empty())
2317                         format = doc_buffer->params().getDefaultOutputFormat();
2318                 enable = doc_buffer->params().isExportable(format, true);
2319                 break;
2320         }
2321
2322         case LFUN_BUFFER_RELOAD:
2323                 enable = doc_buffer && !doc_buffer->isUnnamed()
2324                         && doc_buffer->fileName().exists()
2325                         && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2326                 break;
2327
2328         case LFUN_BUFFER_RESET_EXPORT:
2329                 enable = doc_buffer != nullptr;
2330                 break;
2331
2332         case LFUN_BUFFER_CHILD_OPEN:
2333                 enable = doc_buffer != nullptr;
2334                 break;
2335
2336         case LFUN_MASTER_BUFFER_FORALL: {
2337                 if (doc_buffer == nullptr) {
2338                         flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2339                         enable = false;
2340                         break;
2341                 }
2342                 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2343                 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2344                         flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2345                         enable = false;
2346                         break;
2347                 }
2348                 enable = false;
2349                 for (Buffer * buf : doc_buffer->allRelatives()) {
2350                         GuiWorkArea * wa = workArea(*buf);
2351                         if (!wa)
2352                                 continue;
2353                         if (wa->bufferView().getStatus(cmdToPass, flag)) {
2354                                 enable = flag.enabled();
2355                                 break;
2356                         }
2357                 }
2358                 break;
2359         }
2360
2361         case LFUN_BUFFER_WRITE:
2362                 enable = doc_buffer && (doc_buffer->isUnnamed()
2363                                         || (!doc_buffer->isClean()
2364                                             || cmd.argument() == "force"));
2365                 break;
2366
2367         //FIXME: This LFUN should be moved to GuiApplication.
2368         case LFUN_BUFFER_WRITE_ALL: {
2369                 // We enable the command only if there are some modified buffers
2370                 Buffer * first = theBufferList().first();
2371                 enable = false;
2372                 if (!first)
2373                         break;
2374                 Buffer * b = first;
2375                 // We cannot use a for loop as the buffer list is a cycle.
2376                 do {
2377                         if (!b->isClean()) {
2378                                 enable = true;
2379                                 break;
2380                         }
2381                         b = theBufferList().next(b);
2382                 } while (b != first);
2383                 break;
2384         }
2385
2386         case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2387                 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2388                 break;
2389
2390         case LFUN_BUFFER_EXPORT: {
2391                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2392                         enable = false;
2393                         break;
2394                 }
2395                 return doc_buffer->getStatus(cmd, flag);
2396         }
2397
2398         case LFUN_BUFFER_EXPORT_AS:
2399                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2400                         enable = false;
2401                         break;
2402                 }
2403                 // fall through
2404         case LFUN_BUFFER_WRITE_AS:
2405         case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2406                 enable = doc_buffer != nullptr;
2407                 break;
2408
2409         case LFUN_EXPORT_CANCEL:
2410                 enable = d.processing_thread_watcher_.isRunning();
2411                 break;
2412
2413         case LFUN_BUFFER_CLOSE:
2414         case LFUN_VIEW_CLOSE:
2415                 enable = doc_buffer != nullptr;
2416                 break;
2417
2418         case LFUN_BUFFER_CLOSE_ALL:
2419                 enable = theBufferList().last() != theBufferList().first();
2420                 break;
2421
2422         case LFUN_BUFFER_CHKTEX: {
2423                 // hide if we have no checktex command
2424                 if (lyxrc.chktex_command.empty()) {
2425                         flag.setUnknown(true);
2426                         enable = false;
2427                         break;
2428                 }
2429                 if (!doc_buffer || !doc_buffer->params().isLatex()
2430                     || d.processing_thread_watcher_.isRunning()) {
2431                         // grey out, don't hide
2432                         enable = false;
2433                         break;
2434                 }
2435                 enable = true;
2436                 break;
2437         }
2438
2439         case LFUN_VIEW_SPLIT:
2440                 if (cmd.getArg(0) == "vertical")
2441                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2442                                          d.splitter_->orientation() == Qt::Vertical);
2443                 else
2444                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2445                                          d.splitter_->orientation() == Qt::Horizontal);
2446                 break;
2447
2448         case LFUN_TAB_GROUP_NEXT:
2449         case LFUN_TAB_GROUP_PREVIOUS:
2450                 enable = (d.splitter_->count() > 1);
2451                 break;
2452
2453         case LFUN_TAB_GROUP_CLOSE:
2454                 enable = d.tabWorkAreaCount() > 1;
2455                 break;
2456
2457         case LFUN_DEVEL_MODE_TOGGLE:
2458                 flag.setOnOff(devel_mode_);
2459                 break;
2460
2461         case LFUN_TOOLBAR_SET: {
2462                 string const name = cmd.getArg(0);
2463                 string const state = cmd.getArg(1);
2464                 if (name.empty() || state.empty()) {
2465                         enable = false;
2466                         docstring const msg =
2467                                 _("Function toolbar-set requires two arguments!");
2468                         flag.message(msg);
2469                         break;
2470                 }
2471                 if (state != "on" && state != "off" && state != "auto") {
2472                         enable = false;
2473                         docstring const msg =
2474                                 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2475                                         from_utf8(state));
2476                         flag.message(msg);
2477                         break;
2478                 }
2479                 if (GuiToolbar * t = toolbar(name)) {
2480                         bool const autovis = t->visibility() & Toolbars::AUTO;
2481                         if (state == "on")
2482                                 flag.setOnOff(t->isVisible() && !autovis);
2483                         else if (state == "off")
2484                                 flag.setOnOff(!t->isVisible() && !autovis);
2485                         else if (state == "auto")
2486                                 flag.setOnOff(autovis);
2487                 } else {
2488                         enable = false;
2489                         docstring const msg =
2490                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2491                         flag.message(msg);
2492                 }
2493                 break;
2494         }
2495
2496         case LFUN_TOOLBAR_TOGGLE: {
2497                 string const name = cmd.getArg(0);
2498                 if (GuiToolbar * t = toolbar(name))
2499                         flag.setOnOff(t->isVisible());
2500                 else {
2501                         enable = false;
2502                         docstring const msg =
2503                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2504                         flag.message(msg);
2505                 }
2506                 break;
2507         }
2508
2509         case LFUN_TOOLBAR_MOVABLE: {
2510                 string const name = cmd.getArg(0);
2511                 // use negation since locked == !movable
2512                 if (name == "*")
2513                         // toolbar name * locks all toolbars
2514                         flag.setOnOff(!toolbarsMovable_);
2515                 else if (GuiToolbar * t = toolbar(name))
2516                         flag.setOnOff(!(t->isMovable()));
2517                 else {
2518                         enable = false;
2519                         docstring const msg =
2520                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2521                         flag.message(msg);
2522                 }
2523                 break;
2524         }
2525
2526         case LFUN_ICON_SIZE:
2527                 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2528                 break;
2529
2530         case LFUN_DROP_LAYOUTS_CHOICE:
2531                 enable = buf != nullptr;
2532                 break;
2533
2534         case LFUN_UI_TOGGLE:
2535                 if (cmd.argument() == "zoomlevel") {
2536                         flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2537                 } else if (cmd.argument() == "zoomslider") {
2538                         flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2539                 } else if (cmd.argument() == "statistics-w") {
2540                         flag.setOnOff(word_count_enabled_);
2541                 } else if (cmd.argument() == "statistics-cb") {
2542                         flag.setOnOff(char_count_enabled_);
2543                 } else if (cmd.argument() == "statistics-c") {
2544                         flag.setOnOff(char_nb_count_enabled_);
2545                 } else
2546                         flag.setOnOff(isFullScreen());
2547                 break;
2548
2549         case LFUN_DIALOG_DISCONNECT_INSET:
2550                 break;
2551
2552         case LFUN_DIALOG_HIDE:
2553                 // FIXME: should we check if the dialog is shown?
2554                 break;
2555
2556         case LFUN_DIALOG_TOGGLE:
2557                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2558                 // to set "enable"
2559                 // fall through
2560         case LFUN_DIALOG_SHOW: {
2561                 string const name = cmd.getArg(0);
2562                 if (!doc_buffer)
2563                         enable = name == "aboutlyx"
2564                                 || name == "file" //FIXME: should be removed.
2565                                 || name == "lyxfiles"
2566                                 || name == "prefs"
2567                                 || name == "texinfo"
2568                                 || name == "progress"
2569                                 || name == "compare";
2570                 else if (name == "character" || name == "symbols"
2571                         || name == "mathdelimiter" || name == "mathmatrix") {
2572                         if (!buf || buf->isReadonly())
2573                                 enable = false;
2574                         else {
2575                                 Cursor const & cur = currentBufferView()->cursor();
2576                                 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2577                         }
2578                 }
2579                 else if (name == "latexlog")
2580                         enable = FileName(doc_buffer->logName()).isReadableFile();
2581                 else if (name == "spellchecker")
2582                         enable = theSpellChecker()
2583                                 && !doc_buffer->text().empty();
2584                 else if (name == "vclog")
2585                         enable = doc_buffer->lyxvc().inUse();
2586                 break;
2587         }
2588
2589         case LFUN_DIALOG_UPDATE: {
2590                 string const name = cmd.getArg(0);
2591                 if (!buf)
2592                         enable = name == "prefs";
2593                 break;
2594         }
2595
2596         case LFUN_COMMAND_EXECUTE:
2597         case LFUN_MESSAGE:
2598         case LFUN_MENU_OPEN:
2599                 // Nothing to check.
2600                 break;
2601
2602         case LFUN_COMPLETION_INLINE:
2603                 if (!d.current_work_area_
2604                         || !d.current_work_area_->completer().inlinePossible(
2605                         currentBufferView()->cursor()))
2606                         enable = false;
2607                 break;
2608
2609         case LFUN_COMPLETION_POPUP:
2610                 if (!d.current_work_area_
2611                         || !d.current_work_area_->completer().popupPossible(
2612                         currentBufferView()->cursor()))
2613                         enable = false;
2614                 break;
2615
2616         case LFUN_COMPLETE:
2617                 if (!d.current_work_area_
2618                         || !d.current_work_area_->completer().inlinePossible(
2619                         currentBufferView()->cursor()))
2620                         enable = false;
2621                 break;
2622
2623         case LFUN_COMPLETION_ACCEPT:
2624                 if (!d.current_work_area_
2625                         || (!d.current_work_area_->completer().popupVisible()
2626                         && !d.current_work_area_->completer().inlineVisible()
2627                         && !d.current_work_area_->completer().completionAvailable()))
2628                         enable = false;
2629                 break;
2630
2631         case LFUN_COMPLETION_CANCEL:
2632                 if (!d.current_work_area_
2633                         || (!d.current_work_area_->completer().popupVisible()
2634                         && !d.current_work_area_->completer().inlineVisible()))
2635                         enable = false;
2636                 break;
2637
2638         case LFUN_BUFFER_ZOOM_OUT:
2639         case LFUN_BUFFER_ZOOM_IN:
2640         case LFUN_BUFFER_ZOOM: {
2641                 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2642                 if (zoom < zoom_min_) {
2643                         docstring const msg =
2644                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2645                         flag.message(msg);
2646                         enable = false;
2647                 } else if (zoom > zoom_max_) {
2648                         docstring const msg =
2649                                 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2650                         flag.message(msg);
2651                         enable = false;
2652                 } else
2653                         enable = doc_buffer;
2654                 break;
2655         }
2656
2657
2658         case LFUN_BUFFER_MOVE_NEXT:
2659         case LFUN_BUFFER_MOVE_PREVIOUS:
2660                 // we do not cycle when moving
2661         case LFUN_BUFFER_NEXT:
2662         case LFUN_BUFFER_PREVIOUS:
2663                 // because we cycle, it doesn't matter whether on first or last
2664                 enable = (d.currentTabWorkArea()->count() > 1);
2665                 break;
2666         case LFUN_BUFFER_SWITCH:
2667                 // toggle on the current buffer, but do not toggle off
2668                 // the other ones (is that a good idea?)
2669                 if (doc_buffer
2670                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2671                         flag.setOnOff(true);
2672                 break;
2673
2674         case LFUN_VC_REGISTER:
2675                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2676                 break;
2677         case LFUN_VC_RENAME:
2678                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2679                 break;
2680         case LFUN_VC_COPY:
2681                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2682                 break;
2683         case LFUN_VC_CHECK_IN:
2684                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2685                 break;
2686         case LFUN_VC_CHECK_OUT:
2687                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2688                 break;
2689         case LFUN_VC_LOCKING_TOGGLE:
2690                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2691                         && doc_buffer->lyxvc().lockingToggleEnabled();
2692                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2693                 break;
2694         case LFUN_VC_REVERT:
2695                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2696                         && !doc_buffer->hasReadonlyFlag();
2697                 break;
2698         case LFUN_VC_UNDO_LAST:
2699                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2700                 break;
2701         case LFUN_VC_REPO_UPDATE:
2702                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2703                 break;
2704         case LFUN_VC_COMMAND: {
2705                 if (cmd.argument().empty())
2706                         enable = false;
2707                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2708                         enable = false;
2709                 break;
2710         }
2711         case LFUN_VC_COMPARE:
2712                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2713                 break;
2714
2715         case LFUN_SERVER_GOTO_FILE_ROW:
2716         case LFUN_LYX_ACTIVATE:
2717         case LFUN_WINDOW_RAISE:
2718                 break;
2719         case LFUN_FORWARD_SEARCH:
2720                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2721                         doc_buffer && doc_buffer->isSyncTeXenabled();
2722                 break;
2723
2724         case LFUN_FILE_INSERT_PLAINTEXT:
2725         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2726                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2727                 break;
2728
2729         case LFUN_SPELLING_CONTINUOUSLY:
2730                 flag.setOnOff(lyxrc.spellcheck_continuously);
2731                 break;
2732
2733         case LFUN_CITATION_OPEN:
2734                 enable = true;
2735                 break;
2736
2737         default:
2738                 return false;
2739         }
2740
2741         if (!enable)
2742                 flag.setEnabled(false);
2743
2744         return true;
2745 }
2746
2747
2748 static FileName selectTemplateFile()
2749 {
2750         FileDialog dlg(qt_("Select template file"));
2751         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2752         dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2753
2754         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2755                                  QStringList(qt_("LyX Documents (*.lyx)")));
2756
2757         if (result.first == FileDialog::Later)
2758                 return FileName();
2759         if (result.second.isEmpty())
2760                 return FileName();
2761         return FileName(fromqstr(result.second));
2762 }
2763
2764
2765 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2766 {
2767         setBusy(true);
2768
2769         Buffer * newBuffer = nullptr;
2770         try {
2771                 newBuffer = checkAndLoadLyXFile(filename);
2772         } catch (ExceptionMessage const &) {
2773                 setBusy(false);
2774                 throw;
2775         }
2776         setBusy(false);
2777
2778         if (!newBuffer) {
2779                 message(_("Document not loaded."));
2780                 return nullptr;
2781         }
2782
2783         setBuffer(newBuffer);
2784         newBuffer->errors("Parse");
2785
2786         if (tolastfiles) {
2787                 theSession().lastFiles().add(filename);
2788                 theSession().writeFile();
2789   }
2790
2791         return newBuffer;
2792 }
2793
2794
2795 void GuiView::openDocuments(string const & fname, int origin)
2796 {
2797         string initpath = lyxrc.document_path;
2798
2799         if (documentBufferView()) {
2800                 string const trypath = documentBufferView()->buffer().filePath();
2801                 // If directory is writeable, use this as default.
2802                 if (FileName(trypath).isDirWritable())
2803                         initpath = trypath;
2804         }
2805
2806         QStringList files;
2807
2808         if (fname.empty()) {
2809                 FileDialog dlg(qt_("Select documents to open"));
2810                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2811                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2812
2813                 QStringList const filter({
2814                                 qt_("LyX Documents (*.lyx)"),
2815                                 qt_("LyX Document Backups (*.lyx~)"),
2816                                 qt_("All Files (*.*)")
2817                 });
2818                 FileDialog::Results results =
2819                         dlg.openMulti(toqstr(initpath), filter);
2820
2821                 if (results.first == FileDialog::Later)
2822                         return;
2823
2824                 files = results.second;
2825
2826                 // check selected filename
2827                 if (files.isEmpty()) {
2828                         message(_("Canceled."));
2829                         return;
2830                 }
2831         } else
2832                 files << toqstr(fname);
2833
2834         // iterate over all selected files
2835         for (auto const & file : files) {
2836                 string filename = fromqstr(file);
2837
2838                 // get absolute path of file and add ".lyx" to the filename if
2839                 // necessary.
2840                 FileName const fullname =
2841                                 fileSearch(string(), filename, "lyx", support::may_not_exist);
2842                 if (!fullname.empty())
2843                         filename = fullname.absFileName();
2844
2845                 if (!fullname.onlyPath().isDirectory()) {
2846                         Alert::warning(_("Invalid filename"),
2847                                         bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2848                                         from_utf8(fullname.absFileName())));
2849                         continue;
2850                 }
2851
2852                 // if the file doesn't exist and isn't already open (bug 6645),
2853                 // let the user create one
2854                 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2855                         !LyXVC::file_not_found_hook(fullname)) {
2856                         // see bug #12609
2857                         if (origin == FuncRequest::MENU) {
2858                                 docstring const & msg =
2859                                         bformat(_("File\n"
2860                                                 "%1$s\n"
2861                                                 "does not exist. Create empty file?"),
2862                                                         from_utf8(filename));
2863                                 int ret = Alert::prompt(_("File does not exist"),
2864                                                         msg, 0, 1,
2865                                                         _("Create &File"),
2866                                                         _("&Cancel"));
2867                                 if (ret == 1)
2868                                         continue;
2869                         }
2870                         Buffer * const b = newFile(filename, string(), true);
2871                         if (b)
2872                                 setBuffer(b);
2873                         continue;
2874                 }
2875
2876                 docstring const disp_fn = makeDisplayPath(filename);
2877                 message(bformat(_("Opening document %1$s..."), disp_fn));
2878
2879                 docstring str2;
2880                 Buffer * buf = loadDocument(fullname);
2881                 if (buf) {
2882                         str2 = bformat(_("Document %1$s opened."), disp_fn);
2883                         if (buf->lyxvc().inUse())
2884                                 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2885                                         " " + _("Version control detected.");
2886                 } else {
2887                         str2 = bformat(_("Could not open document %1$s"), disp_fn);
2888                 }
2889                 message(str2);
2890         }
2891 }
2892
2893 // FIXME: clean that
2894 static bool import(GuiView * lv, FileName const & filename,
2895         string const & format, ErrorList & errorList)
2896 {
2897         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2898
2899         string loader_format;
2900         vector<string> loaders = theConverters().loaders();
2901         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2902                 for (string const & loader : loaders) {
2903                         if (!theConverters().isReachable(format, loader))
2904                                 continue;
2905
2906                         string const tofile =
2907                                 support::changeExtension(filename.absFileName(),
2908                                 theFormats().extension(loader));
2909                         if (theConverters().convert(nullptr, filename, FileName(tofile),
2910                                 filename, format, loader, errorList) != Converters::SUCCESS)
2911                                 return false;
2912                         loader_format = loader;
2913                         break;
2914                 }
2915                 if (loader_format.empty()) {
2916                         frontend::Alert::error(_("Couldn't import file"),
2917                                          bformat(_("No information for importing the format %1$s."),
2918                                          translateIfPossible(theFormats().prettyName(format))));
2919                         return false;
2920                 }
2921         } else
2922                 loader_format = format;
2923
2924         if (loader_format == "lyx") {
2925                 Buffer * buf = lv->loadDocument(lyxfile);
2926                 if (!buf)
2927                         return false;
2928         } else {
2929                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2930                 if (!b)
2931                         return false;
2932                 lv->setBuffer(b);
2933                 bool as_paragraphs = loader_format == "textparagraph";
2934                 string filename2 = (loader_format == format) ? filename.absFileName()
2935                         : support::changeExtension(filename.absFileName(),
2936                                           theFormats().extension(loader_format));
2937                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2938                         as_paragraphs);
2939                 guiApp->setCurrentView(lv);
2940                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2941         }
2942
2943         return true;
2944 }
2945
2946
2947 void GuiView::importDocument(string const & argument)
2948 {
2949         string format;
2950         string filename = split(argument, format, ' ');
2951
2952         LYXERR(Debug::INFO, format << " file: " << filename);
2953
2954         // need user interaction
2955         if (filename.empty()) {
2956                 string initpath = lyxrc.document_path;
2957                 if (documentBufferView()) {
2958                         string const trypath = documentBufferView()->buffer().filePath();
2959                         // If directory is writeable, use this as default.
2960                         if (FileName(trypath).isDirWritable())
2961                                 initpath = trypath;
2962                 }
2963
2964                 docstring const text = bformat(_("Select %1$s file to import"),
2965                         translateIfPossible(theFormats().prettyName(format)));
2966
2967                 FileDialog dlg(toqstr(text));
2968                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2969                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2970
2971                 docstring filter = translateIfPossible(theFormats().prettyName(format));
2972                 filter += " (*.{";
2973                 // FIXME UNICODE
2974                 filter += from_utf8(theFormats().extensions(format));
2975                 filter += "})";
2976
2977                 FileDialog::Result result =
2978                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2979
2980                 if (result.first == FileDialog::Later)
2981                         return;
2982
2983                 filename = fromqstr(result.second);
2984
2985                 // check selected filename
2986                 if (filename.empty())
2987                         message(_("Canceled."));
2988         }
2989
2990         if (filename.empty())
2991                 return;
2992
2993         // get absolute path of file
2994         FileName const fullname(support::makeAbsPath(filename));
2995
2996         // Can happen if the user entered a path into the dialog
2997         // (see bug #7437)
2998         if (fullname.onlyFileName().empty()) {
2999                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3000                                           "Aborting import."),
3001                                         from_utf8(fullname.absFileName()));
3002                 frontend::Alert::error(_("File name error"), msg);
3003                 message(_("Canceled."));
3004                 return;
3005         }
3006
3007
3008         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3009
3010         // Check if the document already is open
3011         Buffer * buf = theBufferList().getBuffer(lyxfile);
3012         if (buf) {
3013                 setBuffer(buf);
3014                 if (!closeBuffer()) {
3015                         message(_("Canceled."));
3016                         return;
3017                 }
3018         }
3019
3020         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3021
3022         // if the file exists already, and we didn't do
3023         // -i lyx thefile.lyx, warn
3024         if (lyxfile.exists() && fullname != lyxfile) {
3025
3026                 docstring text = bformat(_("The document %1$s already exists.\n\n"
3027                         "Do you want to overwrite that document?"), displaypath);
3028                 int const ret = Alert::prompt(_("Overwrite document?"),
3029                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
3030
3031                 if (ret == 1) {
3032                         message(_("Canceled."));
3033                         return;
3034                 }
3035         }
3036
3037         message(bformat(_("Importing %1$s..."), displaypath));
3038         ErrorList errorList;
3039         if (import(this, fullname, format, errorList))
3040                 message(_("imported."));
3041         else
3042                 message(_("file not imported!"));
3043
3044         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3045 }
3046
3047
3048 void GuiView::newDocument(string const & filename, string templatefile,
3049                           bool from_template)
3050 {
3051         FileName initpath(lyxrc.document_path);
3052         if (documentBufferView()) {
3053                 FileName const trypath(documentBufferView()->buffer().filePath());
3054                 // If directory is writeable, use this as default.
3055                 if (trypath.isDirWritable())
3056                         initpath = trypath;
3057         }
3058
3059         if (from_template) {
3060                 if (templatefile.empty())
3061                         templatefile =  selectTemplateFile().absFileName();
3062                 if (templatefile.empty())
3063                         return;
3064         }
3065
3066         Buffer * b;
3067         if (filename.empty())
3068                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3069         else
3070                 b = newFile(filename, templatefile, true);
3071
3072         if (b)
3073                 setBuffer(b);
3074
3075         // If no new document could be created, it is unsure
3076         // whether there is a valid BufferView.
3077         if (currentBufferView())
3078                 // Ensure the cursor is correctly positioned on screen.
3079                 currentBufferView()->showCursor();
3080 }
3081
3082
3083 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3084 {
3085         BufferView * bv = documentBufferView();
3086         if (!bv)
3087                 return false;
3088
3089         // FIXME UNICODE
3090         FileName filename(to_utf8(fname));
3091         if (filename.empty()) {
3092                 // Launch a file browser
3093                 // FIXME UNICODE
3094                 string initpath = lyxrc.document_path;
3095                 string const trypath = bv->buffer().filePath();
3096                 // If directory is writeable, use this as default.
3097                 if (FileName(trypath).isDirWritable())
3098                         initpath = trypath;
3099
3100                 // FIXME UNICODE
3101                 FileDialog dlg(qt_("Select LyX document to insert"));
3102                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3103                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3104
3105                 FileDialog::Result result = dlg.open(toqstr(initpath),
3106                                          QStringList(qt_("LyX Documents (*.lyx)")));
3107
3108                 if (result.first == FileDialog::Later)
3109                         return false;
3110
3111                 // FIXME UNICODE
3112                 filename.set(fromqstr(result.second));
3113
3114                 // check selected filename
3115                 if (filename.empty()) {
3116                         // emit message signal.
3117                         message(_("Canceled."));
3118                         return false;
3119                 }
3120         }
3121
3122         bv->insertLyXFile(filename, ignorelang);
3123         bv->buffer().errors("Parse");
3124         return true;
3125 }
3126
3127
3128 string const GuiView::getTemplatesPath(Buffer & b)
3129 {
3130         // We start off with the user's templates path
3131         string result = addPath(package().user_support().absFileName(), "templates");
3132         // Check for the document language
3133         string const langcode = b.params().language->code();
3134         string const shortcode = langcode.substr(0, 2);
3135         if (!langcode.empty() && shortcode != "en") {
3136                 string subpath = addPath(result, shortcode);
3137                 string subpath_long = addPath(result, langcode);
3138                 // If we have a subdirectory for the language already,
3139                 // navigate there
3140                 FileName sp = FileName(subpath);
3141                 if (sp.isDirectory())
3142                         result = subpath;
3143                 else if (FileName(subpath_long).isDirectory())
3144                         result = subpath_long;
3145                 else {
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 document language (%1$s).\n"
3150                                           "This subdirectory does not exists yet.\n"
3151                                           "Do you want to create it?"),
3152                                         _(b.params().language->display()));
3153                         if (Alert::prompt(_("Create Language 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."));
3160                                 else
3161                                         result = subpath;
3162                         }
3163                 }
3164         }
3165         // Do we have a layout category?
3166         string const cat = b.params().baseClass() ?
3167                                 b.params().baseClass()->category()
3168                               : string();
3169         if (!cat.empty()) {
3170                 string subpath = addPath(result, cat);
3171                 // If we have a subdirectory for the category already,
3172                 // navigate there
3173                 FileName sp = FileName(subpath);
3174                 if (sp.isDirectory())
3175                         result = subpath;
3176                 else {
3177                         // Ask whether we should create such a subdirectory
3178                         docstring const text =
3179                                 bformat(_("It is suggested to save the template in a subdirectory\n"
3180                                           "appropriate to the layout category (%1$s).\n"
3181                                           "This subdirectory does not exists yet.\n"
3182                                           "Do you want to create it?"),
3183                                         _(cat));
3184                         if (Alert::prompt(_("Create Category Directory?"),
3185                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3186                                 // If the user agreed, we try to create it and report if this failed.
3187                                 if (!sp.createDirectory(0777))
3188                                         Alert::error(_("Subdirectory creation failed!"),
3189                                                      _("Could not create subdirectory.\n"
3190                                                        "The template will be saved in the parent directory."));
3191                                 else
3192                                         result = subpath;
3193                         }
3194                 }
3195         }
3196         return result;
3197 }
3198
3199
3200 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3201 {
3202         FileName fname = b.fileName();
3203         FileName const oldname = fname;
3204         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3205
3206         if (!newname.empty()) {
3207                 // FIXME UNICODE
3208                 if (as_template)
3209                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3210                 else
3211                         fname = support::makeAbsPath(to_utf8(newname),
3212                                                      oldname.onlyPath().absFileName());
3213         } else {
3214                 // Switch to this Buffer.
3215                 setBuffer(&b);
3216
3217                 // No argument? Ask user through dialog.
3218                 // FIXME UNICODE
3219                 QString const title = as_template ? qt_("Choose a filename to save template as")
3220                                                   : qt_("Choose a filename to save document as");
3221                 FileDialog dlg(title);
3222                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3223                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3224
3225                 fname.ensureExtension(".lyx");
3226
3227                 string const path = as_template ?
3228                                         getTemplatesPath(b)
3229                                       : fname.onlyPath().absFileName();
3230                 FileDialog::Result result =
3231                         dlg.save(toqstr(path),
3232                                    QStringList(qt_("LyX Documents (*.lyx)")),
3233                                          toqstr(fname.onlyFileName()));
3234
3235                 if (result.first == FileDialog::Later)
3236                         return false;
3237
3238                 fname.set(fromqstr(result.second));
3239
3240                 if (fname.empty())
3241                         return false;
3242
3243                 fname.ensureExtension(".lyx");
3244         }
3245
3246         // fname is now the new Buffer location.
3247
3248         // if there is already a Buffer open with this name, we do not want
3249         // to have another one. (the second test makes sure we're not just
3250         // trying to overwrite ourselves, which is fine.)
3251         if (theBufferList().exists(fname) && fname != oldname
3252                   && theBufferList().getBuffer(fname) != &b) {
3253                 docstring const text =
3254                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
3255                             "Please close it before attempting to overwrite it.\n"
3256                             "Do you want to choose a new filename?"),
3257                                 from_utf8(fname.absFileName()));
3258                 int const ret = Alert::prompt(_("Chosen File Already Open"),
3259                         text, 0, 1, _("&Rename"), _("&Cancel"));
3260                 switch (ret) {
3261                 case 0: return renameBuffer(b, docstring(), kind);
3262                 case 1: return false;
3263                 }
3264                 //return false;
3265         }
3266
3267         bool const existsLocal = fname.exists();
3268         bool const existsInVC = LyXVC::fileInVC(fname);
3269         if (existsLocal || existsInVC) {
3270                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3271                 if (kind != LV_WRITE_AS && existsInVC) {
3272                         // renaming to a name that is already in VC
3273                         // would not work
3274                         docstring text = bformat(_("The document %1$s "
3275                                         "is already registered.\n\n"
3276                                         "Do you want to choose a new name?"),
3277                                 file);
3278                         docstring const title = (kind == LV_VC_RENAME) ?
3279                                 _("Rename document?") : _("Copy document?");
3280                         docstring const button = (kind == LV_VC_RENAME) ?
3281                                 _("&Rename") : _("&Copy");
3282                         int const ret = Alert::prompt(title, text, 0, 1,
3283                                 button, _("&Cancel"));
3284                         switch (ret) {
3285                         case 0: return renameBuffer(b, docstring(), kind);
3286                         case 1: return false;
3287                         }
3288                 }
3289
3290                 if (existsLocal) {
3291                         docstring text = bformat(_("The document %1$s "
3292                                         "already exists.\n\n"
3293                                         "Do you want to overwrite that document?"),
3294                                 file);
3295                         int const ret = Alert::prompt(_("Overwrite document?"),
3296                                         text, 0, 2, _("&Overwrite"),
3297                                         _("&Rename"), _("&Cancel"));
3298                         switch (ret) {
3299                         case 0: break;
3300                         case 1: return renameBuffer(b, docstring(), kind);
3301                         case 2: return false;
3302                         }
3303                 }
3304         }
3305
3306         switch (kind) {
3307         case LV_VC_RENAME: {
3308                 string msg = b.lyxvc().rename(fname);
3309                 if (msg.empty())
3310                         return false;
3311                 message(from_utf8(msg));
3312                 break;
3313         }
3314         case LV_VC_COPY: {
3315                 string msg = b.lyxvc().copy(fname);
3316                 if (msg.empty())
3317                         return false;
3318                 message(from_utf8(msg));
3319                 break;
3320         }
3321         case LV_WRITE_AS:
3322         case LV_WRITE_AS_TEMPLATE:
3323                 break;
3324         }
3325         // LyXVC created the file already in case of LV_VC_RENAME or
3326         // LV_VC_COPY, but call saveBuffer() nevertheless to get
3327         // relative paths of included stuff right if we moved e.g. from
3328         // /a/b.lyx to /a/c/b.lyx.
3329
3330         bool const saved = saveBuffer(b, fname);
3331         if (saved)
3332                 b.reload();
3333         return saved;
3334 }
3335
3336
3337 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3338 {
3339         FileName fname = b.fileName();
3340
3341         FileDialog dlg(qt_("Choose a filename to export the document as"));
3342         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3343
3344         QStringList types;
3345         QString const anyformat = qt_("Guess from extension (*.*)");
3346         types << anyformat;
3347
3348         vector<Format const *> export_formats;
3349         for (Format const & f : theFormats())
3350                 if (f.documentFormat())
3351                         export_formats.push_back(&f);
3352         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3353         map<QString, string> fmap;
3354         QString filter;
3355         string ext;
3356         for (Format const * f : export_formats) {
3357                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3358                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3359                                                      loc_prettyname,
3360                                                      from_ascii(f->extension())));
3361                 types << loc_filter;
3362                 fmap[loc_filter] = f->name();
3363                 if (from_ascii(f->name()) == iformat) {
3364                         filter = loc_filter;
3365                         ext = f->extension();
3366                 }
3367         }
3368         string ofname = fname.onlyFileName();
3369         if (!ext.empty())
3370                 ofname = support::changeExtension(ofname, ext);
3371         FileDialog::Result result =
3372                 dlg.save(toqstr(fname.onlyPath().absFileName()),
3373                          types,
3374                          toqstr(ofname),
3375                          &filter);
3376         if (result.first != FileDialog::Chosen)
3377                 return false;
3378
3379         string fmt_name;
3380         fname.set(fromqstr(result.second));
3381         if (filter == anyformat)
3382                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3383         else
3384                 fmt_name = fmap[filter];
3385         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3386                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3387
3388         if (fmt_name.empty() || fname.empty())
3389                 return false;
3390
3391         fname.ensureExtension(theFormats().extension(fmt_name));
3392
3393         // fname is now the new Buffer location.
3394         if (fname.exists()) {
3395                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3396                 docstring text = bformat(_("The document %1$s already "
3397                                            "exists.\n\nDo you want to "
3398                                            "overwrite that document?"),
3399                                          file);
3400                 int const ret = Alert::prompt(_("Overwrite document?"),
3401                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3402                 switch (ret) {
3403                 case 0: break;
3404                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3405                 case 2: return false;
3406                 }
3407         }
3408
3409         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3410         DispatchResult dr;
3411         dispatch(cmd, dr);
3412         return dr.dispatched();
3413 }
3414
3415
3416 bool GuiView::saveBuffer(Buffer & b)
3417 {
3418         return saveBuffer(b, FileName());
3419 }
3420
3421
3422 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3423 {
3424         if (workArea(b) && workArea(b)->inDialogMode())
3425                 return true;
3426
3427         if (fn.empty() && b.isUnnamed())
3428                 return renameBuffer(b, docstring());
3429
3430         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3431         if (success) {
3432                 theSession().lastFiles().add(b.fileName());
3433                 theSession().writeFile();
3434                 return true;
3435         }
3436
3437         // Switch to this Buffer.
3438         setBuffer(&b);
3439
3440         // FIXME: we don't tell the user *WHY* the save failed !!
3441         docstring const file = makeDisplayPath(b.absFileName(), 30);
3442         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3443                                    "Do you want to rename the document and "
3444                                    "try again?"), file);
3445         int const ret = Alert::prompt(_("Rename and save?"),
3446                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3447         switch (ret) {
3448         case 0:
3449                 if (!renameBuffer(b, docstring()))
3450                         return false;
3451                 break;
3452         case 1:
3453                 break;
3454         case 2:
3455                 return false;
3456         }
3457
3458         return saveBuffer(b, fn);
3459 }
3460
3461
3462 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3463 {
3464         return closeWorkArea(wa, false);
3465 }
3466
3467
3468 // We only want to close the buffer if it is not visible in other workareas
3469 // of the same view, nor in other views, and if this is not a child
3470 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3471 {
3472         Buffer & buf = wa->bufferView().buffer();
3473
3474         bool last_wa = d.countWorkAreasOf(buf) == 1
3475                 && !inOtherView(buf) && !buf.parent();
3476
3477         bool close_buffer = last_wa;
3478
3479         if (last_wa) {
3480                 if (lyxrc.close_buffer_with_last_view == "yes")
3481                         ; // Nothing to do
3482                 else if (lyxrc.close_buffer_with_last_view == "no")
3483                         close_buffer = false;
3484                 else {
3485                         docstring file;
3486                         if (buf.isUnnamed())
3487                                 file = from_utf8(buf.fileName().onlyFileName());
3488                         else
3489                                 file = buf.fileName().displayName(30);
3490                         docstring const text = bformat(
3491                                 _("Last view on document %1$s is being closed.\n"
3492                                   "Would you like to close or hide the document?\n"
3493                                   "\n"
3494                                   "Hidden documents can be displayed back through\n"
3495                                   "the menu: View->Hidden->...\n"
3496                                   "\n"
3497                                   "To remove this question, set your preference in:\n"
3498                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3499                                 ), file);
3500                         int ret = Alert::prompt(_("Close or hide document?"),
3501                                 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3502                         if (ret == 2)
3503                                 return false;
3504                         close_buffer = (ret == 0);
3505                 }
3506         }
3507
3508         return closeWorkArea(wa, close_buffer);
3509 }
3510
3511
3512 bool GuiView::closeBuffer()
3513 {
3514         GuiWorkArea * wa = currentMainWorkArea();
3515         // coverity complained about this
3516         // it seems unnecessary, but perhaps is worth the check
3517         LASSERT(wa, return false);
3518
3519         setCurrentWorkArea(wa);
3520         Buffer & buf = wa->bufferView().buffer();
3521         return closeWorkArea(wa, !buf.parent());
3522 }
3523
3524
3525 void GuiView::writeSession() const {
3526         GuiWorkArea const * active_wa = currentMainWorkArea();
3527         for (int i = 0; i < d.splitter_->count(); ++i) {
3528                 TabWorkArea * twa = d.tabWorkArea(i);
3529                 for (int j = 0; j < twa->count(); ++j) {
3530                         GuiWorkArea * wa = twa->workArea(j);
3531                         Buffer & buf = wa->bufferView().buffer();
3532                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3533                 }
3534         }
3535 }
3536
3537
3538 bool GuiView::closeBufferAll()
3539 {
3540
3541         for (auto & buf : theBufferList()) {
3542                 if (!saveBufferIfNeeded(*buf, false)) {
3543                         // Closing has been cancelled, so abort.
3544                         return false;
3545                 }
3546         }
3547
3548         // Close the workareas in all other views
3549         QList<int> const ids = guiApp->viewIds();
3550         for (int i = 0; i != ids.size(); ++i) {
3551                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3552                         return false;
3553         }
3554
3555         // Close our own workareas
3556         if (!closeWorkAreaAll())
3557                 return false;
3558
3559         return true;
3560 }
3561
3562
3563 bool GuiView::closeWorkAreaAll()
3564 {
3565         setCurrentWorkArea(currentMainWorkArea());
3566
3567         // We might be in a situation that there is still a tabWorkArea, but
3568         // there are no tabs anymore. This can happen when we get here after a
3569         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3570         // many TabWorkArea's have no documents anymore.
3571         int empty_twa = 0;
3572
3573         // We have to call count() each time, because it can happen that
3574         // more than one splitter will disappear in one iteration (bug 5998).
3575         while (d.splitter_->count() > empty_twa) {
3576                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3577
3578                 if (twa->count() == 0)
3579                         ++empty_twa;
3580                 else {
3581                         setCurrentWorkArea(twa->currentWorkArea());
3582                         if (!closeTabWorkArea(twa))
3583                                 return false;
3584                 }
3585         }
3586         return true;
3587 }
3588
3589
3590 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3591 {
3592         if (!wa)
3593                 return false;
3594
3595         Buffer & buf = wa->bufferView().buffer();
3596
3597         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3598                 Alert::warning(_("Close document"),
3599                         _("Document could not be closed because it is being processed by LyX."));
3600                 return false;
3601         }
3602
3603         if (close_buffer)
3604                 return closeBuffer(buf);
3605         else {
3606                 if (!inMultiTabs(wa))
3607                         if (!saveBufferIfNeeded(buf, true))
3608                                 return false;
3609                 removeWorkArea(wa);
3610                 return true;
3611         }
3612 }
3613
3614
3615 bool GuiView::closeBuffer(Buffer & buf)
3616 {
3617         bool success = true;
3618         for (Buffer * child_buf : buf.getChildren()) {
3619                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3620                         child_buf->setParent(nullptr);
3621                         continue;
3622                 }
3623
3624                 // FIXME: should we look in other tabworkareas?
3625                 // ANSWER: I don't think so. I've tested, and if the child is
3626                 // open in some other window, it closes without a problem.
3627                 GuiWorkArea * child_wa = workArea(*child_buf);
3628                 if (child_wa) {
3629                         if (closing_)
3630                                 // If we are in a close_event all children will be closed in some time,
3631                                 // so no need to do it here. This will ensure that the children end up
3632                                 // in the session file in the correct order. If we close the master
3633                                 // buffer, we can close or release the child buffers here too.
3634                                 continue;
3635                         success = closeWorkArea(child_wa, true);
3636                         if (!success)
3637                                 break;
3638                 } else {
3639                         // In this case the child buffer is open but hidden.
3640                         // Even in this case, children can be dirty (e.g.,
3641                         // after a label change in the master, see #11405).
3642                         // Therefore, check this
3643                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3644                                 // If we are in a close_event all children will be closed in some time,
3645                                 // so no need to do it here. This will ensure that the children end up
3646                                 // in the session file in the correct order. If we close the master
3647                                 // buffer, we can close or release the child buffers here too.
3648                                 continue;
3649                         }
3650                         // Save dirty buffers also if closing_!
3651                         if (saveBufferIfNeeded(*child_buf, false)) {
3652                                 child_buf->removeAutosaveFile();
3653                                 theBufferList().release(child_buf);
3654                         } else {
3655                                 // Saving of dirty children has been cancelled.
3656                                 // Cancel the whole process.
3657                                 success = false;
3658                                 break;
3659                         }
3660                 }
3661         }
3662         if (success) {
3663                 // goto bookmark to update bookmark pit.
3664                 // FIXME: we should update only the bookmarks related to this buffer!
3665                 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3666                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3667                 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3668                         guiApp->gotoBookmark(i, false, false);
3669
3670                 if (saveBufferIfNeeded(buf, false)) {
3671                         buf.removeAutosaveFile();
3672                         theBufferList().release(&buf);
3673                         return true;
3674                 }
3675         }
3676         // open all children again to avoid a crash because of dangling
3677         // pointers (bug 6603)
3678         buf.updateBuffer();
3679         return false;
3680 }
3681
3682
3683 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3684 {
3685         while (twa == d.currentTabWorkArea()) {
3686                 twa->setCurrentIndex(twa->count() - 1);
3687
3688                 GuiWorkArea * wa = twa->currentWorkArea();
3689                 Buffer & b = wa->bufferView().buffer();
3690
3691                 // We only want to close the buffer if the same buffer is not visible
3692                 // in another view, and if this is not a child and if we are closing
3693                 // a view (not a tabgroup).
3694                 bool const close_buffer =
3695                         !inOtherView(b) && !b.parent() && closing_;
3696
3697                 if (!closeWorkArea(wa, close_buffer))
3698                         return false;
3699         }
3700         return true;
3701 }
3702
3703
3704 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3705 {
3706         if (buf.isClean() || buf.paragraphs().empty())
3707                 return true;
3708
3709         // Switch to this Buffer.
3710         setBuffer(&buf);
3711
3712         docstring file;
3713         bool exists;
3714         // FIXME: Unicode?
3715         if (buf.isUnnamed()) {
3716                 file = from_utf8(buf.fileName().onlyFileName());
3717                 exists = false;
3718         } else {
3719                 FileName filename = buf.fileName();
3720                 filename.refresh();
3721                 file = filename.displayName(30);
3722                 exists = filename.exists();
3723         }
3724
3725         // Bring this window to top before asking questions.
3726         raise();
3727         activateWindow();
3728
3729         int ret;
3730         if (hiding && buf.isUnnamed()) {
3731                 docstring const text = bformat(_("The document %1$s has not been "
3732                                                  "saved yet.\n\nDo you want to save "
3733                                                  "the document?"), file);
3734                 ret = Alert::prompt(_("Save new document?"),
3735                         text, 0, 1, _("&Save"), _("&Cancel"));
3736                 if (ret == 1)
3737                         ++ret;
3738         } else {
3739                 docstring const text = exists ?
3740                         bformat(_("The document %1$s has unsaved changes."
3741                                   "\n\nDo you want to save the document or "
3742                                   "discard the changes?"), file) :
3743                         bformat(_("The document %1$s has not been saved yet."
3744                                   "\n\nDo you want to save the document or "
3745                                   "discard it entirely?"), file);
3746                 docstring const title = exists ?
3747                         _("Save changed document?") : _("Save document?");
3748                 ret = Alert::prompt(title, text, 0, 2,
3749                                     _("&Save"), _("&Discard"), _("&Cancel"));
3750         }
3751
3752         switch (ret) {
3753         case 0:
3754                 if (!saveBuffer(buf))
3755                         return false;
3756                 break;
3757         case 1:
3758                 // If we crash after this we could have no autosave file
3759                 // but I guess this is really improbable (Jug).
3760                 // Sometimes improbable things happen:
3761                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3762                 // buf.removeAutosaveFile();
3763                 if (hiding)
3764                         // revert all changes
3765                         reloadBuffer(buf);
3766                 buf.markClean();
3767                 break;
3768         case 2:
3769                 return false;
3770         }
3771         return true;
3772 }
3773
3774
3775 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3776 {
3777         Buffer & buf = wa->bufferView().buffer();
3778
3779         for (int i = 0; i != d.splitter_->count(); ++i) {
3780                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3781                 if (wa_ && wa_ != wa)
3782                         return true;
3783         }
3784         return inOtherView(buf);
3785 }
3786
3787
3788 bool GuiView::inOtherView(Buffer & buf)
3789 {
3790         QList<int> const ids = guiApp->viewIds();
3791
3792         for (int i = 0; i != ids.size(); ++i) {
3793                 if (id_ == ids[i])
3794                         continue;
3795
3796                 if (guiApp->view(ids[i]).workArea(buf))
3797                         return true;
3798         }
3799         return false;
3800 }
3801
3802
3803 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3804 {
3805         if (!documentBufferView())
3806                 return;
3807
3808         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3809                 Buffer * const curbuf = &documentBufferView()->buffer();
3810                 int nwa = twa->count();
3811                 for (int i = 0; i < nwa; ++i) {
3812                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3813                                 int next_index;
3814                                 if (np == NEXT)
3815                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3816                                 else
3817                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3818                                 if (move)
3819                                         twa->moveTab(i, next_index);
3820                                 else
3821                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3822                                 break;
3823                         }
3824                 }
3825         }
3826 }
3827
3828
3829 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3830 {
3831         int count = d.splitter_->count();
3832         for (int i = 0; i < count; ++i) {
3833                 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3834                         int new_index;
3835                         if (np == NEXT)
3836                                 new_index = (i == count - 1 ? 0 : i + 1);
3837                         else
3838                                 new_index = (i == 0 ? count - 1 : i - 1);
3839                         setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3840                         break;
3841                 }
3842         }
3843 }
3844
3845
3846 /// make sure the document is saved
3847 static bool ensureBufferClean(Buffer * buffer)
3848 {
3849         LASSERT(buffer, return false);
3850         if (buffer->isClean() && !buffer->isUnnamed())
3851                 return true;
3852
3853         docstring const file = buffer->fileName().displayName(30);
3854         docstring title;
3855         docstring text;
3856         if (!buffer->isUnnamed()) {
3857                 text = bformat(_("The document %1$s has unsaved "
3858                                                  "changes.\n\nDo you want to save "
3859                                                  "the document?"), file);
3860                 title = _("Save changed document?");
3861
3862         } else {
3863                 text = bformat(_("The document %1$s has not been "
3864                                                  "saved yet.\n\nDo you want to save "
3865                                                  "the document?"), file);
3866                 title = _("Save new document?");
3867         }
3868         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3869
3870         if (ret == 0)
3871                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3872
3873         return buffer->isClean() && !buffer->isUnnamed();
3874 }
3875
3876
3877 bool GuiView::reloadBuffer(Buffer & buf)
3878 {
3879         currentBufferView()->cursor().reset();
3880         Buffer::ReadStatus status = buf.reload();
3881         return status == Buffer::ReadSuccess;
3882 }
3883
3884
3885 void GuiView::checkExternallyModifiedBuffers()
3886 {
3887         for (Buffer * buf : theBufferList()) {
3888                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3889                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3890                                         " Reload now? Any local changes will be lost."),
3891                                         from_utf8(buf->absFileName()));
3892                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3893                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3894                         if (!ret)
3895                                 reloadBuffer(*buf);
3896                 }
3897         }
3898 }
3899
3900
3901 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3902 {
3903         Buffer * buffer = documentBufferView()
3904                 ? &(documentBufferView()->buffer()) : nullptr;
3905
3906         switch (cmd.action()) {
3907         case LFUN_VC_REGISTER:
3908                 if (!buffer || !ensureBufferClean(buffer))
3909                         break;
3910                 if (!buffer->lyxvc().inUse()) {
3911                         if (buffer->lyxvc().registrer()) {
3912                                 reloadBuffer(*buffer);
3913                                 dr.clearMessageUpdate();
3914                         }
3915                 }
3916                 break;
3917
3918         case LFUN_VC_RENAME:
3919         case LFUN_VC_COPY: {
3920                 if (!buffer || !ensureBufferClean(buffer))
3921                         break;
3922                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3923                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3924                                 // Some changes are not yet committed.
3925                                 // We test here and not in getStatus(), since
3926                                 // this test is expensive.
3927                                 string log;
3928                                 LyXVC::CommandResult ret =
3929                                         buffer->lyxvc().checkIn(log);
3930                                 dr.setMessage(log);
3931                                 if (ret == LyXVC::ErrorCommand ||
3932                                     ret == LyXVC::VCSuccess)
3933                                         reloadBuffer(*buffer);
3934                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3935                                         frontend::Alert::error(
3936                                                 _("Revision control error."),
3937                                                 _("Document could not be checked in."));
3938                                         break;
3939                                 }
3940                         }
3941                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3942                                 LV_VC_RENAME : LV_VC_COPY;
3943                         renameBuffer(*buffer, cmd.argument(), kind);
3944                 }
3945                 break;
3946         }
3947
3948         case LFUN_VC_CHECK_IN:
3949                 if (!buffer || !ensureBufferClean(buffer))
3950                         break;
3951                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3952                         string log;
3953                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3954                         dr.setMessage(log);
3955                         // Only skip reloading if the checkin was cancelled or
3956                         // an error occurred before the real checkin VCS command
3957                         // was executed, since the VCS might have changed the
3958                         // file even if it could not checkin successfully.
3959                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3960                                 reloadBuffer(*buffer);
3961                 }
3962                 break;
3963
3964         case LFUN_VC_CHECK_OUT:
3965                 if (!buffer || !ensureBufferClean(buffer))
3966                         break;
3967                 if (buffer->lyxvc().inUse()) {
3968                         dr.setMessage(buffer->lyxvc().checkOut());
3969                         reloadBuffer(*buffer);
3970                 }
3971                 break;
3972
3973         case LFUN_VC_LOCKING_TOGGLE:
3974                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3975                         break;
3976                 if (buffer->lyxvc().inUse()) {
3977                         string res = buffer->lyxvc().lockingToggle();
3978                         if (res.empty()) {
3979                                 frontend::Alert::error(_("Revision control error."),
3980                                 _("Error when setting the locking property."));
3981                         } else {
3982                                 dr.setMessage(res);
3983                                 reloadBuffer(*buffer);
3984                         }
3985                 }
3986                 break;
3987
3988         case LFUN_VC_REVERT:
3989                 if (!buffer)
3990                         break;
3991                 if (buffer->lyxvc().revert()) {
3992                         reloadBuffer(*buffer);
3993                         dr.clearMessageUpdate();
3994                 }
3995                 break;
3996
3997         case LFUN_VC_UNDO_LAST:
3998                 if (!buffer)
3999                         break;
4000                 buffer->lyxvc().undoLast();
4001                 reloadBuffer(*buffer);
4002                 dr.clearMessageUpdate();
4003                 break;
4004
4005         case LFUN_VC_REPO_UPDATE:
4006                 if (!buffer)
4007                         break;
4008                 if (ensureBufferClean(buffer)) {
4009                         dr.setMessage(buffer->lyxvc().repoUpdate());
4010                         checkExternallyModifiedBuffers();
4011                 }
4012                 break;
4013
4014         case LFUN_VC_COMMAND: {
4015                 string flag = cmd.getArg(0);
4016                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4017                         break;
4018                 docstring message;
4019                 if (contains(flag, 'M')) {
4020                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
4021                                 break;
4022                 }
4023                 string path = cmd.getArg(1);
4024                 if (contains(path, "$$p") && buffer)
4025                         path = subst(path, "$$p", buffer->filePath());
4026                 LYXERR(Debug::LYXVC, "Directory: " << path);
4027                 FileName pp(path);
4028                 if (!pp.isReadableDirectory()) {
4029                         lyxerr << _("Directory is not accessible.") << endl;
4030                         break;
4031                 }
4032                 support::PathChanger p(pp);
4033
4034                 string command = cmd.getArg(2);
4035                 if (command.empty())
4036                         break;
4037                 if (buffer) {
4038                         command = subst(command, "$$i", buffer->absFileName());
4039                         command = subst(command, "$$p", buffer->filePath());
4040                 }
4041                 command = subst(command, "$$m", to_utf8(message));
4042                 LYXERR(Debug::LYXVC, "Command: " << command);
4043                 Systemcall one;
4044                 one.startscript(Systemcall::Wait, command);
4045
4046                 if (!buffer)
4047                         break;
4048                 if (contains(flag, 'I'))
4049                         buffer->markDirty();
4050                 if (contains(flag, 'R'))
4051                         reloadBuffer(*buffer);
4052
4053                 break;
4054                 }
4055
4056         case LFUN_VC_COMPARE: {
4057                 if (cmd.argument().empty()) {
4058                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4059                         break;
4060                 }
4061                 if (!buffer)
4062                         break;
4063
4064                 string rev1 = cmd.getArg(0);
4065                 string f1, f2;
4066
4067                 // f1
4068                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4069                         break;
4070
4071                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4072                         f2 = buffer->absFileName();
4073                 } else {
4074                         string rev2 = cmd.getArg(1);
4075                         if (rev2.empty())
4076                                 break;
4077                         // f2
4078                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4079                                 break;
4080                 }
4081
4082                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4083                                         f1 << "\n"  << f2 << "\n" );
4084                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4085                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4086                 break;
4087         }
4088
4089         default:
4090                 break;
4091         }
4092 }
4093
4094
4095 void GuiView::openChildDocument(string const & fname)
4096 {
4097         LASSERT(documentBufferView(), return);
4098         Buffer & buffer = documentBufferView()->buffer();
4099         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4100         documentBufferView()->saveBookmark(false);
4101         Buffer * child = nullptr;
4102         if (theBufferList().exists(filename)) {
4103                 child = theBufferList().getBuffer(filename);
4104                 setBuffer(child);
4105         } else {
4106                 message(bformat(_("Opening child document %1$s..."),
4107                         makeDisplayPath(filename.absFileName())));
4108                 child = loadDocument(filename, false);
4109         }
4110         // Set the parent name of the child document.
4111         // This makes insertion of citations and references in the child work,
4112         // when the target is in the parent or another child document.
4113         if (child)
4114                 child->setParent(&buffer);
4115 }
4116
4117
4118 bool GuiView::goToFileRow(string const & argument)
4119 {
4120         string file_name;
4121         int row = -1;
4122         size_t i = argument.find_last_of(' ');
4123         if (i != string::npos) {
4124                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4125                 istringstream is(argument.substr(i + 1));
4126                 is >> row;
4127                 if (is.fail())
4128                         i = string::npos;
4129         }
4130         if (i == string::npos) {
4131                 LYXERR0("Wrong argument: " << argument);
4132                 return false;
4133         }
4134         Buffer * buf = nullptr;
4135         string const realtmp = package().temp_dir().realPath();
4136         // We have to use os::path_prefix_is() here, instead of
4137         // simply prefixIs(), because the file name comes from
4138         // an external application and may need case adjustment.
4139         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4140                 buf = theBufferList().getBufferFromTmp(file_name, true);
4141                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4142                            << (buf ? " success" : " failed"));
4143         } else {
4144                 // Must replace extension of the file to be .lyx
4145                 // and get full path
4146                 FileName const s = fileSearch(string(),
4147                                                   support::changeExtension(file_name, ".lyx"), "lyx");
4148                 // Either change buffer or load the file
4149                 if (theBufferList().exists(s))
4150                         buf = theBufferList().getBuffer(s);
4151                 else if (s.exists()) {
4152                         buf = loadDocument(s);
4153                         if (!buf)
4154                                 return false;
4155                 } else {
4156                         message(bformat(
4157                                         _("File does not exist: %1$s"),
4158                                         makeDisplayPath(file_name)));
4159                         return false;
4160                 }
4161         }
4162         if (!buf) {
4163                 message(bformat(
4164                         _("No buffer for file: %1$s."),
4165                         makeDisplayPath(file_name))
4166                 );
4167                 return false;
4168         }
4169         setBuffer(buf);
4170         bool success = documentBufferView()->setCursorFromRow(row);
4171         if (!success) {
4172                 LYXERR(Debug::OUTFILE,
4173                        "setCursorFromRow: invalid position for row " << row);
4174                 frontend::Alert::error(_("Inverse Search Failed"),
4175                                        _("Invalid position requested by inverse search.\n"
4176                                          "You may need to update the viewed document."));
4177         }
4178         return success;
4179 }
4180
4181
4182 template<class T>
4183 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4184                 Buffer const * orig, Buffer * clone, string const & format)
4185 {
4186         Buffer::ExportStatus const status = func(format);
4187
4188         // the cloning operation will have produced a clone of the entire set of
4189         // documents, starting from the master. so we must delete those.
4190         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4191         delete mbuf;
4192         busyBuffers.remove(orig);
4193         return status;
4194 }
4195
4196
4197 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4198                 Buffer const * orig, Buffer * clone, string const & format)
4199 {
4200         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4201                         &Buffer::doExport;
4202         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4203 }
4204
4205
4206 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4207                 Buffer const * orig, Buffer * clone, string const & format)
4208 {
4209         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4210                         &Buffer::doExport;
4211         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4212 }
4213
4214
4215 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4216                 Buffer const * orig, Buffer * clone, string const & format)
4217 {
4218         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4219                         &Buffer::preview;
4220         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4221 }
4222
4223
4224 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4225                            Buffer const * used_buffer,
4226                            docstring const & msg,
4227                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4228                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4229                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4230                            bool allow_async, bool use_tmpdir)
4231 {
4232         if (!used_buffer)
4233                 return false;
4234
4235         string format = argument;
4236         if (format.empty())
4237                 format = used_buffer->params().getDefaultOutputFormat();
4238         processing_format = format;
4239         if (!msg.empty()) {
4240                 progress_->clearMessages();
4241                 gv_->message(msg);
4242         }
4243 #if EXPORT_in_THREAD
4244         if (allow_async) {
4245                 GuiViewPrivate::busyBuffers.insert(used_buffer);
4246                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4247                 if (!cloned_buffer) {
4248                         Alert::error(_("Export Error"),
4249                                      _("Error cloning the Buffer."));
4250                         return false;
4251                 }
4252                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4253                                         asyncFunc,
4254                                         used_buffer,
4255                                         cloned_buffer,
4256                                         format);
4257                 setPreviewFuture(f);
4258                 last_export_format = used_buffer->params().bufferFormat();
4259                 (void) syncFunc;
4260                 (void) previewFunc;
4261                 // We are asynchronous, so we don't know here anything about the success
4262                 return true;
4263         } else {
4264                 Buffer::ExportStatus status;
4265                 if (syncFunc) {
4266                         status = (used_buffer->*syncFunc)(format, use_tmpdir);
4267                 } else if (previewFunc) {
4268                         status = (used_buffer->*previewFunc)(format);
4269                 } else
4270                         return false;
4271                 handleExportStatus(gv_, status, format);
4272                 (void) asyncFunc;
4273                 return (status == Buffer::ExportSuccess
4274                                 || status == Buffer::PreviewSuccess);
4275         }
4276 #else
4277         (void) allow_async;
4278         Buffer::ExportStatus status;
4279         if (syncFunc) {
4280                 status = (used_buffer->*syncFunc)(format, true);
4281         } else if (previewFunc) {
4282                 status = (used_buffer->*previewFunc)(format);
4283         } else
4284                 return false;
4285         handleExportStatus(gv_, status, format);
4286         (void) asyncFunc;
4287         return (status == Buffer::ExportSuccess
4288                         || status == Buffer::PreviewSuccess);
4289 #endif
4290 }
4291
4292 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4293 {
4294         BufferView * bv = currentBufferView();
4295         LASSERT(bv, return);
4296
4297         // Let the current BufferView dispatch its own actions.
4298         bv->dispatch(cmd, dr);
4299         if (dr.dispatched()) {
4300                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4301                         updateDialog("document", "");
4302                 return;
4303         }
4304
4305         // Try with the document BufferView dispatch if any.
4306         BufferView * doc_bv = documentBufferView();
4307         if (doc_bv && doc_bv != bv) {
4308                 doc_bv->dispatch(cmd, dr);
4309                 if (dr.dispatched()) {
4310                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4311                                 updateDialog("document", "");
4312                         return;
4313                 }
4314         }
4315
4316         // Then let the current Cursor dispatch its own actions.
4317         bv->cursor().dispatch(cmd);
4318
4319         // update completion. We do it here and not in
4320         // processKeySym to avoid another redraw just for a
4321         // changed inline completion
4322         if (cmd.origin() == FuncRequest::KEYBOARD) {
4323                 if (cmd.action() == LFUN_SELF_INSERT
4324                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4325                         updateCompletion(bv->cursor(), true, true);
4326                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4327                         updateCompletion(bv->cursor(), false, true);
4328                 else
4329                         updateCompletion(bv->cursor(), false, false);
4330         }
4331
4332         dr = bv->cursor().result();
4333 }
4334
4335
4336 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4337 {
4338         BufferView * bv = currentBufferView();
4339         // By default we won't need any update.
4340         dr.screenUpdate(Update::None);
4341         // assume cmd will be dispatched
4342         dr.dispatched(true);
4343
4344         Buffer * doc_buffer = documentBufferView()
4345                 ? &(documentBufferView()->buffer()) : nullptr;
4346
4347         if (cmd.origin() == FuncRequest::TOC) {
4348                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4349                 toc->doDispatch(bv->cursor(), cmd, dr);
4350                 return;
4351         }
4352
4353         string const argument = to_utf8(cmd.argument());
4354
4355         switch(cmd.action()) {
4356                 case LFUN_BUFFER_CHILD_OPEN:
4357                         openChildDocument(to_utf8(cmd.argument()));
4358                         break;
4359
4360                 case LFUN_BUFFER_IMPORT:
4361                         importDocument(to_utf8(cmd.argument()));
4362                         break;
4363
4364                 case LFUN_MASTER_BUFFER_EXPORT:
4365                         if (doc_buffer)
4366                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4367                         // fall through
4368                 case LFUN_BUFFER_EXPORT: {
4369                         if (!doc_buffer)
4370                                 break;
4371                         // GCC only sees strfwd.h when building merged
4372                         if (::lyx::operator==(cmd.argument(), "custom")) {
4373                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4374                                 // so the following test should not be needed.
4375                                 // In principle, we could try to switch to such a view...
4376                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4377                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4378                                 break;
4379                         }
4380
4381                         string const dest = cmd.getArg(1);
4382                         FileName target_dir;
4383                         if (!dest.empty() && FileName::isAbsolute(dest))
4384                                 target_dir = FileName(support::onlyPath(dest));
4385                         else
4386                                 target_dir = doc_buffer->fileName().onlyPath();
4387
4388                         string const format = (argument.empty() || argument == "default") ?
4389                                 doc_buffer->params().getDefaultOutputFormat() : argument;
4390
4391                         if ((dest.empty() && doc_buffer->isUnnamed())
4392                             || !target_dir.isDirWritable()) {
4393                                 exportBufferAs(*doc_buffer, from_utf8(format));
4394                                 break;
4395                         }
4396                         /* TODO/Review: Is it a problem to also export the children?
4397                                         See the update_unincluded flag */
4398                         d.asyncBufferProcessing(format,
4399                                                 doc_buffer,
4400                                                 _("Exporting ..."),
4401                                                 &GuiViewPrivate::exportAndDestroy,
4402                                                 &Buffer::doExport,
4403                                                 nullptr, cmd.allowAsync());
4404                         // TODO Inform user about success
4405                         break;
4406                 }
4407
4408                 case LFUN_BUFFER_EXPORT_AS: {
4409                         LASSERT(doc_buffer, break);
4410                         docstring f = cmd.argument();
4411                         if (f.empty())
4412                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4413                         exportBufferAs(*doc_buffer, f);
4414                         break;
4415                 }
4416
4417                 case LFUN_BUFFER_UPDATE: {
4418                         d.asyncBufferProcessing(argument,
4419                                                 doc_buffer,
4420                                                 _("Exporting ..."),
4421                                                 &GuiViewPrivate::compileAndDestroy,
4422                                                 &Buffer::doExport,
4423                                                 nullptr, cmd.allowAsync(), true);
4424                         break;
4425                 }
4426                 case LFUN_BUFFER_VIEW: {
4427                         d.asyncBufferProcessing(argument,
4428                                                 doc_buffer,
4429                                                 _("Previewing ..."),
4430                                                 &GuiViewPrivate::previewAndDestroy,
4431                                                 nullptr,
4432                                                 &Buffer::preview, cmd.allowAsync());
4433                         break;
4434                 }
4435                 case LFUN_MASTER_BUFFER_UPDATE: {
4436                         d.asyncBufferProcessing(argument,
4437                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4438                                                 docstring(),
4439                                                 &GuiViewPrivate::compileAndDestroy,
4440                                                 &Buffer::doExport,
4441                                                 nullptr, cmd.allowAsync(), true);
4442                         break;
4443                 }
4444                 case LFUN_MASTER_BUFFER_VIEW: {
4445                         d.asyncBufferProcessing(argument,
4446                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4447                                                 docstring(),
4448                                                 &GuiViewPrivate::previewAndDestroy,
4449                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4450                         break;
4451                 }
4452                 case LFUN_EXPORT_CANCEL: {
4453                         cancelExport();
4454                         break;
4455                 }
4456                 case LFUN_BUFFER_SWITCH: {
4457                         string const file_name = to_utf8(cmd.argument());
4458                         if (!FileName::isAbsolute(file_name)) {
4459                                 dr.setError(true);
4460                                 dr.setMessage(_("Absolute filename expected."));
4461                                 break;
4462                         }
4463
4464                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4465                         if (!buffer) {
4466                                 dr.setError(true);
4467                                 dr.setMessage(_("Document not loaded"));
4468                                 break;
4469                         }
4470
4471                         // Do we open or switch to the buffer in this view ?
4472                         if (workArea(*buffer)
4473                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4474                                 setBuffer(buffer);
4475                                 break;
4476                         }
4477
4478                         // Look for the buffer in other views
4479                         QList<int> const ids = guiApp->viewIds();
4480                         int i = 0;
4481                         for (; i != ids.size(); ++i) {
4482                                 GuiView & gv = guiApp->view(ids[i]);
4483                                 if (gv.workArea(*buffer)) {
4484                                         gv.raise();
4485                                         gv.activateWindow();
4486                                         gv.setFocus();
4487                                         gv.setBuffer(buffer);
4488                                         break;
4489                                 }
4490                         }
4491
4492                         // If necessary, open a new window as a last resort
4493                         if (i == ids.size()) {
4494                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4495                                 lyx::dispatch(cmd);
4496                         }
4497                         break;
4498                 }
4499
4500                 case LFUN_BUFFER_NEXT:
4501                         gotoNextOrPreviousBuffer(NEXT, false);
4502                         break;
4503
4504                 case LFUN_BUFFER_MOVE_NEXT:
4505                         gotoNextOrPreviousBuffer(NEXT, true);
4506                         break;
4507
4508                 case LFUN_BUFFER_PREVIOUS:
4509                         gotoNextOrPreviousBuffer(PREV, false);
4510                         break;
4511
4512                 case LFUN_BUFFER_MOVE_PREVIOUS:
4513                         gotoNextOrPreviousBuffer(PREV, true);
4514                         break;
4515
4516                 case LFUN_BUFFER_CHKTEX:
4517                         LASSERT(doc_buffer, break);
4518                         doc_buffer->runChktex();
4519                         break;
4520
4521                 case LFUN_COMMAND_EXECUTE: {
4522                         command_execute_ = true;
4523                         minibuffer_focus_ = true;
4524                         break;
4525                 }
4526                 case LFUN_DROP_LAYOUTS_CHOICE:
4527                         d.layout_->showPopup();
4528                         break;
4529
4530                 case LFUN_MENU_OPEN:
4531                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4532                                 menu->exec(QCursor::pos());
4533                         break;
4534
4535                 case LFUN_FILE_INSERT: {
4536                         bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4537                         if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4538                                 dr.forceBufferUpdate();
4539                                 dr.screenUpdate(Update::Force);
4540                         }
4541                         break;
4542                 }
4543
4544                 case LFUN_FILE_INSERT_PLAINTEXT:
4545                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4546                         string const fname = to_utf8(cmd.argument());
4547                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4548                                 dr.setMessage(_("Absolute filename expected."));
4549                                 break;
4550                         }
4551
4552                         FileName filename(fname);
4553                         if (fname.empty()) {
4554                                 FileDialog dlg(qt_("Select file to insert"));
4555
4556                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4557                                         QStringList(qt_("All Files (*)")));
4558
4559                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4560                                         dr.setMessage(_("Canceled."));
4561                                         break;
4562                                 }
4563
4564                                 filename.set(fromqstr(result.second));
4565                         }
4566
4567                         if (bv) {
4568                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4569                                 bv->dispatch(new_cmd, dr);
4570                         }
4571                         break;
4572                 }
4573
4574                 case LFUN_BUFFER_RELOAD: {
4575                         LASSERT(doc_buffer, break);
4576
4577                         // drop changes?
4578                         bool drop = (cmd.argument() == "dump");
4579
4580                         int ret = 0;
4581                         if (!drop && !doc_buffer->isClean()) {
4582                                 docstring const file =
4583                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4584                                 if (doc_buffer->notifiesExternalModification()) {
4585                                         docstring text = _("The current version will be lost. "
4586                                             "Are you sure you want to load the version on disk "
4587                                             "of the document %1$s?");
4588                                         ret = Alert::prompt(_("Reload saved document?"),
4589                                                             bformat(text, file), 1, 1,
4590                                                             _("&Reload"), _("&Cancel"));
4591                                 } else {
4592                                         docstring text = _("Any changes will be lost. "
4593                                             "Are you sure you want to revert to the saved version "
4594                                             "of the document %1$s?");
4595                                         ret = Alert::prompt(_("Revert to saved document?"),
4596                                                             bformat(text, file), 1, 1,
4597                                                             _("&Revert"), _("&Cancel"));
4598                                 }
4599                         }
4600
4601                         if (ret == 0) {
4602                                 doc_buffer->markClean();
4603                                 reloadBuffer(*doc_buffer);
4604                                 dr.forceBufferUpdate();
4605                         }
4606                         break;
4607                 }
4608
4609                 case LFUN_BUFFER_RESET_EXPORT:
4610                         LASSERT(doc_buffer, break);
4611                         doc_buffer->requireFreshStart(true);
4612                         dr.setMessage(_("Buffer export reset."));
4613                         break;
4614
4615                 case LFUN_BUFFER_WRITE:
4616                         LASSERT(doc_buffer, break);
4617                         saveBuffer(*doc_buffer);
4618                         break;
4619
4620                 case LFUN_BUFFER_WRITE_AS:
4621                         LASSERT(doc_buffer, break);
4622                         renameBuffer(*doc_buffer, cmd.argument());
4623                         break;
4624
4625                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4626                         LASSERT(doc_buffer, break);
4627                         renameBuffer(*doc_buffer, cmd.argument(),
4628                                      LV_WRITE_AS_TEMPLATE);
4629                         break;
4630
4631                 case LFUN_BUFFER_WRITE_ALL: {
4632                         Buffer * first = theBufferList().first();
4633                         if (!first)
4634                                 break;
4635                         message(_("Saving all documents..."));
4636                         // We cannot use a for loop as the buffer list cycles.
4637                         Buffer * b = first;
4638                         do {
4639                                 if (!b->isClean()) {
4640                                         saveBuffer(*b);
4641                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4642                                 }
4643                                 b = theBufferList().next(b);
4644                         } while (b != first);
4645                         dr.setMessage(_("All documents saved."));
4646                         break;
4647                 }
4648
4649                 case LFUN_MASTER_BUFFER_FORALL: {
4650                         if (!doc_buffer)
4651                                 break;
4652
4653                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4654                         funcToRun.allowAsync(false);
4655
4656                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4657                                 // Switch to other buffer view and resend cmd
4658                                 lyx::dispatch(FuncRequest(
4659                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4660                                 lyx::dispatch(funcToRun);
4661                         }
4662                         // switch back
4663                         lyx::dispatch(FuncRequest(
4664                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4665                         break;
4666                 }
4667
4668                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4669                         LASSERT(doc_buffer, break);
4670                         doc_buffer->clearExternalModification();
4671                         break;
4672
4673                 case LFUN_BUFFER_CLOSE:
4674                         closeBuffer();
4675                         break;
4676
4677                 case LFUN_BUFFER_CLOSE_ALL:
4678                         closeBufferAll();
4679                         break;
4680
4681                 case LFUN_DEVEL_MODE_TOGGLE:
4682                         devel_mode_ = !devel_mode_;
4683                         if (devel_mode_)
4684                                 dr.setMessage(_("Developer mode is now enabled."));
4685                         else
4686                                 dr.setMessage(_("Developer mode is now disabled."));
4687                         break;
4688
4689                 case LFUN_TOOLBAR_SET: {
4690                         string const name = cmd.getArg(0);
4691                         string const state = cmd.getArg(1);
4692                         if (GuiToolbar * t = toolbar(name))
4693                                 t->setState(state);
4694                         break;
4695                 }
4696
4697                 case LFUN_TOOLBAR_TOGGLE: {
4698                         string const name = cmd.getArg(0);
4699                         if (GuiToolbar * t = toolbar(name))
4700                                 t->toggle();
4701                         break;
4702                 }
4703
4704                 case LFUN_TOOLBAR_MOVABLE: {
4705                         string const name = cmd.getArg(0);
4706                         if (name == "*") {
4707                                 // toggle (all) toolbars movablility
4708                                 toolbarsMovable_ = !toolbarsMovable_;
4709                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4710                                         GuiToolbar * tb = toolbar(ti.name);
4711                                         if (tb && tb->isMovable() != toolbarsMovable_)
4712                                                 // toggle toolbar movablity if it does not fit lock
4713                                                 // (all) toolbars positions state silent = true, since
4714                                                 // status bar notifications are slow
4715                                                 tb->movable(true);
4716                                 }
4717                                 if (toolbarsMovable_)
4718                                         dr.setMessage(_("Toolbars unlocked."));
4719                                 else
4720                                         dr.setMessage(_("Toolbars locked."));
4721                         } else if (GuiToolbar * tb = toolbar(name))
4722                                 // toggle current toolbar movablity
4723                                 tb->movable();
4724                         // update lock (all) toolbars positions
4725                         updateLockToolbars();
4726                         break;
4727                 }
4728
4729                 case LFUN_ICON_SIZE: {
4730                         QSize size = d.iconSize(cmd.argument());
4731                         setIconSize(size);
4732                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4733                                                 size.width(), size.height()));
4734                         break;
4735                 }
4736
4737                 case LFUN_DIALOG_UPDATE: {
4738                         string const name = to_utf8(cmd.argument());
4739                         if (name == "prefs" || name == "document")
4740                                 updateDialog(name, string());
4741                         else if (name == "paragraph")
4742                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4743                         else if (currentBufferView()) {
4744                                 Inset * inset = currentBufferView()->editedInset(name);
4745                                 // Can only update a dialog connected to an existing inset
4746                                 if (inset) {
4747                                         // FIXME: get rid of this indirection; GuiView ask the inset
4748                                         // if he is kind enough to update itself...
4749                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4750                                         //FIXME: pass DispatchResult here?
4751                                         inset->dispatch(currentBufferView()->cursor(), fr);
4752                                 }
4753                         }
4754                         break;
4755                 }
4756
4757                 case LFUN_DIALOG_TOGGLE: {
4758                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4759                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4760                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4761                         break;
4762                 }
4763
4764                 case LFUN_DIALOG_DISCONNECT_INSET:
4765                         disconnectDialog(to_utf8(cmd.argument()));
4766                         break;
4767
4768                 case LFUN_DIALOG_HIDE: {
4769                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4770                         break;
4771                 }
4772
4773                 case LFUN_DIALOG_SHOW: {
4774                         string const name = cmd.getArg(0);
4775                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4776
4777                         if (name == "latexlog") {
4778                                 // getStatus checks that
4779                                 LASSERT(doc_buffer, break);
4780                                 Buffer::LogType type;
4781                                 string const logfile = doc_buffer->logName(&type);
4782                                 switch (type) {
4783                                 case Buffer::latexlog:
4784                                         sdata = "latex ";
4785                                         break;
4786                                 case Buffer::buildlog:
4787                                         sdata = "literate ";
4788                                         break;
4789                                 }
4790                                 sdata += Lexer::quoteString(logfile);
4791                                 showDialog("log", sdata);
4792                         } else if (name == "vclog") {
4793                                 // getStatus checks that
4794                                 LASSERT(doc_buffer, break);
4795                                 string const sdata2 = "vc " +
4796                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4797                                 showDialog("log", sdata2);
4798                         } else if (name == "symbols") {
4799                                 sdata = bv->cursor().getEncoding()->name();
4800                                 if (!sdata.empty())
4801                                         showDialog("symbols", sdata);
4802                         } else if (name == "findreplace") {
4803                                 sdata = to_utf8(bv->cursor().selectionAsString(false));
4804                                 showDialog(name, sdata);
4805                         // bug 5274
4806                         } else if (name == "prefs" && isFullScreen()) {
4807                                 lfunUiToggle("fullscreen");
4808                                 showDialog("prefs", sdata);
4809                         } else
4810                                 showDialog(name, sdata);
4811                         break;
4812                 }
4813
4814                 case LFUN_MESSAGE:
4815                         dr.setMessage(cmd.argument());
4816                         break;
4817
4818                 case LFUN_UI_TOGGLE: {
4819                         string arg = cmd.getArg(0);
4820                         if (!lfunUiToggle(arg)) {
4821                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4822                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4823                         }
4824                         // Make sure the keyboard focus stays in the work area.
4825                         setFocus();
4826                         break;
4827                 }
4828
4829                 case LFUN_VIEW_SPLIT: {
4830                         LASSERT(doc_buffer, break);
4831                         string const orientation = cmd.getArg(0);
4832                         d.splitter_->setOrientation(orientation == "vertical"
4833                                 ? Qt::Vertical : Qt::Horizontal);
4834                         TabWorkArea * twa = addTabWorkArea();
4835                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4836                         setCurrentWorkArea(wa);
4837                         break;
4838                 }
4839
4840                 case LFUN_TAB_GROUP_NEXT:
4841                         gotoNextTabWorkArea(NEXT);
4842                         break;
4843
4844                 case LFUN_TAB_GROUP_PREVIOUS:
4845                         gotoNextTabWorkArea(PREV);
4846                         break;
4847
4848                 case LFUN_TAB_GROUP_CLOSE:
4849                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4850                                 closeTabWorkArea(twa);
4851                                 d.current_work_area_ = nullptr;
4852                                 twa = d.currentTabWorkArea();
4853                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4854                                 if (twa) {
4855                                         // Make sure the work area is up to date.
4856                                         setCurrentWorkArea(twa->currentWorkArea());
4857                                 } else {
4858                                         setCurrentWorkArea(nullptr);
4859                                 }
4860                         }
4861                         break;
4862
4863                 case LFUN_VIEW_CLOSE:
4864                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4865                                 closeWorkArea(twa->currentWorkArea());
4866                                 d.current_work_area_ = nullptr;
4867                                 twa = d.currentTabWorkArea();
4868                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4869                                 if (twa) {
4870                                         // Make sure the work area is up to date.
4871                                         setCurrentWorkArea(twa->currentWorkArea());
4872                                 } else {
4873                                         setCurrentWorkArea(nullptr);
4874                                 }
4875                         }
4876                         break;
4877
4878                 case LFUN_COMPLETION_INLINE:
4879                         if (d.current_work_area_)
4880                                 d.current_work_area_->completer().showInline();
4881                         break;
4882
4883                 case LFUN_COMPLETION_POPUP:
4884                         if (d.current_work_area_)
4885                                 d.current_work_area_->completer().showPopup();
4886                         break;
4887
4888
4889                 case LFUN_COMPLETE:
4890                         if (d.current_work_area_)
4891                                 d.current_work_area_->completer().tab();
4892                         break;
4893
4894                 case LFUN_COMPLETION_CANCEL:
4895                         if (d.current_work_area_) {
4896                                 if (d.current_work_area_->completer().popupVisible())
4897                                         d.current_work_area_->completer().hidePopup();
4898                                 else
4899                                         d.current_work_area_->completer().hideInline();
4900                         }
4901                         break;
4902
4903                 case LFUN_COMPLETION_ACCEPT:
4904                         if (d.current_work_area_)
4905                                 d.current_work_area_->completer().activate();
4906                         break;
4907
4908                 case LFUN_BUFFER_ZOOM_IN:
4909                 case LFUN_BUFFER_ZOOM_OUT:
4910                 case LFUN_BUFFER_ZOOM: {
4911                         zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4912
4913                         // Actual zoom value: default zoom + fractional extra value
4914                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4915                         zoom = min(max(zoom, zoom_min_), zoom_max_);
4916
4917                         setCurrentZoom(zoom);
4918
4919                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4920                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4921
4922                         guiApp->fontLoader().update();
4923                         // Regenerate instant previews
4924                         if (lyxrc.preview != LyXRC::PREVIEW_OFF
4925                             && doc_buffer && doc_buffer->loader())
4926                                 doc_buffer->loader()->refreshPreviews();
4927                         dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4928                         break;
4929                 }
4930
4931                 case LFUN_VC_REGISTER:
4932                 case LFUN_VC_RENAME:
4933                 case LFUN_VC_COPY:
4934                 case LFUN_VC_CHECK_IN:
4935                 case LFUN_VC_CHECK_OUT:
4936                 case LFUN_VC_REPO_UPDATE:
4937                 case LFUN_VC_LOCKING_TOGGLE:
4938                 case LFUN_VC_REVERT:
4939                 case LFUN_VC_UNDO_LAST:
4940                 case LFUN_VC_COMMAND:
4941                 case LFUN_VC_COMPARE:
4942                         dispatchVC(cmd, dr);
4943                         break;
4944
4945                 case LFUN_SERVER_GOTO_FILE_ROW:
4946                         if(goToFileRow(to_utf8(cmd.argument())))
4947                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4948                         break;
4949
4950                 case LFUN_LYX_ACTIVATE:
4951                         activateWindow();
4952                         break;
4953
4954                 case LFUN_WINDOW_RAISE:
4955                         raise();
4956                         activateWindow();
4957                         showNormal();
4958                         break;
4959
4960                 case LFUN_FORWARD_SEARCH: {
4961                         // it seems safe to assume we have a document buffer, since
4962                         // getStatus wants one.
4963                         LASSERT(doc_buffer, break);
4964                         Buffer const * doc_master = doc_buffer->masterBuffer();
4965                         FileName const path(doc_master->temppath());
4966                         string const texname = doc_master->isChild(doc_buffer)
4967                                 ? DocFileName(changeExtension(
4968                                         doc_buffer->absFileName(),
4969                                                 "tex")).mangledFileName()
4970                                 : doc_buffer->latexName();
4971                         string const fulltexname =
4972                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4973                         string const mastername =
4974                                 removeExtension(doc_master->latexName());
4975                         FileName const dviname(addName(path.absFileName(),
4976                                         addExtension(mastername, "dvi")));
4977                         FileName const pdfname(addName(path.absFileName(),
4978                                         addExtension(mastername, "pdf")));
4979                         bool const have_dvi = dviname.exists();
4980                         bool const have_pdf = pdfname.exists();
4981                         if (!have_dvi && !have_pdf) {
4982                                 dr.setMessage(_("Please, preview the document first."));
4983                                 break;
4984                         }
4985                         bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4986                         bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4987                         string outname = dviname.onlyFileName();
4988                         string command = lyxrc.forward_search_dvi;
4989                         if ((!goto_dvi || goto_pdf) &&
4990                             pdfname.lastModified() > dviname.lastModified()) {
4991                                 outname = pdfname.onlyFileName();
4992                                 command = lyxrc.forward_search_pdf;
4993                         }
4994
4995                         DocIterator cur = bv->cursor();
4996                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4997                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4998                                    << " cur:" << cur);
4999                         if (row == -1 || command.empty()) {
5000                                 dr.setMessage(_("Couldn't proceed."));
5001                                 break;
5002                         }
5003                         string texrow = convert<string>(row);
5004
5005                         command = subst(command, "$$n", texrow);
5006                         command = subst(command, "$$f", fulltexname);
5007                         command = subst(command, "$$t", texname);
5008                         command = subst(command, "$$o", outname);
5009
5010                         volatile PathChanger p(path);
5011                         Systemcall one;
5012                         one.startscript(Systemcall::DontWait, command);
5013                         break;
5014                 }
5015
5016                 case LFUN_SPELLING_CONTINUOUSLY:
5017                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5018                         dr.screenUpdate(Update::Force);
5019                         break;
5020
5021                 case LFUN_CITATION_OPEN: {
5022                         string pdfv, psv;
5023                         if (theFormats().getFormat("pdf"))
5024                                 pdfv = theFormats().getFormat("pdf")->viewer();
5025                         if (theFormats().getFormat("ps"))
5026                                 psv = theFormats().getFormat("ps")->viewer();
5027                         frontend::showTarget(argument, pdfv, psv);
5028                         break;
5029                 }
5030
5031                 default:
5032                         // The LFUN must be for one of BufferView, Buffer or Cursor;
5033                         // let's try that:
5034                         dispatchToBufferView(cmd, dr);
5035                         break;
5036         }
5037
5038         // Need to update bv because many LFUNs here might have destroyed it
5039         bv = currentBufferView();
5040
5041         // Clear non-empty selections
5042         // (e.g. from a "char-forward-select" followed by "char-backward-select")
5043         if (bv) {
5044                 Cursor & cur = bv->cursor();
5045                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5046                         cur.clearSelection();
5047                 }
5048         }
5049 }
5050
5051
5052 bool GuiView::lfunUiToggle(string const & ui_component)
5053 {
5054         if (ui_component == "scrollbar") {
5055                 // hide() is of no help
5056                 if (d.current_work_area_->verticalScrollBarPolicy() ==
5057                         Qt::ScrollBarAlwaysOff)
5058
5059                         d.current_work_area_->setVerticalScrollBarPolicy(
5060                                 Qt::ScrollBarAsNeeded);
5061                 else
5062                         d.current_work_area_->setVerticalScrollBarPolicy(
5063                                 Qt::ScrollBarAlwaysOff);
5064         } else if (ui_component == "statusbar") {
5065                 statusBar()->setVisible(!statusBar()->isVisible());
5066         } else if (ui_component == "menubar") {
5067                 menuBar()->setVisible(!menuBar()->isVisible());
5068         } else if (ui_component == "zoomlevel") {
5069                 zoom_value_->setVisible(!zoom_value_->isVisible());
5070         } else if (ui_component == "zoomslider") {
5071                 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5072                 zoom_in_->setVisible(zoom_slider_->isVisible());
5073                 zoom_out_->setVisible(zoom_slider_->isVisible());
5074         } else if (ui_component == "statistics-w") {
5075                 word_count_enabled_ = !word_count_enabled_;
5076                 if (statsEnabled())
5077                         showStats();
5078         } else if (ui_component == "statistics-cb") {
5079                 char_count_enabled_ = !char_count_enabled_;
5080                 if (statsEnabled())
5081                         showStats();
5082         } else if (ui_component == "statistics-c") {
5083                 char_nb_count_enabled_ = !char_nb_count_enabled_;
5084                 if (statsEnabled())
5085                         showStats();
5086         } else if (ui_component == "frame") {
5087                 int const l = contentsMargins().left();
5088
5089                 //are the frames in default state?
5090                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5091                 if (l == 0) {
5092 #if QT_VERSION >  0x050903
5093                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5094 #endif
5095                         setContentsMargins(-2, -2, -2, -2);
5096                 } else {
5097 #if QT_VERSION >  0x050903
5098                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5099 #endif
5100                         setContentsMargins(0, 0, 0, 0);
5101                 }
5102         } else
5103         if (ui_component == "fullscreen") {
5104                 toggleFullScreen();
5105         } else
5106                 return false;
5107         stat_counts_->setVisible(statsEnabled());
5108         return true;
5109 }
5110
5111
5112 void GuiView::cancelExport()
5113 {
5114         Systemcall::killscript();
5115         // stop busy signal immediately so that in the subsequent
5116         // "Export canceled" prompt the status bar icons are accurate.
5117         Q_EMIT scriptKilled();
5118 }
5119
5120
5121 void GuiView::toggleFullScreen()
5122 {
5123         setWindowState(windowState() ^ Qt::WindowFullScreen);
5124 }
5125
5126
5127 Buffer const * GuiView::updateInset(Inset const * inset)
5128 {
5129         if (!inset)
5130                 return nullptr;
5131
5132         Buffer const * inset_buffer = &(inset->buffer());
5133
5134         for (int i = 0; i != d.splitter_->count(); ++i) {
5135                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5136                 if (!wa)
5137                         continue;
5138                 Buffer const * buffer = &(wa->bufferView().buffer());
5139                 if (inset_buffer == buffer)
5140                         wa->scheduleRedraw(true);
5141         }
5142         return inset_buffer;
5143 }
5144
5145
5146 void GuiView::restartCaret()
5147 {
5148         /* When we move around, or type, it's nice to be able to see
5149          * the caret immediately after the keypress.
5150          */
5151         if (d.current_work_area_)
5152                 d.current_work_area_->startBlinkingCaret();
5153
5154         // Take this occasion to update the other GUI elements.
5155         updateDialogs();
5156         updateStatusBar();
5157 }
5158
5159
5160 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5161 {
5162         if (d.current_work_area_)
5163                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5164 }
5165
5166 namespace {
5167
5168 // This list should be kept in sync with the list of insets in
5169 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
5170 // dialog should have the same name as the inset.
5171 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5172 // docs in LyXAction.cpp.
5173
5174 char const * const dialognames[] = {
5175
5176 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5177 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5178 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5179 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5180 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5181 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5182 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5183 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5184
5185 char const * const * const end_dialognames =
5186         dialognames + (sizeof(dialognames) / sizeof(char *));
5187
5188 class cmpCStr {
5189 public:
5190         cmpCStr(char const * name) : name_(name) {}
5191         bool operator()(char const * other) {
5192                 return strcmp(other, name_) == 0;
5193         }
5194 private:
5195         char const * name_;
5196 };
5197
5198
5199 bool isValidName(string const & name)
5200 {
5201         return find_if(dialognames, end_dialognames,
5202                 cmpCStr(name.c_str())) != end_dialognames;
5203 }
5204
5205 } // namespace
5206
5207
5208 void GuiView::resetDialogs()
5209 {
5210         // Make sure that no LFUN uses any GuiView.
5211         guiApp->setCurrentView(nullptr);
5212         saveLayout();
5213         saveUISettings();
5214         menuBar()->clear();
5215         constructToolbars();
5216         guiApp->menus().fillMenuBar(menuBar(), this, false);
5217         d.layout_->updateContents(true);
5218         // Now update controls with current buffer.
5219         guiApp->setCurrentView(this);
5220         restoreLayout();
5221         restartCaret();
5222 }
5223
5224
5225 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5226 {
5227         for (QObject * child: widget->children()) {
5228                 if (child->inherits("QGroupBox")) {
5229                         QGroupBox * box = (QGroupBox*) child;
5230                         box->setFlat(flag);
5231                 } else {
5232                         flatGroupBoxes(child, flag);
5233                 }
5234         }
5235 }
5236
5237
5238 Dialog * GuiView::find(string const & name, bool hide_it) const
5239 {
5240         if (!isValidName(name))
5241                 return nullptr;
5242
5243         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5244
5245         if (it != d.dialogs_.end()) {
5246                 if (hide_it)
5247                         it->second->hideView();
5248                 return it->second.get();
5249         }
5250         return nullptr;
5251 }
5252
5253
5254 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5255 {
5256         Dialog * dialog = find(name, hide_it);
5257         if (dialog != nullptr)
5258                 return dialog;
5259
5260         dialog = build(name);
5261         d.dialogs_[name].reset(dialog);
5262         // Force a uniform style for group boxes
5263         // On Mac non-flat works better, on Linux flat is standard
5264         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5265         if (lyxrc.allow_geometry_session)
5266                 dialog->restoreSession();
5267         if (hide_it)
5268                 dialog->hideView();
5269         return dialog;
5270 }
5271
5272
5273 void GuiView::showDialog(string const & name, string const & sdata,
5274         Inset * inset)
5275 {
5276         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5277 }
5278
5279
5280 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5281         Inset * inset)
5282 {
5283         if (d.in_show_)
5284                 return;
5285
5286         const string name = fromqstr(qname);
5287         const string sdata = fromqstr(qdata);
5288
5289         d.in_show_ = true;
5290         try {
5291                 Dialog * dialog = findOrBuild(name, false);
5292                 if (dialog) {
5293                         bool const visible = dialog->isVisibleView();
5294                         dialog->showData(sdata);
5295                         if (currentBufferView())
5296                                 currentBufferView()->editInset(name, inset);
5297                         // We only set the focus to the new dialog if it was not yet
5298                         // visible in order not to change the existing previous behaviour
5299                         if (visible) {
5300                                 // activateWindow is needed for floating dockviews
5301                                 dialog->asQWidget()->raise();
5302                                 dialog->asQWidget()->activateWindow();
5303                                 if (dialog->wantInitialFocus())
5304                                         dialog->asQWidget()->setFocus();
5305                         }
5306                 }
5307         }
5308         catch (ExceptionMessage const &) {
5309                 d.in_show_ = false;
5310                 throw;
5311         }
5312         d.in_show_ = false;
5313 }
5314
5315
5316 bool GuiView::isDialogVisible(string const & name) const
5317 {
5318         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5319         if (it == d.dialogs_.end())
5320                 return false;
5321         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5322 }
5323
5324
5325 void GuiView::hideDialog(string const & name, Inset * inset)
5326 {
5327         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5328         if (it == d.dialogs_.end())
5329                 return;
5330
5331         if (inset) {
5332                 if (!currentBufferView())
5333                         return;
5334                 if (inset != currentBufferView()->editedInset(name))
5335                         return;
5336         }
5337
5338         Dialog * const dialog = it->second.get();
5339         if (dialog->isVisibleView())
5340                 dialog->hideView();
5341         if (currentBufferView())
5342                 currentBufferView()->editInset(name, nullptr);
5343 }
5344
5345
5346 void GuiView::disconnectDialog(string const & name)
5347 {
5348         if (!isValidName(name))
5349                 return;
5350         if (currentBufferView())
5351                 currentBufferView()->editInset(name, nullptr);
5352 }
5353
5354
5355 void GuiView::hideAll() const
5356 {
5357         for(auto const & dlg_p : d.dialogs_)
5358                 dlg_p.second->hideView();
5359 }
5360
5361
5362 void GuiView::updateDialogs()
5363 {
5364         for(auto const & dlg_p : d.dialogs_) {
5365                 Dialog * dialog = dlg_p.second.get();
5366                 if (dialog) {
5367                         if (dialog->needBufferOpen() && !documentBufferView())
5368                                 hideDialog(fromqstr(dialog->name()), nullptr);
5369                         else if (dialog->isVisibleView())
5370                                 dialog->checkStatus();
5371                 }
5372         }
5373         updateToolbars();
5374         updateLayoutList();
5375 }
5376
5377
5378 Dialog * GuiView::build(string const & name)
5379 {
5380         return createDialog(*this, name);
5381 }
5382
5383
5384 SEMenu::SEMenu(QWidget * parent)
5385 {
5386         QAction * action = addAction(qt_("Disable Shell Escape"));
5387         connect(action, SIGNAL(triggered()),
5388                 parent, SLOT(disableShellEscape()));
5389 }
5390
5391
5392 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5393 {
5394         if (event->button() == Qt::LeftButton) {
5395         Q_EMIT pressed();
5396     }
5397 }
5398
5399 } // namespace frontend
5400 } // namespace lyx
5401
5402 #include "moc_GuiView.cpp"