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