]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiView.cpp
Try to be more precise in the hyperlink type tooltips
[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::openDocument(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         string filename;
2807
2808         if (fname.empty()) {
2809                 FileDialog dlg(qt_("Select document 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::Result result =
2819                         dlg.open(toqstr(initpath), filter);
2820
2821                 if (result.first == FileDialog::Later)
2822                         return;
2823
2824                 filename = fromqstr(result.second);
2825
2826                 // check selected filename
2827                 if (filename.empty()) {
2828                         message(_("Canceled."));
2829                         return;
2830                 }
2831         } else
2832                 filename = fname;
2833
2834         // get absolute path of file and add ".lyx" to the filename if
2835         // necessary.
2836         FileName const fullname =
2837                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2838         if (!fullname.empty())
2839                 filename = fullname.absFileName();
2840
2841         if (!fullname.onlyPath().isDirectory()) {
2842                 Alert::warning(_("Invalid filename"),
2843                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2844                                 from_utf8(fullname.absFileName())));
2845                 return;
2846         }
2847
2848         // if the file doesn't exist and isn't already open (bug 6645),
2849         // let the user create one
2850         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2851             !LyXVC::file_not_found_hook(fullname)) {
2852                 // see bug #12609
2853                 if (origin == FuncRequest::MENU) {
2854                         docstring const & msg =
2855                                 bformat(_("File\n"
2856                                           "%1$s\n"
2857                                           "does not exist. Create empty file?"),
2858                                                 from_utf8(filename));
2859                         int ret = Alert::prompt(_("File does not exist"),
2860                                                 msg, 0, 1,
2861                                                 _("Create &File"),
2862                                                 _("&Cancel"));
2863                         if (ret == 1)
2864                                 return;
2865                 }
2866                 Buffer * const b = newFile(filename, string(), true);
2867                 if (b)
2868                         setBuffer(b);
2869                 return;
2870         }
2871
2872         docstring const disp_fn = makeDisplayPath(filename);
2873         message(bformat(_("Opening document %1$s..."), disp_fn));
2874
2875         docstring str2;
2876         Buffer * buf = loadDocument(fullname);
2877         if (buf) {
2878                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2879                 if (buf->lyxvc().inUse())
2880                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2881                                 " " + _("Version control detected.");
2882         } else {
2883                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2884         }
2885         message(str2);
2886 }
2887
2888 // FIXME: clean that
2889 static bool import(GuiView * lv, FileName const & filename,
2890         string const & format, ErrorList & errorList)
2891 {
2892         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2893
2894         string loader_format;
2895         vector<string> loaders = theConverters().loaders();
2896         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2897                 for (string const & loader : loaders) {
2898                         if (!theConverters().isReachable(format, loader))
2899                                 continue;
2900
2901                         string const tofile =
2902                                 support::changeExtension(filename.absFileName(),
2903                                 theFormats().extension(loader));
2904                         if (theConverters().convert(nullptr, filename, FileName(tofile),
2905                                 filename, format, loader, errorList) != Converters::SUCCESS)
2906                                 return false;
2907                         loader_format = loader;
2908                         break;
2909                 }
2910                 if (loader_format.empty()) {
2911                         frontend::Alert::error(_("Couldn't import file"),
2912                                          bformat(_("No information for importing the format %1$s."),
2913                                          translateIfPossible(theFormats().prettyName(format))));
2914                         return false;
2915                 }
2916         } else
2917                 loader_format = format;
2918
2919         if (loader_format == "lyx") {
2920                 Buffer * buf = lv->loadDocument(lyxfile);
2921                 if (!buf)
2922                         return false;
2923         } else {
2924                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2925                 if (!b)
2926                         return false;
2927                 lv->setBuffer(b);
2928                 bool as_paragraphs = loader_format == "textparagraph";
2929                 string filename2 = (loader_format == format) ? filename.absFileName()
2930                         : support::changeExtension(filename.absFileName(),
2931                                           theFormats().extension(loader_format));
2932                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2933                         as_paragraphs);
2934                 guiApp->setCurrentView(lv);
2935                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2936         }
2937
2938         return true;
2939 }
2940
2941
2942 void GuiView::importDocument(string const & argument)
2943 {
2944         string format;
2945         string filename = split(argument, format, ' ');
2946
2947         LYXERR(Debug::INFO, format << " file: " << filename);
2948
2949         // need user interaction
2950         if (filename.empty()) {
2951                 string initpath = lyxrc.document_path;
2952                 if (documentBufferView()) {
2953                         string const trypath = documentBufferView()->buffer().filePath();
2954                         // If directory is writeable, use this as default.
2955                         if (FileName(trypath).isDirWritable())
2956                                 initpath = trypath;
2957                 }
2958
2959                 docstring const text = bformat(_("Select %1$s file to import"),
2960                         translateIfPossible(theFormats().prettyName(format)));
2961
2962                 FileDialog dlg(toqstr(text));
2963                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2964                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2965
2966                 docstring filter = translateIfPossible(theFormats().prettyName(format));
2967                 filter += " (*.{";
2968                 // FIXME UNICODE
2969                 filter += from_utf8(theFormats().extensions(format));
2970                 filter += "})";
2971
2972                 FileDialog::Result result =
2973                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2974
2975                 if (result.first == FileDialog::Later)
2976                         return;
2977
2978                 filename = fromqstr(result.second);
2979
2980                 // check selected filename
2981                 if (filename.empty())
2982                         message(_("Canceled."));
2983         }
2984
2985         if (filename.empty())
2986                 return;
2987
2988         // get absolute path of file
2989         FileName const fullname(support::makeAbsPath(filename));
2990
2991         // Can happen if the user entered a path into the dialog
2992         // (see bug #7437)
2993         if (fullname.onlyFileName().empty()) {
2994                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2995                                           "Aborting import."),
2996                                         from_utf8(fullname.absFileName()));
2997                 frontend::Alert::error(_("File name error"), msg);
2998                 message(_("Canceled."));
2999                 return;
3000         }
3001
3002
3003         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3004
3005         // Check if the document already is open
3006         Buffer * buf = theBufferList().getBuffer(lyxfile);
3007         if (buf) {
3008                 setBuffer(buf);
3009                 if (!closeBuffer()) {
3010                         message(_("Canceled."));
3011                         return;
3012                 }
3013         }
3014
3015         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3016
3017         // if the file exists already, and we didn't do
3018         // -i lyx thefile.lyx, warn
3019         if (lyxfile.exists() && fullname != lyxfile) {
3020
3021                 docstring text = bformat(_("The document %1$s already exists.\n\n"
3022                         "Do you want to overwrite that document?"), displaypath);
3023                 int const ret = Alert::prompt(_("Overwrite document?"),
3024                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
3025
3026                 if (ret == 1) {
3027                         message(_("Canceled."));
3028                         return;
3029                 }
3030         }
3031
3032         message(bformat(_("Importing %1$s..."), displaypath));
3033         ErrorList errorList;
3034         if (import(this, fullname, format, errorList))
3035                 message(_("imported."));
3036         else
3037                 message(_("file not imported!"));
3038
3039         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3040 }
3041
3042
3043 void GuiView::newDocument(string const & filename, string templatefile,
3044                           bool from_template)
3045 {
3046         FileName initpath(lyxrc.document_path);
3047         if (documentBufferView()) {
3048                 FileName const trypath(documentBufferView()->buffer().filePath());
3049                 // If directory is writeable, use this as default.
3050                 if (trypath.isDirWritable())
3051                         initpath = trypath;
3052         }
3053
3054         if (from_template) {
3055                 if (templatefile.empty())
3056                         templatefile =  selectTemplateFile().absFileName();
3057                 if (templatefile.empty())
3058                         return;
3059         }
3060
3061         Buffer * b;
3062         if (filename.empty())
3063                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3064         else
3065                 b = newFile(filename, templatefile, true);
3066
3067         if (b)
3068                 setBuffer(b);
3069
3070         // If no new document could be created, it is unsure
3071         // whether there is a valid BufferView.
3072         if (currentBufferView())
3073                 // Ensure the cursor is correctly positioned on screen.
3074                 currentBufferView()->showCursor();
3075 }
3076
3077
3078 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3079 {
3080         BufferView * bv = documentBufferView();
3081         if (!bv)
3082                 return false;
3083
3084         // FIXME UNICODE
3085         FileName filename(to_utf8(fname));
3086         if (filename.empty()) {
3087                 // Launch a file browser
3088                 // FIXME UNICODE
3089                 string initpath = lyxrc.document_path;
3090                 string const trypath = bv->buffer().filePath();
3091                 // If directory is writeable, use this as default.
3092                 if (FileName(trypath).isDirWritable())
3093                         initpath = trypath;
3094
3095                 // FIXME UNICODE
3096                 FileDialog dlg(qt_("Select LyX document to insert"));
3097                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3098                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3099
3100                 FileDialog::Result result = dlg.open(toqstr(initpath),
3101                                          QStringList(qt_("LyX Documents (*.lyx)")));
3102
3103                 if (result.first == FileDialog::Later)
3104                         return false;
3105
3106                 // FIXME UNICODE
3107                 filename.set(fromqstr(result.second));
3108
3109                 // check selected filename
3110                 if (filename.empty()) {
3111                         // emit message signal.
3112                         message(_("Canceled."));
3113                         return false;
3114                 }
3115         }
3116
3117         bv->insertLyXFile(filename, ignorelang);
3118         bv->buffer().errors("Parse");
3119         return true;
3120 }
3121
3122
3123 string const GuiView::getTemplatesPath(Buffer & b)
3124 {
3125         // We start off with the user's templates path
3126         string result = addPath(package().user_support().absFileName(), "templates");
3127         // Check for the document language
3128         string const langcode = b.params().language->code();
3129         string const shortcode = langcode.substr(0, 2);
3130         if (!langcode.empty() && shortcode != "en") {
3131                 string subpath = addPath(result, shortcode);
3132                 string subpath_long = addPath(result, langcode);
3133                 // If we have a subdirectory for the language already,
3134                 // navigate there
3135                 FileName sp = FileName(subpath);
3136                 if (sp.isDirectory())
3137                         result = subpath;
3138                 else if (FileName(subpath_long).isDirectory())
3139                         result = subpath_long;
3140                 else {
3141                         // Ask whether we should create such a subdirectory
3142                         docstring const text =
3143                                 bformat(_("It is suggested to save the template in a subdirectory\n"
3144                                           "appropriate to the document language (%1$s).\n"
3145                                           "This subdirectory does not exists yet.\n"
3146                                           "Do you want to create it?"),
3147                                         _(b.params().language->display()));
3148                         if (Alert::prompt(_("Create Language Directory?"),
3149                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3150                                 // If the user agreed, we try to create it and report if this failed.
3151                                 if (!sp.createDirectory(0777))
3152                                         Alert::error(_("Subdirectory creation failed!"),
3153                                                      _("Could not create subdirectory.\n"
3154                                                        "The template will be saved in the parent directory."));
3155                                 else
3156                                         result = subpath;
3157                         }
3158                 }
3159         }
3160         // Do we have a layout category?
3161         string const cat = b.params().baseClass() ?
3162                                 b.params().baseClass()->category()
3163                               : string();
3164         if (!cat.empty()) {
3165                 string subpath = addPath(result, cat);
3166                 // If we have a subdirectory for the category already,
3167                 // navigate there
3168                 FileName sp = FileName(subpath);
3169                 if (sp.isDirectory())
3170                         result = subpath;
3171                 else {
3172                         // Ask whether we should create such a subdirectory
3173                         docstring const text =
3174                                 bformat(_("It is suggested to save the template in a subdirectory\n"
3175                                           "appropriate to the layout category (%1$s).\n"
3176                                           "This subdirectory does not exists yet.\n"
3177                                           "Do you want to create it?"),
3178                                         _(cat));
3179                         if (Alert::prompt(_("Create Category Directory?"),
3180                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3181                                 // If the user agreed, we try to create it and report if this failed.
3182                                 if (!sp.createDirectory(0777))
3183                                         Alert::error(_("Subdirectory creation failed!"),
3184                                                      _("Could not create subdirectory.\n"
3185                                                        "The template will be saved in the parent directory."));
3186                                 else
3187                                         result = subpath;
3188                         }
3189                 }
3190         }
3191         return result;
3192 }
3193
3194
3195 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3196 {
3197         FileName fname = b.fileName();
3198         FileName const oldname = fname;
3199         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3200
3201         if (!newname.empty()) {
3202                 // FIXME UNICODE
3203                 if (as_template)
3204                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3205                 else
3206                         fname = support::makeAbsPath(to_utf8(newname),
3207                                                      oldname.onlyPath().absFileName());
3208         } else {
3209                 // Switch to this Buffer.
3210                 setBuffer(&b);
3211
3212                 // No argument? Ask user through dialog.
3213                 // FIXME UNICODE
3214                 QString const title = as_template ? qt_("Choose a filename to save template as")
3215                                                   : qt_("Choose a filename to save document as");
3216                 FileDialog dlg(title);
3217                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3218                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3219
3220                 fname.ensureExtension(".lyx");
3221
3222                 string const path = as_template ?
3223                                         getTemplatesPath(b)
3224                                       : fname.onlyPath().absFileName();
3225                 FileDialog::Result result =
3226                         dlg.save(toqstr(path),
3227                                    QStringList(qt_("LyX Documents (*.lyx)")),
3228                                          toqstr(fname.onlyFileName()));
3229
3230                 if (result.first == FileDialog::Later)
3231                         return false;
3232
3233                 fname.set(fromqstr(result.second));
3234
3235                 if (fname.empty())
3236                         return false;
3237
3238                 fname.ensureExtension(".lyx");
3239         }
3240
3241         // fname is now the new Buffer location.
3242
3243         // if there is already a Buffer open with this name, we do not want
3244         // to have another one. (the second test makes sure we're not just
3245         // trying to overwrite ourselves, which is fine.)
3246         if (theBufferList().exists(fname) && fname != oldname
3247                   && theBufferList().getBuffer(fname) != &b) {
3248                 docstring const text =
3249                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
3250                             "Please close it before attempting to overwrite it.\n"
3251                             "Do you want to choose a new filename?"),
3252                                 from_utf8(fname.absFileName()));
3253                 int const ret = Alert::prompt(_("Chosen File Already Open"),
3254                         text, 0, 1, _("&Rename"), _("&Cancel"));
3255                 switch (ret) {
3256                 case 0: return renameBuffer(b, docstring(), kind);
3257                 case 1: return false;
3258                 }
3259                 //return false;
3260         }
3261
3262         bool const existsLocal = fname.exists();
3263         bool const existsInVC = LyXVC::fileInVC(fname);
3264         if (existsLocal || existsInVC) {
3265                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3266                 if (kind != LV_WRITE_AS && existsInVC) {
3267                         // renaming to a name that is already in VC
3268                         // would not work
3269                         docstring text = bformat(_("The document %1$s "
3270                                         "is already registered.\n\n"
3271                                         "Do you want to choose a new name?"),
3272                                 file);
3273                         docstring const title = (kind == LV_VC_RENAME) ?
3274                                 _("Rename document?") : _("Copy document?");
3275                         docstring const button = (kind == LV_VC_RENAME) ?
3276                                 _("&Rename") : _("&Copy");
3277                         int const ret = Alert::prompt(title, text, 0, 1,
3278                                 button, _("&Cancel"));
3279                         switch (ret) {
3280                         case 0: return renameBuffer(b, docstring(), kind);
3281                         case 1: return false;
3282                         }
3283                 }
3284
3285                 if (existsLocal) {
3286                         docstring text = bformat(_("The document %1$s "
3287                                         "already exists.\n\n"
3288                                         "Do you want to overwrite that document?"),
3289                                 file);
3290                         int const ret = Alert::prompt(_("Overwrite document?"),
3291                                         text, 0, 2, _("&Overwrite"),
3292                                         _("&Rename"), _("&Cancel"));
3293                         switch (ret) {
3294                         case 0: break;
3295                         case 1: return renameBuffer(b, docstring(), kind);
3296                         case 2: return false;
3297                         }
3298                 }
3299         }
3300
3301         switch (kind) {
3302         case LV_VC_RENAME: {
3303                 string msg = b.lyxvc().rename(fname);
3304                 if (msg.empty())
3305                         return false;
3306                 message(from_utf8(msg));
3307                 break;
3308         }
3309         case LV_VC_COPY: {
3310                 string msg = b.lyxvc().copy(fname);
3311                 if (msg.empty())
3312                         return false;
3313                 message(from_utf8(msg));
3314                 break;
3315         }
3316         case LV_WRITE_AS:
3317         case LV_WRITE_AS_TEMPLATE:
3318                 break;
3319         }
3320         // LyXVC created the file already in case of LV_VC_RENAME or
3321         // LV_VC_COPY, but call saveBuffer() nevertheless to get
3322         // relative paths of included stuff right if we moved e.g. from
3323         // /a/b.lyx to /a/c/b.lyx.
3324
3325         bool const saved = saveBuffer(b, fname);
3326         if (saved)
3327                 b.reload();
3328         return saved;
3329 }
3330
3331
3332 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3333 {
3334         FileName fname = b.fileName();
3335
3336         FileDialog dlg(qt_("Choose a filename to export the document as"));
3337         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3338
3339         QStringList types;
3340         QString const anyformat = qt_("Guess from extension (*.*)");
3341         types << anyformat;
3342
3343         vector<Format const *> export_formats;
3344         for (Format const & f : theFormats())
3345                 if (f.documentFormat())
3346                         export_formats.push_back(&f);
3347         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3348         map<QString, string> fmap;
3349         QString filter;
3350         string ext;
3351         for (Format const * f : export_formats) {
3352                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3353                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3354                                                      loc_prettyname,
3355                                                      from_ascii(f->extension())));
3356                 types << loc_filter;
3357                 fmap[loc_filter] = f->name();
3358                 if (from_ascii(f->name()) == iformat) {
3359                         filter = loc_filter;
3360                         ext = f->extension();
3361                 }
3362         }
3363         string ofname = fname.onlyFileName();
3364         if (!ext.empty())
3365                 ofname = support::changeExtension(ofname, ext);
3366         FileDialog::Result result =
3367                 dlg.save(toqstr(fname.onlyPath().absFileName()),
3368                          types,
3369                          toqstr(ofname),
3370                          &filter);
3371         if (result.first != FileDialog::Chosen)
3372                 return false;
3373
3374         string fmt_name;
3375         fname.set(fromqstr(result.second));
3376         if (filter == anyformat)
3377                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3378         else
3379                 fmt_name = fmap[filter];
3380         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3381                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3382
3383         if (fmt_name.empty() || fname.empty())
3384                 return false;
3385
3386         fname.ensureExtension(theFormats().extension(fmt_name));
3387
3388         // fname is now the new Buffer location.
3389         if (fname.exists()) {
3390                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3391                 docstring text = bformat(_("The document %1$s already "
3392                                            "exists.\n\nDo you want to "
3393                                            "overwrite that document?"),
3394                                          file);
3395                 int const ret = Alert::prompt(_("Overwrite document?"),
3396                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3397                 switch (ret) {
3398                 case 0: break;
3399                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3400                 case 2: return false;
3401                 }
3402         }
3403
3404         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3405         DispatchResult dr;
3406         dispatch(cmd, dr);
3407         return dr.dispatched();
3408 }
3409
3410
3411 bool GuiView::saveBuffer(Buffer & b)
3412 {
3413         return saveBuffer(b, FileName());
3414 }
3415
3416
3417 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3418 {
3419         if (workArea(b) && workArea(b)->inDialogMode())
3420                 return true;
3421
3422         if (fn.empty() && b.isUnnamed())
3423                 return renameBuffer(b, docstring());
3424
3425         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3426         if (success) {
3427                 theSession().lastFiles().add(b.fileName());
3428                 theSession().writeFile();
3429                 return true;
3430         }
3431
3432         // Switch to this Buffer.
3433         setBuffer(&b);
3434
3435         // FIXME: we don't tell the user *WHY* the save failed !!
3436         docstring const file = makeDisplayPath(b.absFileName(), 30);
3437         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3438                                    "Do you want to rename the document and "
3439                                    "try again?"), file);
3440         int const ret = Alert::prompt(_("Rename and save?"),
3441                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3442         switch (ret) {
3443         case 0:
3444                 if (!renameBuffer(b, docstring()))
3445                         return false;
3446                 break;
3447         case 1:
3448                 break;
3449         case 2:
3450                 return false;
3451         }
3452
3453         return saveBuffer(b, fn);
3454 }
3455
3456
3457 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3458 {
3459         return closeWorkArea(wa, false);
3460 }
3461
3462
3463 // We only want to close the buffer if it is not visible in other workareas
3464 // of the same view, nor in other views, and if this is not a child
3465 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3466 {
3467         Buffer & buf = wa->bufferView().buffer();
3468
3469         bool last_wa = d.countWorkAreasOf(buf) == 1
3470                 && !inOtherView(buf) && !buf.parent();
3471
3472         bool close_buffer = last_wa;
3473
3474         if (last_wa) {
3475                 if (lyxrc.close_buffer_with_last_view == "yes")
3476                         ; // Nothing to do
3477                 else if (lyxrc.close_buffer_with_last_view == "no")
3478                         close_buffer = false;
3479                 else {
3480                         docstring file;
3481                         if (buf.isUnnamed())
3482                                 file = from_utf8(buf.fileName().onlyFileName());
3483                         else
3484                                 file = buf.fileName().displayName(30);
3485                         docstring const text = bformat(
3486                                 _("Last view on document %1$s is being closed.\n"
3487                                   "Would you like to close or hide the document?\n"
3488                                   "\n"
3489                                   "Hidden documents can be displayed back through\n"
3490                                   "the menu: View->Hidden->...\n"
3491                                   "\n"
3492                                   "To remove this question, set your preference in:\n"
3493                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3494                                 ), file);
3495                         int ret = Alert::prompt(_("Close or hide document?"),
3496                                 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3497                         if (ret == 2)
3498                                 return false;
3499                         close_buffer = (ret == 0);
3500                 }
3501         }
3502
3503         return closeWorkArea(wa, close_buffer);
3504 }
3505
3506
3507 bool GuiView::closeBuffer()
3508 {
3509         GuiWorkArea * wa = currentMainWorkArea();
3510         // coverity complained about this
3511         // it seems unnecessary, but perhaps is worth the check
3512         LASSERT(wa, return false);
3513
3514         setCurrentWorkArea(wa);
3515         Buffer & buf = wa->bufferView().buffer();
3516         return closeWorkArea(wa, !buf.parent());
3517 }
3518
3519
3520 void GuiView::writeSession() const {
3521         GuiWorkArea const * active_wa = currentMainWorkArea();
3522         for (int i = 0; i < d.splitter_->count(); ++i) {
3523                 TabWorkArea * twa = d.tabWorkArea(i);
3524                 for (int j = 0; j < twa->count(); ++j) {
3525                         GuiWorkArea * wa = twa->workArea(j);
3526                         Buffer & buf = wa->bufferView().buffer();
3527                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3528                 }
3529         }
3530 }
3531
3532
3533 bool GuiView::closeBufferAll()
3534 {
3535
3536         for (auto & buf : theBufferList()) {
3537                 if (!saveBufferIfNeeded(*buf, false)) {
3538                         // Closing has been cancelled, so abort.
3539                         return false;
3540                 }
3541         }
3542
3543         // Close the workareas in all other views
3544         QList<int> const ids = guiApp->viewIds();
3545         for (int i = 0; i != ids.size(); ++i) {
3546                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3547                         return false;
3548         }
3549
3550         // Close our own workareas
3551         if (!closeWorkAreaAll())
3552                 return false;
3553
3554         return true;
3555 }
3556
3557
3558 bool GuiView::closeWorkAreaAll()
3559 {
3560         setCurrentWorkArea(currentMainWorkArea());
3561
3562         // We might be in a situation that there is still a tabWorkArea, but
3563         // there are no tabs anymore. This can happen when we get here after a
3564         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3565         // many TabWorkArea's have no documents anymore.
3566         int empty_twa = 0;
3567
3568         // We have to call count() each time, because it can happen that
3569         // more than one splitter will disappear in one iteration (bug 5998).
3570         while (d.splitter_->count() > empty_twa) {
3571                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3572
3573                 if (twa->count() == 0)
3574                         ++empty_twa;
3575                 else {
3576                         setCurrentWorkArea(twa->currentWorkArea());
3577                         if (!closeTabWorkArea(twa))
3578                                 return false;
3579                 }
3580         }
3581         return true;
3582 }
3583
3584
3585 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3586 {
3587         if (!wa)
3588                 return false;
3589
3590         Buffer & buf = wa->bufferView().buffer();
3591
3592         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3593                 Alert::warning(_("Close document"),
3594                         _("Document could not be closed because it is being processed by LyX."));
3595                 return false;
3596         }
3597
3598         if (close_buffer)
3599                 return closeBuffer(buf);
3600         else {
3601                 if (!inMultiTabs(wa))
3602                         if (!saveBufferIfNeeded(buf, true))
3603                                 return false;
3604                 removeWorkArea(wa);
3605                 return true;
3606         }
3607 }
3608
3609
3610 bool GuiView::closeBuffer(Buffer & buf)
3611 {
3612         bool success = true;
3613         for (Buffer * child_buf : buf.getChildren()) {
3614                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3615                         child_buf->setParent(nullptr);
3616                         continue;
3617                 }
3618
3619                 // FIXME: should we look in other tabworkareas?
3620                 // ANSWER: I don't think so. I've tested, and if the child is
3621                 // open in some other window, it closes without a problem.
3622                 GuiWorkArea * child_wa = workArea(*child_buf);
3623                 if (child_wa) {
3624                         if (closing_)
3625                                 // If we are in a close_event all children will be closed in some time,
3626                                 // so no need to do it here. This will ensure that the children end up
3627                                 // in the session file in the correct order. If we close the master
3628                                 // buffer, we can close or release the child buffers here too.
3629                                 continue;
3630                         success = closeWorkArea(child_wa, true);
3631                         if (!success)
3632                                 break;
3633                 } else {
3634                         // In this case the child buffer is open but hidden.
3635                         // Even in this case, children can be dirty (e.g.,
3636                         // after a label change in the master, see #11405).
3637                         // Therefore, check this
3638                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3639                                 // If we are in a close_event all children will be closed in some time,
3640                                 // so no need to do it here. This will ensure that the children end up
3641                                 // in the session file in the correct order. If we close the master
3642                                 // buffer, we can close or release the child buffers here too.
3643                                 continue;
3644                         }
3645                         // Save dirty buffers also if closing_!
3646                         if (saveBufferIfNeeded(*child_buf, false)) {
3647                                 child_buf->removeAutosaveFile();
3648                                 theBufferList().release(child_buf);
3649                         } else {
3650                                 // Saving of dirty children has been cancelled.
3651                                 // Cancel the whole process.
3652                                 success = false;
3653                                 break;
3654                         }
3655                 }
3656         }
3657         if (success) {
3658                 // goto bookmark to update bookmark pit.
3659                 // FIXME: we should update only the bookmarks related to this buffer!
3660                 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3661                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3662                 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3663                         guiApp->gotoBookmark(i, false, false);
3664
3665                 if (saveBufferIfNeeded(buf, false)) {
3666                         buf.removeAutosaveFile();
3667                         theBufferList().release(&buf);
3668                         return true;
3669                 }
3670         }
3671         // open all children again to avoid a crash because of dangling
3672         // pointers (bug 6603)
3673         buf.updateBuffer();
3674         return false;
3675 }
3676
3677
3678 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3679 {
3680         while (twa == d.currentTabWorkArea()) {
3681                 twa->setCurrentIndex(twa->count() - 1);
3682
3683                 GuiWorkArea * wa = twa->currentWorkArea();
3684                 Buffer & b = wa->bufferView().buffer();
3685
3686                 // We only want to close the buffer if the same buffer is not visible
3687                 // in another view, and if this is not a child and if we are closing
3688                 // a view (not a tabgroup).
3689                 bool const close_buffer =
3690                         !inOtherView(b) && !b.parent() && closing_;
3691
3692                 if (!closeWorkArea(wa, close_buffer))
3693                         return false;
3694         }
3695         return true;
3696 }
3697
3698
3699 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3700 {
3701         if (buf.isClean() || buf.paragraphs().empty())
3702                 return true;
3703
3704         // Switch to this Buffer.
3705         setBuffer(&buf);
3706
3707         docstring file;
3708         bool exists;
3709         // FIXME: Unicode?
3710         if (buf.isUnnamed()) {
3711                 file = from_utf8(buf.fileName().onlyFileName());
3712                 exists = false;
3713         } else {
3714                 FileName filename = buf.fileName();
3715                 filename.refresh();
3716                 file = filename.displayName(30);
3717                 exists = filename.exists();
3718         }
3719
3720         // Bring this window to top before asking questions.
3721         raise();
3722         activateWindow();
3723
3724         int ret;
3725         if (hiding && buf.isUnnamed()) {
3726                 docstring const text = bformat(_("The document %1$s has not been "
3727                                                  "saved yet.\n\nDo you want to save "
3728                                                  "the document?"), file);
3729                 ret = Alert::prompt(_("Save new document?"),
3730                         text, 0, 1, _("&Save"), _("&Cancel"));
3731                 if (ret == 1)
3732                         ++ret;
3733         } else {
3734                 docstring const text = exists ?
3735                         bformat(_("The document %1$s has unsaved changes."
3736                                   "\n\nDo you want to save the document or "
3737                                   "discard the changes?"), file) :
3738                         bformat(_("The document %1$s has not been saved yet."
3739                                   "\n\nDo you want to save the document or "
3740                                   "discard it entirely?"), file);
3741                 docstring const title = exists ?
3742                         _("Save changed document?") : _("Save document?");
3743                 ret = Alert::prompt(title, text, 0, 2,
3744                                     _("&Save"), _("&Discard"), _("&Cancel"));
3745         }
3746
3747         switch (ret) {
3748         case 0:
3749                 if (!saveBuffer(buf))
3750                         return false;
3751                 break;
3752         case 1:
3753                 // If we crash after this we could have no autosave file
3754                 // but I guess this is really improbable (Jug).
3755                 // Sometimes improbable things happen:
3756                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3757                 // buf.removeAutosaveFile();
3758                 if (hiding)
3759                         // revert all changes
3760                         reloadBuffer(buf);
3761                 buf.markClean();
3762                 break;
3763         case 2:
3764                 return false;
3765         }
3766         return true;
3767 }
3768
3769
3770 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3771 {
3772         Buffer & buf = wa->bufferView().buffer();
3773
3774         for (int i = 0; i != d.splitter_->count(); ++i) {
3775                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3776                 if (wa_ && wa_ != wa)
3777                         return true;
3778         }
3779         return inOtherView(buf);
3780 }
3781
3782
3783 bool GuiView::inOtherView(Buffer & buf)
3784 {
3785         QList<int> const ids = guiApp->viewIds();
3786
3787         for (int i = 0; i != ids.size(); ++i) {
3788                 if (id_ == ids[i])
3789                         continue;
3790
3791                 if (guiApp->view(ids[i]).workArea(buf))
3792                         return true;
3793         }
3794         return false;
3795 }
3796
3797
3798 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3799 {
3800         if (!documentBufferView())
3801                 return;
3802
3803         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3804                 Buffer * const curbuf = &documentBufferView()->buffer();
3805                 int nwa = twa->count();
3806                 for (int i = 0; i < nwa; ++i) {
3807                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3808                                 int next_index;
3809                                 if (np == NEXT)
3810                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3811                                 else
3812                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3813                                 if (move)
3814                                         twa->moveTab(i, next_index);
3815                                 else
3816                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3817                                 break;
3818                         }
3819                 }
3820         }
3821 }
3822
3823
3824 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3825 {
3826         int count = d.splitter_->count();
3827         for (int i = 0; i < count; ++i) {
3828                 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3829                         int new_index;
3830                         if (np == NEXT)
3831                                 new_index = (i == count - 1 ? 0 : i + 1);
3832                         else
3833                                 new_index = (i == 0 ? count - 1 : i - 1);
3834                         setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3835                         break;
3836                 }
3837         }
3838 }
3839
3840
3841 /// make sure the document is saved
3842 static bool ensureBufferClean(Buffer * buffer)
3843 {
3844         LASSERT(buffer, return false);
3845         if (buffer->isClean() && !buffer->isUnnamed())
3846                 return true;
3847
3848         docstring const file = buffer->fileName().displayName(30);
3849         docstring title;
3850         docstring text;
3851         if (!buffer->isUnnamed()) {
3852                 text = bformat(_("The document %1$s has unsaved "
3853                                                  "changes.\n\nDo you want to save "
3854                                                  "the document?"), file);
3855                 title = _("Save changed document?");
3856
3857         } else {
3858                 text = bformat(_("The document %1$s has not been "
3859                                                  "saved yet.\n\nDo you want to save "
3860                                                  "the document?"), file);
3861                 title = _("Save new document?");
3862         }
3863         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3864
3865         if (ret == 0)
3866                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3867
3868         return buffer->isClean() && !buffer->isUnnamed();
3869 }
3870
3871
3872 bool GuiView::reloadBuffer(Buffer & buf)
3873 {
3874         currentBufferView()->cursor().reset();
3875         Buffer::ReadStatus status = buf.reload();
3876         return status == Buffer::ReadSuccess;
3877 }
3878
3879
3880 void GuiView::checkExternallyModifiedBuffers()
3881 {
3882         for (Buffer * buf : theBufferList()) {
3883                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3884                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3885                                         " Reload now? Any local changes will be lost."),
3886                                         from_utf8(buf->absFileName()));
3887                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3888                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3889                         if (!ret)
3890                                 reloadBuffer(*buf);
3891                 }
3892         }
3893 }
3894
3895
3896 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3897 {
3898         Buffer * buffer = documentBufferView()
3899                 ? &(documentBufferView()->buffer()) : nullptr;
3900
3901         switch (cmd.action()) {
3902         case LFUN_VC_REGISTER:
3903                 if (!buffer || !ensureBufferClean(buffer))
3904                         break;
3905                 if (!buffer->lyxvc().inUse()) {
3906                         if (buffer->lyxvc().registrer()) {
3907                                 reloadBuffer(*buffer);
3908                                 dr.clearMessageUpdate();
3909                         }
3910                 }
3911                 break;
3912
3913         case LFUN_VC_RENAME:
3914         case LFUN_VC_COPY: {
3915                 if (!buffer || !ensureBufferClean(buffer))
3916                         break;
3917                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3918                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3919                                 // Some changes are not yet committed.
3920                                 // We test here and not in getStatus(), since
3921                                 // this test is expensive.
3922                                 string log;
3923                                 LyXVC::CommandResult ret =
3924                                         buffer->lyxvc().checkIn(log);
3925                                 dr.setMessage(log);
3926                                 if (ret == LyXVC::ErrorCommand ||
3927                                     ret == LyXVC::VCSuccess)
3928                                         reloadBuffer(*buffer);
3929                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3930                                         frontend::Alert::error(
3931                                                 _("Revision control error."),
3932                                                 _("Document could not be checked in."));
3933                                         break;
3934                                 }
3935                         }
3936                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3937                                 LV_VC_RENAME : LV_VC_COPY;
3938                         renameBuffer(*buffer, cmd.argument(), kind);
3939                 }
3940                 break;
3941         }
3942
3943         case LFUN_VC_CHECK_IN:
3944                 if (!buffer || !ensureBufferClean(buffer))
3945                         break;
3946                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3947                         string log;
3948                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3949                         dr.setMessage(log);
3950                         // Only skip reloading if the checkin was cancelled or
3951                         // an error occurred before the real checkin VCS command
3952                         // was executed, since the VCS might have changed the
3953                         // file even if it could not checkin successfully.
3954                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3955                                 reloadBuffer(*buffer);
3956                 }
3957                 break;
3958
3959         case LFUN_VC_CHECK_OUT:
3960                 if (!buffer || !ensureBufferClean(buffer))
3961                         break;
3962                 if (buffer->lyxvc().inUse()) {
3963                         dr.setMessage(buffer->lyxvc().checkOut());
3964                         reloadBuffer(*buffer);
3965                 }
3966                 break;
3967
3968         case LFUN_VC_LOCKING_TOGGLE:
3969                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3970                         break;
3971                 if (buffer->lyxvc().inUse()) {
3972                         string res = buffer->lyxvc().lockingToggle();
3973                         if (res.empty()) {
3974                                 frontend::Alert::error(_("Revision control error."),
3975                                 _("Error when setting the locking property."));
3976                         } else {
3977                                 dr.setMessage(res);
3978                                 reloadBuffer(*buffer);
3979                         }
3980                 }
3981                 break;
3982
3983         case LFUN_VC_REVERT:
3984                 if (!buffer)
3985                         break;
3986                 if (buffer->lyxvc().revert()) {
3987                         reloadBuffer(*buffer);
3988                         dr.clearMessageUpdate();
3989                 }
3990                 break;
3991
3992         case LFUN_VC_UNDO_LAST:
3993                 if (!buffer)
3994                         break;
3995                 buffer->lyxvc().undoLast();
3996                 reloadBuffer(*buffer);
3997                 dr.clearMessageUpdate();
3998                 break;
3999
4000         case LFUN_VC_REPO_UPDATE:
4001                 if (!buffer)
4002                         break;
4003                 if (ensureBufferClean(buffer)) {
4004                         dr.setMessage(buffer->lyxvc().repoUpdate());
4005                         checkExternallyModifiedBuffers();
4006                 }
4007                 break;
4008
4009         case LFUN_VC_COMMAND: {
4010                 string flag = cmd.getArg(0);
4011                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4012                         break;
4013                 docstring message;
4014                 if (contains(flag, 'M')) {
4015                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
4016                                 break;
4017                 }
4018                 string path = cmd.getArg(1);
4019                 if (contains(path, "$$p") && buffer)
4020                         path = subst(path, "$$p", buffer->filePath());
4021                 LYXERR(Debug::LYXVC, "Directory: " << path);
4022                 FileName pp(path);
4023                 if (!pp.isReadableDirectory()) {
4024                         lyxerr << _("Directory is not accessible.") << endl;
4025                         break;
4026                 }
4027                 support::PathChanger p(pp);
4028
4029                 string command = cmd.getArg(2);
4030                 if (command.empty())
4031                         break;
4032                 if (buffer) {
4033                         command = subst(command, "$$i", buffer->absFileName());
4034                         command = subst(command, "$$p", buffer->filePath());
4035                 }
4036                 command = subst(command, "$$m", to_utf8(message));
4037                 LYXERR(Debug::LYXVC, "Command: " << command);
4038                 Systemcall one;
4039                 one.startscript(Systemcall::Wait, command);
4040
4041                 if (!buffer)
4042                         break;
4043                 if (contains(flag, 'I'))
4044                         buffer->markDirty();
4045                 if (contains(flag, 'R'))
4046                         reloadBuffer(*buffer);
4047
4048                 break;
4049                 }
4050
4051         case LFUN_VC_COMPARE: {
4052                 if (cmd.argument().empty()) {
4053                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4054                         break;
4055                 }
4056                 if (!buffer)
4057                         break;
4058
4059                 string rev1 = cmd.getArg(0);
4060                 string f1, f2;
4061
4062                 // f1
4063                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4064                         break;
4065
4066                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4067                         f2 = buffer->absFileName();
4068                 } else {
4069                         string rev2 = cmd.getArg(1);
4070                         if (rev2.empty())
4071                                 break;
4072                         // f2
4073                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4074                                 break;
4075                 }
4076
4077                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4078                                         f1 << "\n"  << f2 << "\n" );
4079                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4080                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4081                 break;
4082         }
4083
4084         default:
4085                 break;
4086         }
4087 }
4088
4089
4090 void GuiView::openChildDocument(string const & fname)
4091 {
4092         LASSERT(documentBufferView(), return);
4093         Buffer & buffer = documentBufferView()->buffer();
4094         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4095         documentBufferView()->saveBookmark(false);
4096         Buffer * child = nullptr;
4097         if (theBufferList().exists(filename)) {
4098                 child = theBufferList().getBuffer(filename);
4099                 setBuffer(child);
4100         } else {
4101                 message(bformat(_("Opening child document %1$s..."),
4102                         makeDisplayPath(filename.absFileName())));
4103                 child = loadDocument(filename, false);
4104         }
4105         // Set the parent name of the child document.
4106         // This makes insertion of citations and references in the child work,
4107         // when the target is in the parent or another child document.
4108         if (child)
4109                 child->setParent(&buffer);
4110 }
4111
4112
4113 bool GuiView::goToFileRow(string const & argument)
4114 {
4115         string file_name;
4116         int row = -1;
4117         size_t i = argument.find_last_of(' ');
4118         if (i != string::npos) {
4119                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4120                 istringstream is(argument.substr(i + 1));
4121                 is >> row;
4122                 if (is.fail())
4123                         i = string::npos;
4124         }
4125         if (i == string::npos) {
4126                 LYXERR0("Wrong argument: " << argument);
4127                 return false;
4128         }
4129         Buffer * buf = nullptr;
4130         string const realtmp = package().temp_dir().realPath();
4131         // We have to use os::path_prefix_is() here, instead of
4132         // simply prefixIs(), because the file name comes from
4133         // an external application and may need case adjustment.
4134         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4135                 buf = theBufferList().getBufferFromTmp(file_name, true);
4136                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4137                            << (buf ? " success" : " failed"));
4138         } else {
4139                 // Must replace extension of the file to be .lyx
4140                 // and get full path
4141                 FileName const s = fileSearch(string(),
4142                                                   support::changeExtension(file_name, ".lyx"), "lyx");
4143                 // Either change buffer or load the file
4144                 if (theBufferList().exists(s))
4145                         buf = theBufferList().getBuffer(s);
4146                 else if (s.exists()) {
4147                         buf = loadDocument(s);
4148                         if (!buf)
4149                                 return false;
4150                 } else {
4151                         message(bformat(
4152                                         _("File does not exist: %1$s"),
4153                                         makeDisplayPath(file_name)));
4154                         return false;
4155                 }
4156         }
4157         if (!buf) {
4158                 message(bformat(
4159                         _("No buffer for file: %1$s."),
4160                         makeDisplayPath(file_name))
4161                 );
4162                 return false;
4163         }
4164         setBuffer(buf);
4165         bool success = documentBufferView()->setCursorFromRow(row);
4166         if (!success) {
4167                 LYXERR(Debug::OUTFILE,
4168                        "setCursorFromRow: invalid position for row " << row);
4169                 frontend::Alert::error(_("Inverse Search Failed"),
4170                                        _("Invalid position requested by inverse search.\n"
4171                                          "You may need to update the viewed document."));
4172         }
4173         return success;
4174 }
4175
4176
4177 template<class T>
4178 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4179                 Buffer const * orig, Buffer * clone, string const & format)
4180 {
4181         Buffer::ExportStatus const status = func(format);
4182
4183         // the cloning operation will have produced a clone of the entire set of
4184         // documents, starting from the master. so we must delete those.
4185         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4186         delete mbuf;
4187         busyBuffers.remove(orig);
4188         return status;
4189 }
4190
4191
4192 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4193                 Buffer const * orig, Buffer * clone, string const & format)
4194 {
4195         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4196                         &Buffer::doExport;
4197         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4198 }
4199
4200
4201 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4202                 Buffer const * orig, Buffer * clone, string const & format)
4203 {
4204         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4205                         &Buffer::doExport;
4206         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4207 }
4208
4209
4210 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4211                 Buffer const * orig, Buffer * clone, string const & format)
4212 {
4213         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4214                         &Buffer::preview;
4215         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4216 }
4217
4218
4219 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4220                            Buffer const * used_buffer,
4221                            docstring const & msg,
4222                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4223                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4224                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4225                            bool allow_async, bool use_tmpdir)
4226 {
4227         if (!used_buffer)
4228                 return false;
4229
4230         string format = argument;
4231         if (format.empty())
4232                 format = used_buffer->params().getDefaultOutputFormat();
4233         processing_format = format;
4234         if (!msg.empty()) {
4235                 progress_->clearMessages();
4236                 gv_->message(msg);
4237         }
4238 #if EXPORT_in_THREAD
4239         if (allow_async) {
4240                 GuiViewPrivate::busyBuffers.insert(used_buffer);
4241                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4242                 if (!cloned_buffer) {
4243                         Alert::error(_("Export Error"),
4244                                      _("Error cloning the Buffer."));
4245                         return false;
4246                 }
4247                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4248                                         asyncFunc,
4249                                         used_buffer,
4250                                         cloned_buffer,
4251                                         format);
4252                 setPreviewFuture(f);
4253                 last_export_format = used_buffer->params().bufferFormat();
4254                 (void) syncFunc;
4255                 (void) previewFunc;
4256                 // We are asynchronous, so we don't know here anything about the success
4257                 return true;
4258         } else {
4259                 Buffer::ExportStatus status;
4260                 if (syncFunc) {
4261                         status = (used_buffer->*syncFunc)(format, use_tmpdir);
4262                 } else if (previewFunc) {
4263                         status = (used_buffer->*previewFunc)(format);
4264                 } else
4265                         return false;
4266                 handleExportStatus(gv_, status, format);
4267                 (void) asyncFunc;
4268                 return (status == Buffer::ExportSuccess
4269                                 || status == Buffer::PreviewSuccess);
4270         }
4271 #else
4272         (void) allow_async;
4273         Buffer::ExportStatus status;
4274         if (syncFunc) {
4275                 status = (used_buffer->*syncFunc)(format, true);
4276         } else if (previewFunc) {
4277                 status = (used_buffer->*previewFunc)(format);
4278         } else
4279                 return false;
4280         handleExportStatus(gv_, status, format);
4281         (void) asyncFunc;
4282         return (status == Buffer::ExportSuccess
4283                         || status == Buffer::PreviewSuccess);
4284 #endif
4285 }
4286
4287 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4288 {
4289         BufferView * bv = currentBufferView();
4290         LASSERT(bv, return);
4291
4292         // Let the current BufferView dispatch its own actions.
4293         bv->dispatch(cmd, dr);
4294         if (dr.dispatched()) {
4295                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4296                         updateDialog("document", "");
4297                 return;
4298         }
4299
4300         // Try with the document BufferView dispatch if any.
4301         BufferView * doc_bv = documentBufferView();
4302         if (doc_bv && doc_bv != bv) {
4303                 doc_bv->dispatch(cmd, dr);
4304                 if (dr.dispatched()) {
4305                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4306                                 updateDialog("document", "");
4307                         return;
4308                 }
4309         }
4310
4311         // Then let the current Cursor dispatch its own actions.
4312         bv->cursor().dispatch(cmd);
4313
4314         // update completion. We do it here and not in
4315         // processKeySym to avoid another redraw just for a
4316         // changed inline completion
4317         if (cmd.origin() == FuncRequest::KEYBOARD) {
4318                 if (cmd.action() == LFUN_SELF_INSERT
4319                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4320                         updateCompletion(bv->cursor(), true, true);
4321                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4322                         updateCompletion(bv->cursor(), false, true);
4323                 else
4324                         updateCompletion(bv->cursor(), false, false);
4325         }
4326
4327         dr = bv->cursor().result();
4328 }
4329
4330
4331 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4332 {
4333         BufferView * bv = currentBufferView();
4334         // By default we won't need any update.
4335         dr.screenUpdate(Update::None);
4336         // assume cmd will be dispatched
4337         dr.dispatched(true);
4338
4339         Buffer * doc_buffer = documentBufferView()
4340                 ? &(documentBufferView()->buffer()) : nullptr;
4341
4342         if (cmd.origin() == FuncRequest::TOC) {
4343                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4344                 toc->doDispatch(bv->cursor(), cmd, dr);
4345                 return;
4346         }
4347
4348         string const argument = to_utf8(cmd.argument());
4349
4350         switch(cmd.action()) {
4351                 case LFUN_BUFFER_CHILD_OPEN:
4352                         openChildDocument(to_utf8(cmd.argument()));
4353                         break;
4354
4355                 case LFUN_BUFFER_IMPORT:
4356                         importDocument(to_utf8(cmd.argument()));
4357                         break;
4358
4359                 case LFUN_MASTER_BUFFER_EXPORT:
4360                         if (doc_buffer)
4361                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4362                         // fall through
4363                 case LFUN_BUFFER_EXPORT: {
4364                         if (!doc_buffer)
4365                                 break;
4366                         // GCC only sees strfwd.h when building merged
4367                         if (::lyx::operator==(cmd.argument(), "custom")) {
4368                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4369                                 // so the following test should not be needed.
4370                                 // In principle, we could try to switch to such a view...
4371                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4372                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4373                                 break;
4374                         }
4375
4376                         string const dest = cmd.getArg(1);
4377                         FileName target_dir;
4378                         if (!dest.empty() && FileName::isAbsolute(dest))
4379                                 target_dir = FileName(support::onlyPath(dest));
4380                         else
4381                                 target_dir = doc_buffer->fileName().onlyPath();
4382
4383                         string const format = (argument.empty() || argument == "default") ?
4384                                 doc_buffer->params().getDefaultOutputFormat() : argument;
4385
4386                         if ((dest.empty() && doc_buffer->isUnnamed())
4387                             || !target_dir.isDirWritable()) {
4388                                 exportBufferAs(*doc_buffer, from_utf8(format));
4389                                 break;
4390                         }
4391                         /* TODO/Review: Is it a problem to also export the children?
4392                                         See the update_unincluded flag */
4393                         d.asyncBufferProcessing(format,
4394                                                 doc_buffer,
4395                                                 _("Exporting ..."),
4396                                                 &GuiViewPrivate::exportAndDestroy,
4397                                                 &Buffer::doExport,
4398                                                 nullptr, cmd.allowAsync());
4399                         // TODO Inform user about success
4400                         break;
4401                 }
4402
4403                 case LFUN_BUFFER_EXPORT_AS: {
4404                         LASSERT(doc_buffer, break);
4405                         docstring f = cmd.argument();
4406                         if (f.empty())
4407                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4408                         exportBufferAs(*doc_buffer, f);
4409                         break;
4410                 }
4411
4412                 case LFUN_BUFFER_UPDATE: {
4413                         d.asyncBufferProcessing(argument,
4414                                                 doc_buffer,
4415                                                 _("Exporting ..."),
4416                                                 &GuiViewPrivate::compileAndDestroy,
4417                                                 &Buffer::doExport,
4418                                                 nullptr, cmd.allowAsync(), true);
4419                         break;
4420                 }
4421                 case LFUN_BUFFER_VIEW: {
4422                         d.asyncBufferProcessing(argument,
4423                                                 doc_buffer,
4424                                                 _("Previewing ..."),
4425                                                 &GuiViewPrivate::previewAndDestroy,
4426                                                 nullptr,
4427                                                 &Buffer::preview, cmd.allowAsync());
4428                         break;
4429                 }
4430                 case LFUN_MASTER_BUFFER_UPDATE: {
4431                         d.asyncBufferProcessing(argument,
4432                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4433                                                 docstring(),
4434                                                 &GuiViewPrivate::compileAndDestroy,
4435                                                 &Buffer::doExport,
4436                                                 nullptr, cmd.allowAsync(), true);
4437                         break;
4438                 }
4439                 case LFUN_MASTER_BUFFER_VIEW: {
4440                         d.asyncBufferProcessing(argument,
4441                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4442                                                 docstring(),
4443                                                 &GuiViewPrivate::previewAndDestroy,
4444                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4445                         break;
4446                 }
4447                 case LFUN_EXPORT_CANCEL: {
4448                         cancelExport();
4449                         break;
4450                 }
4451                 case LFUN_BUFFER_SWITCH: {
4452                         string const file_name = to_utf8(cmd.argument());
4453                         if (!FileName::isAbsolute(file_name)) {
4454                                 dr.setError(true);
4455                                 dr.setMessage(_("Absolute filename expected."));
4456                                 break;
4457                         }
4458
4459                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4460                         if (!buffer) {
4461                                 dr.setError(true);
4462                                 dr.setMessage(_("Document not loaded"));
4463                                 break;
4464                         }
4465
4466                         // Do we open or switch to the buffer in this view ?
4467                         if (workArea(*buffer)
4468                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4469                                 setBuffer(buffer);
4470                                 break;
4471                         }
4472
4473                         // Look for the buffer in other views
4474                         QList<int> const ids = guiApp->viewIds();
4475                         int i = 0;
4476                         for (; i != ids.size(); ++i) {
4477                                 GuiView & gv = guiApp->view(ids[i]);
4478                                 if (gv.workArea(*buffer)) {
4479                                         gv.raise();
4480                                         gv.activateWindow();
4481                                         gv.setFocus();
4482                                         gv.setBuffer(buffer);
4483                                         break;
4484                                 }
4485                         }
4486
4487                         // If necessary, open a new window as a last resort
4488                         if (i == ids.size()) {
4489                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4490                                 lyx::dispatch(cmd);
4491                         }
4492                         break;
4493                 }
4494
4495                 case LFUN_BUFFER_NEXT:
4496                         gotoNextOrPreviousBuffer(NEXT, false);
4497                         break;
4498
4499                 case LFUN_BUFFER_MOVE_NEXT:
4500                         gotoNextOrPreviousBuffer(NEXT, true);
4501                         break;
4502
4503                 case LFUN_BUFFER_PREVIOUS:
4504                         gotoNextOrPreviousBuffer(PREV, false);
4505                         break;
4506
4507                 case LFUN_BUFFER_MOVE_PREVIOUS:
4508                         gotoNextOrPreviousBuffer(PREV, true);
4509                         break;
4510
4511                 case LFUN_BUFFER_CHKTEX:
4512                         LASSERT(doc_buffer, break);
4513                         doc_buffer->runChktex();
4514                         break;
4515
4516                 case LFUN_COMMAND_EXECUTE: {
4517                         command_execute_ = true;
4518                         minibuffer_focus_ = true;
4519                         break;
4520                 }
4521                 case LFUN_DROP_LAYOUTS_CHOICE:
4522                         d.layout_->showPopup();
4523                         break;
4524
4525                 case LFUN_MENU_OPEN:
4526                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4527                                 menu->exec(QCursor::pos());
4528                         break;
4529
4530                 case LFUN_FILE_INSERT: {
4531                         bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4532                         if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4533                                 dr.forceBufferUpdate();
4534                                 dr.screenUpdate(Update::Force);
4535                         }
4536                         break;
4537                 }
4538
4539                 case LFUN_FILE_INSERT_PLAINTEXT:
4540                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4541                         string const fname = to_utf8(cmd.argument());
4542                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4543                                 dr.setMessage(_("Absolute filename expected."));
4544                                 break;
4545                         }
4546
4547                         FileName filename(fname);
4548                         if (fname.empty()) {
4549                                 FileDialog dlg(qt_("Select file to insert"));
4550
4551                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4552                                         QStringList(qt_("All Files (*)")));
4553
4554                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4555                                         dr.setMessage(_("Canceled."));
4556                                         break;
4557                                 }
4558
4559                                 filename.set(fromqstr(result.second));
4560                         }
4561
4562                         if (bv) {
4563                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4564                                 bv->dispatch(new_cmd, dr);
4565                         }
4566                         break;
4567                 }
4568
4569                 case LFUN_BUFFER_RELOAD: {
4570                         LASSERT(doc_buffer, break);
4571
4572                         // drop changes?
4573                         bool drop = (cmd.argument() == "dump");
4574
4575                         int ret = 0;
4576                         if (!drop && !doc_buffer->isClean()) {
4577                                 docstring const file =
4578                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4579                                 if (doc_buffer->notifiesExternalModification()) {
4580                                         docstring text = _("The current version will be lost. "
4581                                             "Are you sure you want to load the version on disk "
4582                                             "of the document %1$s?");
4583                                         ret = Alert::prompt(_("Reload saved document?"),
4584                                                             bformat(text, file), 1, 1,
4585                                                             _("&Reload"), _("&Cancel"));
4586                                 } else {
4587                                         docstring text = _("Any changes will be lost. "
4588                                             "Are you sure you want to revert to the saved version "
4589                                             "of the document %1$s?");
4590                                         ret = Alert::prompt(_("Revert to saved document?"),
4591                                                             bformat(text, file), 1, 1,
4592                                                             _("&Revert"), _("&Cancel"));
4593                                 }
4594                         }
4595
4596                         if (ret == 0) {
4597                                 doc_buffer->markClean();
4598                                 reloadBuffer(*doc_buffer);
4599                                 dr.forceBufferUpdate();
4600                         }
4601                         break;
4602                 }
4603
4604                 case LFUN_BUFFER_RESET_EXPORT:
4605                         LASSERT(doc_buffer, break);
4606                         doc_buffer->requireFreshStart(true);
4607                         dr.setMessage(_("Buffer export reset."));
4608                         break;
4609
4610                 case LFUN_BUFFER_WRITE:
4611                         LASSERT(doc_buffer, break);
4612                         saveBuffer(*doc_buffer);
4613                         break;
4614
4615                 case LFUN_BUFFER_WRITE_AS:
4616                         LASSERT(doc_buffer, break);
4617                         renameBuffer(*doc_buffer, cmd.argument());
4618                         break;
4619
4620                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4621                         LASSERT(doc_buffer, break);
4622                         renameBuffer(*doc_buffer, cmd.argument(),
4623                                      LV_WRITE_AS_TEMPLATE);
4624                         break;
4625
4626                 case LFUN_BUFFER_WRITE_ALL: {
4627                         Buffer * first = theBufferList().first();
4628                         if (!first)
4629                                 break;
4630                         message(_("Saving all documents..."));
4631                         // We cannot use a for loop as the buffer list cycles.
4632                         Buffer * b = first;
4633                         do {
4634                                 if (!b->isClean()) {
4635                                         saveBuffer(*b);
4636                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4637                                 }
4638                                 b = theBufferList().next(b);
4639                         } while (b != first);
4640                         dr.setMessage(_("All documents saved."));
4641                         break;
4642                 }
4643
4644                 case LFUN_MASTER_BUFFER_FORALL: {
4645                         if (!doc_buffer)
4646                                 break;
4647
4648                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4649                         funcToRun.allowAsync(false);
4650
4651                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4652                                 // Switch to other buffer view and resend cmd
4653                                 lyx::dispatch(FuncRequest(
4654                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4655                                 lyx::dispatch(funcToRun);
4656                         }
4657                         // switch back
4658                         lyx::dispatch(FuncRequest(
4659                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4660                         break;
4661                 }
4662
4663                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4664                         LASSERT(doc_buffer, break);
4665                         doc_buffer->clearExternalModification();
4666                         break;
4667
4668                 case LFUN_BUFFER_CLOSE:
4669                         closeBuffer();
4670                         break;
4671
4672                 case LFUN_BUFFER_CLOSE_ALL:
4673                         closeBufferAll();
4674                         break;
4675
4676                 case LFUN_DEVEL_MODE_TOGGLE:
4677                         devel_mode_ = !devel_mode_;
4678                         if (devel_mode_)
4679                                 dr.setMessage(_("Developer mode is now enabled."));
4680                         else
4681                                 dr.setMessage(_("Developer mode is now disabled."));
4682                         break;
4683
4684                 case LFUN_TOOLBAR_SET: {
4685                         string const name = cmd.getArg(0);
4686                         string const state = cmd.getArg(1);
4687                         if (GuiToolbar * t = toolbar(name))
4688                                 t->setState(state);
4689                         break;
4690                 }
4691
4692                 case LFUN_TOOLBAR_TOGGLE: {
4693                         string const name = cmd.getArg(0);
4694                         if (GuiToolbar * t = toolbar(name))
4695                                 t->toggle();
4696                         break;
4697                 }
4698
4699                 case LFUN_TOOLBAR_MOVABLE: {
4700                         string const name = cmd.getArg(0);
4701                         if (name == "*") {
4702                                 // toggle (all) toolbars movablility
4703                                 toolbarsMovable_ = !toolbarsMovable_;
4704                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4705                                         GuiToolbar * tb = toolbar(ti.name);
4706                                         if (tb && tb->isMovable() != toolbarsMovable_)
4707                                                 // toggle toolbar movablity if it does not fit lock
4708                                                 // (all) toolbars positions state silent = true, since
4709                                                 // status bar notifications are slow
4710                                                 tb->movable(true);
4711                                 }
4712                                 if (toolbarsMovable_)
4713                                         dr.setMessage(_("Toolbars unlocked."));
4714                                 else
4715                                         dr.setMessage(_("Toolbars locked."));
4716                         } else if (GuiToolbar * tb = toolbar(name))
4717                                 // toggle current toolbar movablity
4718                                 tb->movable();
4719                         // update lock (all) toolbars positions
4720                         updateLockToolbars();
4721                         break;
4722                 }
4723
4724                 case LFUN_ICON_SIZE: {
4725                         QSize size = d.iconSize(cmd.argument());
4726                         setIconSize(size);
4727                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4728                                                 size.width(), size.height()));
4729                         break;
4730                 }
4731
4732                 case LFUN_DIALOG_UPDATE: {
4733                         string const name = to_utf8(cmd.argument());
4734                         if (name == "prefs" || name == "document")
4735                                 updateDialog(name, string());
4736                         else if (name == "paragraph")
4737                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4738                         else if (currentBufferView()) {
4739                                 Inset * inset = currentBufferView()->editedInset(name);
4740                                 // Can only update a dialog connected to an existing inset
4741                                 if (inset) {
4742                                         // FIXME: get rid of this indirection; GuiView ask the inset
4743                                         // if he is kind enough to update itself...
4744                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4745                                         //FIXME: pass DispatchResult here?
4746                                         inset->dispatch(currentBufferView()->cursor(), fr);
4747                                 }
4748                         }
4749                         break;
4750                 }
4751
4752                 case LFUN_DIALOG_TOGGLE: {
4753                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4754                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4755                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4756                         break;
4757                 }
4758
4759                 case LFUN_DIALOG_DISCONNECT_INSET:
4760                         disconnectDialog(to_utf8(cmd.argument()));
4761                         break;
4762
4763                 case LFUN_DIALOG_HIDE: {
4764                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4765                         break;
4766                 }
4767
4768                 case LFUN_DIALOG_SHOW: {
4769                         string const name = cmd.getArg(0);
4770                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4771
4772                         if (name == "latexlog") {
4773                                 // getStatus checks that
4774                                 LASSERT(doc_buffer, break);
4775                                 Buffer::LogType type;
4776                                 string const logfile = doc_buffer->logName(&type);
4777                                 switch (type) {
4778                                 case Buffer::latexlog:
4779                                         sdata = "latex ";
4780                                         break;
4781                                 case Buffer::buildlog:
4782                                         sdata = "literate ";
4783                                         break;
4784                                 }
4785                                 sdata += Lexer::quoteString(logfile);
4786                                 showDialog("log", sdata);
4787                         } else if (name == "vclog") {
4788                                 // getStatus checks that
4789                                 LASSERT(doc_buffer, break);
4790                                 string const sdata2 = "vc " +
4791                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4792                                 showDialog("log", sdata2);
4793                         } else if (name == "symbols") {
4794                                 sdata = bv->cursor().getEncoding()->name();
4795                                 if (!sdata.empty())
4796                                         showDialog("symbols", sdata);
4797                         } else if (name == "findreplace") {
4798                                 sdata = to_utf8(bv->cursor().selectionAsString(false));
4799                                 showDialog(name, sdata);
4800                         // bug 5274
4801                         } else if (name == "prefs" && isFullScreen()) {
4802                                 lfunUiToggle("fullscreen");
4803                                 showDialog("prefs", sdata);
4804                         } else
4805                                 showDialog(name, sdata);
4806                         break;
4807                 }
4808
4809                 case LFUN_MESSAGE:
4810                         dr.setMessage(cmd.argument());
4811                         break;
4812
4813                 case LFUN_UI_TOGGLE: {
4814                         string arg = cmd.getArg(0);
4815                         if (!lfunUiToggle(arg)) {
4816                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4817                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4818                         }
4819                         // Make sure the keyboard focus stays in the work area.
4820                         setFocus();
4821                         break;
4822                 }
4823
4824                 case LFUN_VIEW_SPLIT: {
4825                         LASSERT(doc_buffer, break);
4826                         string const orientation = cmd.getArg(0);
4827                         d.splitter_->setOrientation(orientation == "vertical"
4828                                 ? Qt::Vertical : Qt::Horizontal);
4829                         TabWorkArea * twa = addTabWorkArea();
4830                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4831                         setCurrentWorkArea(wa);
4832                         break;
4833                 }
4834
4835                 case LFUN_TAB_GROUP_NEXT:
4836                         gotoNextTabWorkArea(NEXT);
4837                         break;
4838
4839                 case LFUN_TAB_GROUP_PREVIOUS:
4840                         gotoNextTabWorkArea(PREV);
4841                         break;
4842
4843                 case LFUN_TAB_GROUP_CLOSE:
4844                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4845                                 closeTabWorkArea(twa);
4846                                 d.current_work_area_ = nullptr;
4847                                 twa = d.currentTabWorkArea();
4848                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4849                                 if (twa) {
4850                                         // Make sure the work area is up to date.
4851                                         setCurrentWorkArea(twa->currentWorkArea());
4852                                 } else {
4853                                         setCurrentWorkArea(nullptr);
4854                                 }
4855                         }
4856                         break;
4857
4858                 case LFUN_VIEW_CLOSE:
4859                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4860                                 closeWorkArea(twa->currentWorkArea());
4861                                 d.current_work_area_ = nullptr;
4862                                 twa = d.currentTabWorkArea();
4863                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4864                                 if (twa) {
4865                                         // Make sure the work area is up to date.
4866                                         setCurrentWorkArea(twa->currentWorkArea());
4867                                 } else {
4868                                         setCurrentWorkArea(nullptr);
4869                                 }
4870                         }
4871                         break;
4872
4873                 case LFUN_COMPLETION_INLINE:
4874                         if (d.current_work_area_)
4875                                 d.current_work_area_->completer().showInline();
4876                         break;
4877
4878                 case LFUN_COMPLETION_POPUP:
4879                         if (d.current_work_area_)
4880                                 d.current_work_area_->completer().showPopup();
4881                         break;
4882
4883
4884                 case LFUN_COMPLETE:
4885                         if (d.current_work_area_)
4886                                 d.current_work_area_->completer().tab();
4887                         break;
4888
4889                 case LFUN_COMPLETION_CANCEL:
4890                         if (d.current_work_area_) {
4891                                 if (d.current_work_area_->completer().popupVisible())
4892                                         d.current_work_area_->completer().hidePopup();
4893                                 else
4894                                         d.current_work_area_->completer().hideInline();
4895                         }
4896                         break;
4897
4898                 case LFUN_COMPLETION_ACCEPT:
4899                         if (d.current_work_area_)
4900                                 d.current_work_area_->completer().activate();
4901                         break;
4902
4903                 case LFUN_BUFFER_ZOOM_IN:
4904                 case LFUN_BUFFER_ZOOM_OUT:
4905                 case LFUN_BUFFER_ZOOM: {
4906                         zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4907
4908                         // Actual zoom value: default zoom + fractional extra value
4909                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4910                         zoom = min(max(zoom, zoom_min_), zoom_max_);
4911
4912                         setCurrentZoom(zoom);
4913
4914                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4915                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4916
4917                         guiApp->fontLoader().update();
4918                         // Regenerate instant previews
4919                         if (lyxrc.preview != LyXRC::PREVIEW_OFF
4920                             && doc_buffer && doc_buffer->loader())
4921                                 doc_buffer->loader()->refreshPreviews();
4922                         dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4923                         break;
4924                 }
4925
4926                 case LFUN_VC_REGISTER:
4927                 case LFUN_VC_RENAME:
4928                 case LFUN_VC_COPY:
4929                 case LFUN_VC_CHECK_IN:
4930                 case LFUN_VC_CHECK_OUT:
4931                 case LFUN_VC_REPO_UPDATE:
4932                 case LFUN_VC_LOCKING_TOGGLE:
4933                 case LFUN_VC_REVERT:
4934                 case LFUN_VC_UNDO_LAST:
4935                 case LFUN_VC_COMMAND:
4936                 case LFUN_VC_COMPARE:
4937                         dispatchVC(cmd, dr);
4938                         break;
4939
4940                 case LFUN_SERVER_GOTO_FILE_ROW:
4941                         if(goToFileRow(to_utf8(cmd.argument())))
4942                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4943                         break;
4944
4945                 case LFUN_LYX_ACTIVATE:
4946                         activateWindow();
4947                         break;
4948
4949                 case LFUN_WINDOW_RAISE:
4950                         raise();
4951                         activateWindow();
4952                         showNormal();
4953                         break;
4954
4955                 case LFUN_FORWARD_SEARCH: {
4956                         // it seems safe to assume we have a document buffer, since
4957                         // getStatus wants one.
4958                         LASSERT(doc_buffer, break);
4959                         Buffer const * doc_master = doc_buffer->masterBuffer();
4960                         FileName const path(doc_master->temppath());
4961                         string const texname = doc_master->isChild(doc_buffer)
4962                                 ? DocFileName(changeExtension(
4963                                         doc_buffer->absFileName(),
4964                                                 "tex")).mangledFileName()
4965                                 : doc_buffer->latexName();
4966                         string const fulltexname =
4967                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4968                         string const mastername =
4969                                 removeExtension(doc_master->latexName());
4970                         FileName const dviname(addName(path.absFileName(),
4971                                         addExtension(mastername, "dvi")));
4972                         FileName const pdfname(addName(path.absFileName(),
4973                                         addExtension(mastername, "pdf")));
4974                         bool const have_dvi = dviname.exists();
4975                         bool const have_pdf = pdfname.exists();
4976                         if (!have_dvi && !have_pdf) {
4977                                 dr.setMessage(_("Please, preview the document first."));
4978                                 break;
4979                         }
4980                         bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4981                         bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4982                         string outname = dviname.onlyFileName();
4983                         string command = lyxrc.forward_search_dvi;
4984                         if ((!goto_dvi || goto_pdf) &&
4985                             pdfname.lastModified() > dviname.lastModified()) {
4986                                 outname = pdfname.onlyFileName();
4987                                 command = lyxrc.forward_search_pdf;
4988                         }
4989
4990                         DocIterator cur = bv->cursor();
4991                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4992                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4993                                    << " cur:" << cur);
4994                         if (row == -1 || command.empty()) {
4995                                 dr.setMessage(_("Couldn't proceed."));
4996                                 break;
4997                         }
4998                         string texrow = convert<string>(row);
4999
5000                         command = subst(command, "$$n", texrow);
5001                         command = subst(command, "$$f", fulltexname);
5002                         command = subst(command, "$$t", texname);
5003                         command = subst(command, "$$o", outname);
5004
5005                         volatile PathChanger p(path);
5006                         Systemcall one;
5007                         one.startscript(Systemcall::DontWait, command);
5008                         break;
5009                 }
5010
5011                 case LFUN_SPELLING_CONTINUOUSLY:
5012                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5013                         dr.screenUpdate(Update::Force);
5014                         break;
5015
5016                 case LFUN_CITATION_OPEN: {
5017                         string pdfv, psv;
5018                         if (theFormats().getFormat("pdf"))
5019                                 pdfv = theFormats().getFormat("pdf")->viewer();
5020                         if (theFormats().getFormat("ps"))
5021                                 psv = theFormats().getFormat("ps")->viewer();
5022                         frontend::showTarget(argument, pdfv, psv);
5023                         break;
5024                 }
5025
5026                 default:
5027                         // The LFUN must be for one of BufferView, Buffer or Cursor;
5028                         // let's try that:
5029                         dispatchToBufferView(cmd, dr);
5030                         break;
5031         }
5032
5033         // Need to update bv because many LFUNs here might have destroyed it
5034         bv = currentBufferView();
5035
5036         // Clear non-empty selections
5037         // (e.g. from a "char-forward-select" followed by "char-backward-select")
5038         if (bv) {
5039                 Cursor & cur = bv->cursor();
5040                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5041                         cur.clearSelection();
5042                 }
5043         }
5044 }
5045
5046
5047 bool GuiView::lfunUiToggle(string const & ui_component)
5048 {
5049         if (ui_component == "scrollbar") {
5050                 // hide() is of no help
5051                 if (d.current_work_area_->verticalScrollBarPolicy() ==
5052                         Qt::ScrollBarAlwaysOff)
5053
5054                         d.current_work_area_->setVerticalScrollBarPolicy(
5055                                 Qt::ScrollBarAsNeeded);
5056                 else
5057                         d.current_work_area_->setVerticalScrollBarPolicy(
5058                                 Qt::ScrollBarAlwaysOff);
5059         } else if (ui_component == "statusbar") {
5060                 statusBar()->setVisible(!statusBar()->isVisible());
5061         } else if (ui_component == "menubar") {
5062                 menuBar()->setVisible(!menuBar()->isVisible());
5063         } else if (ui_component == "zoomlevel") {
5064                 zoom_value_->setVisible(!zoom_value_->isVisible());
5065         } else if (ui_component == "zoomslider") {
5066                 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5067                 zoom_in_->setVisible(zoom_slider_->isVisible());
5068                 zoom_out_->setVisible(zoom_slider_->isVisible());
5069         } else if (ui_component == "statistics-w") {
5070                 word_count_enabled_ = !word_count_enabled_;
5071                 if (statsEnabled())
5072                         showStats();
5073         } else if (ui_component == "statistics-cb") {
5074                 char_count_enabled_ = !char_count_enabled_;
5075                 if (statsEnabled())
5076                         showStats();
5077         } else if (ui_component == "statistics-c") {
5078                 char_nb_count_enabled_ = !char_nb_count_enabled_;
5079                 if (statsEnabled())
5080                         showStats();
5081         } else if (ui_component == "frame") {
5082                 int const l = contentsMargins().left();
5083
5084                 //are the frames in default state?
5085                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5086                 if (l == 0) {
5087 #if QT_VERSION >  0x050903
5088                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5089 #endif
5090                         setContentsMargins(-2, -2, -2, -2);
5091                 } else {
5092 #if QT_VERSION >  0x050903
5093                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5094 #endif
5095                         setContentsMargins(0, 0, 0, 0);
5096                 }
5097         } else
5098         if (ui_component == "fullscreen") {
5099                 toggleFullScreen();
5100         } else
5101                 return false;
5102         stat_counts_->setVisible(statsEnabled());
5103         return true;
5104 }
5105
5106
5107 void GuiView::cancelExport()
5108 {
5109         Systemcall::killscript();
5110         // stop busy signal immediately so that in the subsequent
5111         // "Export canceled" prompt the status bar icons are accurate.
5112         Q_EMIT scriptKilled();
5113 }
5114
5115
5116 void GuiView::toggleFullScreen()
5117 {
5118         setWindowState(windowState() ^ Qt::WindowFullScreen);
5119 }
5120
5121
5122 Buffer const * GuiView::updateInset(Inset const * inset)
5123 {
5124         if (!inset)
5125                 return nullptr;
5126
5127         Buffer const * inset_buffer = &(inset->buffer());
5128
5129         for (int i = 0; i != d.splitter_->count(); ++i) {
5130                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5131                 if (!wa)
5132                         continue;
5133                 Buffer const * buffer = &(wa->bufferView().buffer());
5134                 if (inset_buffer == buffer)
5135                         wa->scheduleRedraw(true);
5136         }
5137         return inset_buffer;
5138 }
5139
5140
5141 void GuiView::restartCaret()
5142 {
5143         /* When we move around, or type, it's nice to be able to see
5144          * the caret immediately after the keypress.
5145          */
5146         if (d.current_work_area_)
5147                 d.current_work_area_->startBlinkingCaret();
5148
5149         // Take this occasion to update the other GUI elements.
5150         updateDialogs();
5151         updateStatusBar();
5152 }
5153
5154
5155 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5156 {
5157         if (d.current_work_area_)
5158                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5159 }
5160
5161 namespace {
5162
5163 // This list should be kept in sync with the list of insets in
5164 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
5165 // dialog should have the same name as the inset.
5166 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5167 // docs in LyXAction.cpp.
5168
5169 char const * const dialognames[] = {
5170
5171 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5172 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5173 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5174 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5175 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5176 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5177 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5178 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5179
5180 char const * const * const end_dialognames =
5181         dialognames + (sizeof(dialognames) / sizeof(char *));
5182
5183 class cmpCStr {
5184 public:
5185         cmpCStr(char const * name) : name_(name) {}
5186         bool operator()(char const * other) {
5187                 return strcmp(other, name_) == 0;
5188         }
5189 private:
5190         char const * name_;
5191 };
5192
5193
5194 bool isValidName(string const & name)
5195 {
5196         return find_if(dialognames, end_dialognames,
5197                 cmpCStr(name.c_str())) != end_dialognames;
5198 }
5199
5200 } // namespace
5201
5202
5203 void GuiView::resetDialogs()
5204 {
5205         // Make sure that no LFUN uses any GuiView.
5206         guiApp->setCurrentView(nullptr);
5207         saveLayout();
5208         saveUISettings();
5209         menuBar()->clear();
5210         constructToolbars();
5211         guiApp->menus().fillMenuBar(menuBar(), this, false);
5212         d.layout_->updateContents(true);
5213         // Now update controls with current buffer.
5214         guiApp->setCurrentView(this);
5215         restoreLayout();
5216         restartCaret();
5217 }
5218
5219
5220 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5221 {
5222         for (QObject * child: widget->children()) {
5223                 if (child->inherits("QGroupBox")) {
5224                         QGroupBox * box = (QGroupBox*) child;
5225                         box->setFlat(flag);
5226                 } else {
5227                         flatGroupBoxes(child, flag);
5228                 }
5229         }
5230 }
5231
5232
5233 Dialog * GuiView::find(string const & name, bool hide_it) const
5234 {
5235         if (!isValidName(name))
5236                 return nullptr;
5237
5238         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5239
5240         if (it != d.dialogs_.end()) {
5241                 if (hide_it)
5242                         it->second->hideView();
5243                 return it->second.get();
5244         }
5245         return nullptr;
5246 }
5247
5248
5249 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5250 {
5251         Dialog * dialog = find(name, hide_it);
5252         if (dialog != nullptr)
5253                 return dialog;
5254
5255         dialog = build(name);
5256         d.dialogs_[name].reset(dialog);
5257         // Force a uniform style for group boxes
5258         // On Mac non-flat works better, on Linux flat is standard
5259         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5260         if (lyxrc.allow_geometry_session)
5261                 dialog->restoreSession();
5262         if (hide_it)
5263                 dialog->hideView();
5264         return dialog;
5265 }
5266
5267
5268 void GuiView::showDialog(string const & name, string const & sdata,
5269         Inset * inset)
5270 {
5271         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5272 }
5273
5274
5275 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5276         Inset * inset)
5277 {
5278         if (d.in_show_)
5279                 return;
5280
5281         const string name = fromqstr(qname);
5282         const string sdata = fromqstr(qdata);
5283
5284         d.in_show_ = true;
5285         try {
5286                 Dialog * dialog = findOrBuild(name, false);
5287                 if (dialog) {
5288                         bool const visible = dialog->isVisibleView();
5289                         dialog->showData(sdata);
5290                         if (currentBufferView())
5291                                 currentBufferView()->editInset(name, inset);
5292                         // We only set the focus to the new dialog if it was not yet
5293                         // visible in order not to change the existing previous behaviour
5294                         if (visible) {
5295                                 // activateWindow is needed for floating dockviews
5296                                 dialog->asQWidget()->raise();
5297                                 dialog->asQWidget()->activateWindow();
5298                                 if (dialog->wantInitialFocus())
5299                                         dialog->asQWidget()->setFocus();
5300                         }
5301                 }
5302         }
5303         catch (ExceptionMessage const &) {
5304                 d.in_show_ = false;
5305                 throw;
5306         }
5307         d.in_show_ = false;
5308 }
5309
5310
5311 bool GuiView::isDialogVisible(string const & name) const
5312 {
5313         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5314         if (it == d.dialogs_.end())
5315                 return false;
5316         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5317 }
5318
5319
5320 void GuiView::hideDialog(string const & name, Inset * inset)
5321 {
5322         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5323         if (it == d.dialogs_.end())
5324                 return;
5325
5326         if (inset) {
5327                 if (!currentBufferView())
5328                         return;
5329                 if (inset != currentBufferView()->editedInset(name))
5330                         return;
5331         }
5332
5333         Dialog * const dialog = it->second.get();
5334         if (dialog->isVisibleView())
5335                 dialog->hideView();
5336         if (currentBufferView())
5337                 currentBufferView()->editInset(name, nullptr);
5338 }
5339
5340
5341 void GuiView::disconnectDialog(string const & name)
5342 {
5343         if (!isValidName(name))
5344                 return;
5345         if (currentBufferView())
5346                 currentBufferView()->editInset(name, nullptr);
5347 }
5348
5349
5350 void GuiView::hideAll() const
5351 {
5352         for(auto const & dlg_p : d.dialogs_)
5353                 dlg_p.second->hideView();
5354 }
5355
5356
5357 void GuiView::updateDialogs()
5358 {
5359         for(auto const & dlg_p : d.dialogs_) {
5360                 Dialog * dialog = dlg_p.second.get();
5361                 if (dialog) {
5362                         if (dialog->needBufferOpen() && !documentBufferView())
5363                                 hideDialog(fromqstr(dialog->name()), nullptr);
5364                         else if (dialog->isVisibleView())
5365                                 dialog->checkStatus();
5366                 }
5367         }
5368         updateToolbars();
5369         updateLayoutList();
5370 }
5371
5372
5373 Dialog * GuiView::build(string const & name)
5374 {
5375         return createDialog(*this, name);
5376 }
5377
5378
5379 SEMenu::SEMenu(QWidget * parent)
5380 {
5381         QAction * action = addAction(qt_("Disable Shell Escape"));
5382         connect(action, SIGNAL(triggered()),
5383                 parent, SLOT(disableShellEscape()));
5384 }
5385
5386
5387 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5388 {
5389         if (event->button() == Qt::LeftButton) {
5390         Q_EMIT pressed();
5391     }
5392 }
5393
5394 } // namespace frontend
5395 } // namespace lyx
5396
5397 #include "moc_GuiView.cpp"