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