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