]> git.lyx.org Git - features.git/blob - src/frontends/qt/GuiView.cpp
8753a637f029cef81891be318aa508a6e6c55fe3
[features.git] / src / frontends / qt / GuiView.cpp
1 /**
2  * \file GuiView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
18 #include "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                         setContentsMargins(-2, -2, -2, -2);
1403                         // bug 5274
1404                         hideDialogs("prefs", nullptr);
1405                 } else if (ofstate && !nfstate) {
1406                         LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1407                         // switch back from full-screen state
1408                         if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1409                                 statusBar()->show();
1410                         if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1411                                 menuBar()->show();
1412                         if (lyxrc.full_screen_toolbars) {
1413                                 for (auto const & tb_p  : d.toolbars_)
1414                                         if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1415                                                 tb_p.second->show();
1416                                 //updateToolbars();
1417                         }
1418                         for (int i = 0; i != d.splitter_->count(); ++i)
1419                                 d.tabWorkArea(i)->setFullScreen(false);
1420                         setContentsMargins(0, 0, 0, 0);
1421                 }
1422                 return result;
1423                 }
1424         case QEvent::WindowActivate: {
1425                 GuiView * old_view = guiApp->currentView();
1426                 if (this == old_view) {
1427                         setFocus();
1428                         return QMainWindow::event(e);
1429                 }
1430                 if (old_view && old_view->currentBufferView()) {
1431                         // save current selection to the selection buffer to allow
1432                         // middle-button paste in this window.
1433                         cap::saveSelection(old_view->currentBufferView()->cursor());
1434                 }
1435                 guiApp->setCurrentView(this);
1436                 if (d.current_work_area_)
1437                         on_currentWorkAreaChanged(d.current_work_area_);
1438                 else
1439                         resetWindowTitle();
1440                 setFocus();
1441                 return QMainWindow::event(e);
1442         }
1443
1444         case QEvent::ShortcutOverride: {
1445                 // See bug 4888
1446                 if (isFullScreen() && menuBar()->isHidden()) {
1447                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1448                         // FIXME: we should also try to detect special LyX shortcut such as
1449                         // Alt-P and Alt-M. Right now there is a hack in
1450                         // GuiWorkArea::processKeySym() that hides again the menubar for
1451                         // those cases.
1452                         if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1453                                 menuBar()->show();
1454                                 return QMainWindow::event(e);
1455                         }
1456                 }
1457                 return QMainWindow::event(e);
1458         }
1459
1460         default:
1461                 return QMainWindow::event(e);
1462         }
1463 }
1464
1465 void GuiView::resetWindowTitle()
1466 {
1467         setWindowTitle(qt_("LyX"));
1468 }
1469
1470 bool GuiView::focusNextPrevChild(bool /*next*/)
1471 {
1472         setFocus();
1473         return true;
1474 }
1475
1476
1477 bool GuiView::busy() const
1478 {
1479         return busy_ > 0;
1480 }
1481
1482
1483 void GuiView::setBusy(bool busy)
1484 {
1485         bool const busy_before = busy_ > 0;
1486         busy ? ++busy_ : --busy_;
1487         if ((busy_ > 0) == busy_before)
1488                 // busy state didn't change
1489                 return;
1490
1491         if (busy) {
1492                 QApplication::setOverrideCursor(Qt::WaitCursor);
1493                 return;
1494         }
1495         QApplication::restoreOverrideCursor();
1496         updateLayoutList();
1497 }
1498
1499
1500 void GuiView::resetCommandExecute()
1501 {
1502         command_execute_ = false;
1503         updateToolbars();
1504 }
1505
1506
1507 double GuiView::pixelRatio() const
1508 {
1509 #if QT_VERSION >= 0x050000
1510         return qt_scale_factor * devicePixelRatio();
1511 #else
1512         return 1.0;
1513 #endif
1514 }
1515
1516
1517 GuiWorkArea * GuiView::workArea(int index)
1518 {
1519         if (TabWorkArea * twa = d.currentTabWorkArea())
1520                 if (index < twa->count())
1521                         return twa->workArea(index);
1522         return nullptr;
1523 }
1524
1525
1526 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1527 {
1528         if (currentWorkArea()
1529                 && &currentWorkArea()->bufferView().buffer() == &buffer)
1530                 return currentWorkArea();
1531         if (TabWorkArea * twa = d.currentTabWorkArea())
1532                 return twa->workArea(buffer);
1533         return nullptr;
1534 }
1535
1536
1537 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1538 {
1539         // Automatically create a TabWorkArea if there are none yet.
1540         TabWorkArea * tab_widget = d.splitter_->count()
1541                 ? d.currentTabWorkArea() : addTabWorkArea();
1542         return tab_widget->addWorkArea(buffer, *this);
1543 }
1544
1545
1546 TabWorkArea * GuiView::addTabWorkArea()
1547 {
1548         TabWorkArea * twa = new TabWorkArea;
1549         QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1550                 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1551         QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1552                          this, SLOT(on_lastWorkAreaRemoved()));
1553
1554         d.splitter_->addWidget(twa);
1555         d.stack_widget_->setCurrentWidget(d.splitter_);
1556         return twa;
1557 }
1558
1559
1560 GuiWorkArea const * GuiView::currentWorkArea() const
1561 {
1562         return d.current_work_area_;
1563 }
1564
1565
1566 GuiWorkArea * GuiView::currentWorkArea()
1567 {
1568         return d.current_work_area_;
1569 }
1570
1571
1572 GuiWorkArea const * GuiView::currentMainWorkArea() const
1573 {
1574         if (!d.currentTabWorkArea())
1575                 return nullptr;
1576         return d.currentTabWorkArea()->currentWorkArea();
1577 }
1578
1579
1580 GuiWorkArea * GuiView::currentMainWorkArea()
1581 {
1582         if (!d.currentTabWorkArea())
1583                 return nullptr;
1584         return d.currentTabWorkArea()->currentWorkArea();
1585 }
1586
1587
1588 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1589 {
1590         LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1591         if (!wa) {
1592                 d.current_work_area_ = nullptr;
1593                 d.setBackground();
1594                 Q_EMIT bufferViewChanged();
1595                 return;
1596         }
1597
1598         // FIXME: I've no clue why this is here and why it accesses
1599         //  theGuiApp()->currentView, which might be 0 (bug 6464).
1600         //  See also 27525 (vfr).
1601         if (theGuiApp()->currentView() == this
1602                   && theGuiApp()->currentView()->currentWorkArea() == wa)
1603                 return;
1604
1605         if (currentBufferView())
1606                 cap::saveSelection(currentBufferView()->cursor());
1607
1608         theGuiApp()->setCurrentView(this);
1609         d.current_work_area_ = wa;
1610
1611         // We need to reset this now, because it will need to be
1612         // right if the tabWorkArea gets reset in the for loop. We
1613         // will change it back if we aren't in that case.
1614         GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1615         d.current_main_work_area_ = wa;
1616
1617         for (int i = 0; i != d.splitter_->count(); ++i) {
1618                 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1619                         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1620                                 << ", Current main wa: " << currentMainWorkArea());
1621                         return;
1622                 }
1623         }
1624
1625         d.current_main_work_area_ = old_cmwa;
1626
1627         LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1628         on_currentWorkAreaChanged(wa);
1629         BufferView & bv = wa->bufferView();
1630         bv.cursor().fixIfBroken();
1631         bv.updateMetrics();
1632         wa->setUpdatesEnabled(true);
1633         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1634 }
1635
1636
1637 void GuiView::removeWorkArea(GuiWorkArea * wa)
1638 {
1639         LASSERT(wa, return);
1640         if (wa == d.current_work_area_) {
1641                 disconnectBuffer();
1642                 disconnectBufferView();
1643                 d.current_work_area_ = nullptr;
1644                 d.current_main_work_area_ = nullptr;
1645         }
1646
1647         bool found_twa = false;
1648         for (int i = 0; i != d.splitter_->count(); ++i) {
1649                 TabWorkArea * twa = d.tabWorkArea(i);
1650                 if (twa->removeWorkArea(wa)) {
1651                         // Found in this tab group, and deleted the GuiWorkArea.
1652                         found_twa = true;
1653                         if (twa->count() != 0) {
1654                                 if (d.current_work_area_ == nullptr)
1655                                         // This means that we are closing the current GuiWorkArea, so
1656                                         // switch to the next GuiWorkArea in the found TabWorkArea.
1657                                         setCurrentWorkArea(twa->currentWorkArea());
1658                         } else {
1659                                 // No more WorkAreas in this tab group, so delete it.
1660                                 delete twa;
1661                         }
1662                         break;
1663                 }
1664         }
1665
1666         // It is not a tabbed work area (i.e., the search work area), so it
1667         // should be deleted by other means.
1668         LASSERT(found_twa, return);
1669
1670         if (d.current_work_area_ == nullptr) {
1671                 if (d.splitter_->count() != 0) {
1672                         TabWorkArea * twa = d.currentTabWorkArea();
1673                         setCurrentWorkArea(twa->currentWorkArea());
1674                 } else {
1675                         // No more work areas, switch to the background widget.
1676                         setCurrentWorkArea(nullptr);
1677                 }
1678         }
1679 }
1680
1681
1682 LayoutBox * GuiView::getLayoutDialog() const
1683 {
1684         return d.layout_;
1685 }
1686
1687
1688 void GuiView::updateLayoutList()
1689 {
1690         if (d.layout_)
1691                 d.layout_->updateContents(false);
1692 }
1693
1694
1695 void GuiView::updateToolbars()
1696 {
1697         if (d.current_work_area_) {
1698                 int context = 0;
1699                 if (d.current_work_area_->bufferView().cursor().inMathed()
1700                         && !d.current_work_area_->bufferView().cursor().inRegexped())
1701                         context |= Toolbars::MATH;
1702                 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1703                         context |= Toolbars::TABLE;
1704                 if (currentBufferView()->buffer().areChangesPresent()
1705                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1706                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1707                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1708                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1709                         context |= Toolbars::REVIEW;
1710                 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1711                         context |= Toolbars::MATHMACROTEMPLATE;
1712                 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1713                         context |= Toolbars::IPA;
1714                 if (command_execute_)
1715                         context |= Toolbars::MINIBUFFER;
1716                 if (minibuffer_focus_) {
1717                         context |= Toolbars::MINIBUFFER_FOCUS;
1718                         minibuffer_focus_ = false;
1719                 }
1720
1721                 for (auto const & tb_p : d.toolbars_)
1722                         tb_p.second->update(context);
1723         } else
1724                 for (auto const & tb_p : d.toolbars_)
1725                         tb_p.second->update();
1726 }
1727
1728
1729 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1730 {
1731         LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1732         LASSERT(newBuffer, return);
1733
1734         GuiWorkArea * wa = workArea(*newBuffer);
1735         if (wa == nullptr) {
1736                 setBusy(true);
1737                 newBuffer->masterBuffer()->updateBuffer();
1738                 setBusy(false);
1739                 wa = addWorkArea(*newBuffer);
1740                 // scroll to the position when the BufferView was last closed
1741                 if (lyxrc.use_lastfilepos) {
1742                         LastFilePosSection::FilePos filepos =
1743                                 theSession().lastFilePos().load(newBuffer->fileName());
1744                         wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1745                 }
1746         } else {
1747                 //Disconnect the old buffer...there's no new one.
1748                 disconnectBuffer();
1749         }
1750         connectBuffer(*newBuffer);
1751         connectBufferView(wa->bufferView());
1752         if (switch_to)
1753                 setCurrentWorkArea(wa);
1754 }
1755
1756
1757 void GuiView::connectBuffer(Buffer & buf)
1758 {
1759         buf.setGuiDelegate(this);
1760 }
1761
1762
1763 void GuiView::disconnectBuffer()
1764 {
1765         if (d.current_work_area_)
1766                 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1767 }
1768
1769
1770 void GuiView::connectBufferView(BufferView & bv)
1771 {
1772         bv.setGuiDelegate(this);
1773 }
1774
1775
1776 void GuiView::disconnectBufferView()
1777 {
1778         if (d.current_work_area_)
1779                 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1780 }
1781
1782
1783 void GuiView::errors(string const & error_type, bool from_master)
1784 {
1785         BufferView const * const bv = currentBufferView();
1786         if (!bv)
1787                 return;
1788
1789         ErrorList const & el = from_master ?
1790                 bv->buffer().masterBuffer()->errorList(error_type) :
1791                 bv->buffer().errorList(error_type);
1792
1793         if (el.empty())
1794                 return;
1795
1796         string err = error_type;
1797         if (from_master)
1798                 err = "from_master|" + error_type;
1799         showDialog("errorlist", err);
1800 }
1801
1802
1803 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1804 {
1805         d.toc_models_.updateItem(toqstr(type), dit);
1806 }
1807
1808
1809 void GuiView::structureChanged()
1810 {
1811         // This is called from the Buffer, which has no way to ensure that cursors
1812         // in BufferView remain valid.
1813         if (documentBufferView())
1814                 documentBufferView()->cursor().sanitize();
1815         // FIXME: This is slightly expensive, though less than the tocBackend update
1816         // (#9880). This also resets the view in the Toc Widget (#6675).
1817         d.toc_models_.reset(documentBufferView());
1818         // Navigator needs more than a simple update in this case. It needs to be
1819         // rebuilt.
1820         updateDialog("toc", "");
1821 }
1822
1823
1824 void GuiView::updateDialog(string const & name, string const & sdata)
1825 {
1826         if (!isDialogVisible(name))
1827                 return;
1828
1829         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1830         if (it == d.dialogs_.end())
1831                 return;
1832
1833         Dialog * const dialog = it->second.get();
1834         if (dialog->isVisibleView())
1835                 dialog->initialiseParams(sdata);
1836 }
1837
1838
1839 BufferView * GuiView::documentBufferView()
1840 {
1841         return currentMainWorkArea()
1842                 ? &currentMainWorkArea()->bufferView()
1843                 : nullptr;
1844 }
1845
1846
1847 BufferView const * GuiView::documentBufferView() const
1848 {
1849         return currentMainWorkArea()
1850                 ? &currentMainWorkArea()->bufferView()
1851                 : nullptr;
1852 }
1853
1854
1855 BufferView * GuiView::currentBufferView()
1856 {
1857         return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1858 }
1859
1860
1861 BufferView const * GuiView::currentBufferView() const
1862 {
1863         return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1864 }
1865
1866
1867 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1868         Buffer const * orig, Buffer * clone)
1869 {
1870         bool const success = clone->autoSave();
1871         delete clone;
1872         busyBuffers.remove(orig);
1873         return success
1874                 ? _("Automatic save done.")
1875                 : _("Automatic save failed!");
1876 }
1877
1878
1879 void GuiView::autoSave()
1880 {
1881         LYXERR(Debug::INFO, "Running autoSave()");
1882
1883         Buffer * buffer = documentBufferView()
1884                 ? &documentBufferView()->buffer() : nullptr;
1885         if (!buffer) {
1886                 resetAutosaveTimers();
1887                 return;
1888         }
1889
1890         GuiViewPrivate::busyBuffers.insert(buffer);
1891         QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1892                 buffer, buffer->cloneBufferOnly());
1893         d.autosave_watcher_.setFuture(f);
1894         resetAutosaveTimers();
1895 }
1896
1897
1898 void GuiView::resetAutosaveTimers()
1899 {
1900         if (lyxrc.autosave)
1901                 d.autosave_timeout_.restart();
1902 }
1903
1904
1905 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1906 {
1907         bool enable = true;
1908         Buffer * buf = currentBufferView()
1909                 ? &currentBufferView()->buffer() : nullptr;
1910         Buffer * doc_buffer = documentBufferView()
1911                 ? &(documentBufferView()->buffer()) : nullptr;
1912
1913 #ifdef Q_OS_MAC
1914         /* In LyX/Mac, when a dialog is open, the menus of the
1915            application can still be accessed without giving focus to
1916            the main window. In this case, we want to disable the menu
1917            entries that are buffer-related.
1918            This code must not be used on Linux and Windows, since it
1919            would disable buffer-related entries when hovering over the
1920            menu (see bug #9574).
1921          */
1922         if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1923                 buf = 0;
1924                 doc_buffer = 0;
1925         }
1926 #endif
1927
1928         // Check whether we need a buffer
1929         if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1930                 // no, exit directly
1931                 flag.message(from_utf8(N_("Command not allowed with"
1932                                         "out any document open")));
1933                 flag.setEnabled(false);
1934                 return true;
1935         }
1936
1937         if (cmd.origin() == FuncRequest::TOC) {
1938                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1939                 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1940                         flag.setEnabled(false);
1941                 return true;
1942         }
1943
1944         switch(cmd.action()) {
1945         case LFUN_BUFFER_IMPORT:
1946                 break;
1947
1948         case LFUN_MASTER_BUFFER_EXPORT:
1949                 enable = doc_buffer
1950                         && (doc_buffer->parent() != nullptr
1951                             || doc_buffer->hasChildren())
1952                         && !d.processing_thread_watcher_.isRunning()
1953                         // this launches a dialog, which would be in the wrong Buffer
1954                         && !(::lyx::operator==(cmd.argument(), "custom"));
1955                 break;
1956
1957         case LFUN_MASTER_BUFFER_UPDATE:
1958         case LFUN_MASTER_BUFFER_VIEW:
1959                 enable = doc_buffer
1960                         && (doc_buffer->parent() != nullptr
1961                             || doc_buffer->hasChildren())
1962                         && !d.processing_thread_watcher_.isRunning();
1963                 break;
1964
1965         case LFUN_BUFFER_UPDATE:
1966         case LFUN_BUFFER_VIEW: {
1967                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1968                         enable = false;
1969                         break;
1970                 }
1971                 string format = to_utf8(cmd.argument());
1972                 if (cmd.argument().empty())
1973                         format = doc_buffer->params().getDefaultOutputFormat();
1974                 enable = doc_buffer->params().isExportable(format, true);
1975                 break;
1976         }
1977
1978         case LFUN_BUFFER_RELOAD:
1979                 enable = doc_buffer && !doc_buffer->isUnnamed()
1980                         && doc_buffer->fileName().exists()
1981                         && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1982                 break;
1983
1984         case LFUN_BUFFER_RESET_EXPORT:
1985                 enable = doc_buffer != nullptr;
1986                 break;
1987
1988         case LFUN_BUFFER_CHILD_OPEN:
1989                 enable = doc_buffer != nullptr;
1990                 break;
1991
1992         case LFUN_MASTER_BUFFER_FORALL: {
1993                 if (doc_buffer == nullptr) {
1994                         flag.message(from_utf8(N_("Command not allowed without a buffer open")));
1995                         enable = false;
1996                         break;
1997                 }
1998                 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
1999                 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2000                         flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2001                         enable = false;
2002                         break;
2003                 }
2004                 enable = false;
2005                 for (Buffer * buf : doc_buffer->allRelatives()) {
2006                         GuiWorkArea * wa = workArea(*buf);
2007                         if (!wa)
2008                                 continue;
2009                         if (wa->bufferView().getStatus(cmdToPass, flag)) {
2010                                 enable = flag.enabled();
2011                                 break;
2012                         }
2013                 }
2014                 break;
2015         }
2016
2017         case LFUN_BUFFER_WRITE:
2018                 enable = doc_buffer && (doc_buffer->isUnnamed()
2019                                         || (!doc_buffer->isClean()
2020                                             || cmd.argument() == "force"));
2021                 break;
2022
2023         //FIXME: This LFUN should be moved to GuiApplication.
2024         case LFUN_BUFFER_WRITE_ALL: {
2025                 // We enable the command only if there are some modified buffers
2026                 Buffer * first = theBufferList().first();
2027                 enable = false;
2028                 if (!first)
2029                         break;
2030                 Buffer * b = first;
2031                 // We cannot use a for loop as the buffer list is a cycle.
2032                 do {
2033                         if (!b->isClean()) {
2034                                 enable = true;
2035                                 break;
2036                         }
2037                         b = theBufferList().next(b);
2038                 } while (b != first);
2039                 break;
2040         }
2041
2042         case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2043                 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2044                 break;
2045
2046         case LFUN_BUFFER_EXPORT: {
2047                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2048                         enable = false;
2049                         break;
2050                 }
2051                 return doc_buffer->getStatus(cmd, flag);
2052         }
2053
2054         case LFUN_BUFFER_EXPORT_AS:
2055                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2056                         enable = false;
2057                         break;
2058                 }
2059                 // fall through
2060         case LFUN_BUFFER_WRITE_AS:
2061         case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2062                 enable = doc_buffer != nullptr;
2063                 break;
2064
2065         case LFUN_EXPORT_CANCEL:
2066                 enable = d.processing_thread_watcher_.isRunning();
2067                 break;
2068
2069         case LFUN_BUFFER_CLOSE:
2070         case LFUN_VIEW_CLOSE:
2071                 enable = doc_buffer != nullptr;
2072                 break;
2073
2074         case LFUN_BUFFER_CLOSE_ALL:
2075                 enable = theBufferList().last() != theBufferList().first();
2076                 break;
2077
2078         case LFUN_BUFFER_CHKTEX: {
2079                 // hide if we have no checktex command
2080                 if (lyxrc.chktex_command.empty()) {
2081                         flag.setUnknown(true);
2082                         enable = false;
2083                         break;
2084                 }
2085                 if (!doc_buffer || !doc_buffer->params().isLatex()
2086                     || d.processing_thread_watcher_.isRunning()) {
2087                         // grey out, don't hide
2088                         enable = false;
2089                         break;
2090                 }
2091                 enable = true;
2092                 break;
2093         }
2094
2095         case LFUN_VIEW_SPLIT:
2096                 if (cmd.getArg(0) == "vertical")
2097                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2098                                          d.splitter_->orientation() == Qt::Vertical);
2099                 else
2100                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2101                                          d.splitter_->orientation() == Qt::Horizontal);
2102                 break;
2103
2104         case LFUN_TAB_GROUP_CLOSE:
2105                 enable = d.tabWorkAreaCount() > 1;
2106                 break;
2107
2108         case LFUN_DEVEL_MODE_TOGGLE:
2109                 flag.setOnOff(devel_mode_);
2110                 break;
2111
2112         case LFUN_TOOLBAR_TOGGLE: {
2113                 string const name = cmd.getArg(0);
2114                 if (GuiToolbar * t = toolbar(name))
2115                         flag.setOnOff(t->isVisible());
2116                 else {
2117                         enable = false;
2118                         docstring const msg =
2119                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2120                         flag.message(msg);
2121                 }
2122                 break;
2123         }
2124
2125         case LFUN_TOOLBAR_MOVABLE: {
2126                 string const name = cmd.getArg(0);
2127                 // use negation since locked == !movable
2128                 if (name == "*")
2129                         // toolbar name * locks all toolbars
2130                         flag.setOnOff(!toolbarsMovable_);
2131                 else if (GuiToolbar * t = toolbar(name))
2132                         flag.setOnOff(!(t->isMovable()));
2133                 else {
2134                         enable = false;
2135                         docstring const msg =
2136                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2137                         flag.message(msg);
2138                 }
2139                 break;
2140         }
2141
2142         case LFUN_ICON_SIZE:
2143                 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2144                 break;
2145
2146         case LFUN_DROP_LAYOUTS_CHOICE:
2147                 enable = buf != nullptr;
2148                 break;
2149
2150         case LFUN_UI_TOGGLE:
2151                 flag.setOnOff(isFullScreen());
2152                 break;
2153
2154         case LFUN_DIALOG_DISCONNECT_INSET:
2155                 break;
2156
2157         case LFUN_DIALOG_HIDE:
2158                 // FIXME: should we check if the dialog is shown?
2159                 break;
2160
2161         case LFUN_DIALOG_TOGGLE:
2162                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2163                 // to set "enable"
2164                 // fall through
2165         case LFUN_DIALOG_SHOW: {
2166                 string const name = cmd.getArg(0);
2167                 if (!doc_buffer)
2168                         enable = name == "aboutlyx"
2169                                 || name == "file" //FIXME: should be removed.
2170                                 || name == "lyxfiles"
2171                                 || name == "prefs"
2172                                 || name == "texinfo"
2173                                 || name == "progress"
2174                                 || name == "compare";
2175                 else if (name == "character" || name == "symbols"
2176                         || name == "mathdelimiter" || name == "mathmatrix") {
2177                         if (!buf || buf->isReadonly())
2178                                 enable = false;
2179                         else {
2180                                 Cursor const & cur = currentBufferView()->cursor();
2181                                 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2182                         }
2183                 }
2184                 else if (name == "latexlog")
2185                         enable = FileName(doc_buffer->logName()).isReadableFile();
2186                 else if (name == "spellchecker")
2187                         enable = theSpellChecker()
2188                                 && !doc_buffer->isReadonly()
2189                                 && !doc_buffer->text().empty();
2190                 else if (name == "vclog")
2191                         enable = doc_buffer->lyxvc().inUse();
2192                 break;
2193         }
2194
2195         case LFUN_DIALOG_UPDATE: {
2196                 string const name = cmd.getArg(0);
2197                 if (!buf)
2198                         enable = name == "prefs";
2199                 break;
2200         }
2201
2202         case LFUN_COMMAND_EXECUTE:
2203         case LFUN_MESSAGE:
2204         case LFUN_MENU_OPEN:
2205                 // Nothing to check.
2206                 break;
2207
2208         case LFUN_COMPLETION_INLINE:
2209                 if (!d.current_work_area_
2210                         || !d.current_work_area_->completer().inlinePossible(
2211                         currentBufferView()->cursor()))
2212                         enable = false;
2213                 break;
2214
2215         case LFUN_COMPLETION_POPUP:
2216                 if (!d.current_work_area_
2217                         || !d.current_work_area_->completer().popupPossible(
2218                         currentBufferView()->cursor()))
2219                         enable = false;
2220                 break;
2221
2222         case LFUN_COMPLETE:
2223                 if (!d.current_work_area_
2224                         || !d.current_work_area_->completer().inlinePossible(
2225                         currentBufferView()->cursor()))
2226                         enable = false;
2227                 break;
2228
2229         case LFUN_COMPLETION_ACCEPT:
2230                 if (!d.current_work_area_
2231                         || (!d.current_work_area_->completer().popupVisible()
2232                         && !d.current_work_area_->completer().inlineVisible()
2233                         && !d.current_work_area_->completer().completionAvailable()))
2234                         enable = false;
2235                 break;
2236
2237         case LFUN_COMPLETION_CANCEL:
2238                 if (!d.current_work_area_
2239                         || (!d.current_work_area_->completer().popupVisible()
2240                         && !d.current_work_area_->completer().inlineVisible()))
2241                         enable = false;
2242                 break;
2243
2244         case LFUN_BUFFER_ZOOM_OUT:
2245         case LFUN_BUFFER_ZOOM_IN: {
2246                 // only diff between these two is that the default for ZOOM_OUT
2247                 // is a neg. number
2248                 bool const neg_zoom =
2249                         convert<int>(cmd.argument()) < 0 ||
2250                         (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2251                 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2252                         docstring const msg =
2253                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2254                         flag.message(msg);
2255                         enable = false;
2256                 } else
2257                         enable = doc_buffer;
2258                 break;
2259         }
2260
2261         case LFUN_BUFFER_ZOOM: {
2262                 bool const less_than_min_zoom =
2263                         !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2264                 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2265                         docstring const msg =
2266                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2267                         flag.message(msg);
2268                         enable = false;
2269                 }
2270                 else
2271                         enable = doc_buffer;
2272                 break;
2273         }
2274
2275         case LFUN_BUFFER_MOVE_NEXT:
2276         case LFUN_BUFFER_MOVE_PREVIOUS:
2277                 // we do not cycle when moving
2278         case LFUN_BUFFER_NEXT:
2279         case LFUN_BUFFER_PREVIOUS:
2280                 // because we cycle, it doesn't matter whether on first or last
2281                 enable = (d.currentTabWorkArea()->count() > 1);
2282                 break;
2283         case LFUN_BUFFER_SWITCH:
2284                 // toggle on the current buffer, but do not toggle off
2285                 // the other ones (is that a good idea?)
2286                 if (doc_buffer
2287                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2288                         flag.setOnOff(true);
2289                 break;
2290
2291         case LFUN_VC_REGISTER:
2292                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2293                 break;
2294         case LFUN_VC_RENAME:
2295                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2296                 break;
2297         case LFUN_VC_COPY:
2298                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2299                 break;
2300         case LFUN_VC_CHECK_IN:
2301                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2302                 break;
2303         case LFUN_VC_CHECK_OUT:
2304                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2305                 break;
2306         case LFUN_VC_LOCKING_TOGGLE:
2307                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2308                         && doc_buffer->lyxvc().lockingToggleEnabled();
2309                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2310                 break;
2311         case LFUN_VC_REVERT:
2312                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2313                         && !doc_buffer->hasReadonlyFlag();
2314                 break;
2315         case LFUN_VC_UNDO_LAST:
2316                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2317                 break;
2318         case LFUN_VC_REPO_UPDATE:
2319                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2320                 break;
2321         case LFUN_VC_COMMAND: {
2322                 if (cmd.argument().empty())
2323                         enable = false;
2324                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2325                         enable = false;
2326                 break;
2327         }
2328         case LFUN_VC_COMPARE:
2329                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2330                 break;
2331
2332         case LFUN_SERVER_GOTO_FILE_ROW:
2333         case LFUN_LYX_ACTIVATE:
2334         case LFUN_WINDOW_RAISE:
2335                 break;
2336         case LFUN_FORWARD_SEARCH:
2337                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2338                 break;
2339
2340         case LFUN_FILE_INSERT_PLAINTEXT:
2341         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2342                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2343                 break;
2344
2345         case LFUN_SPELLING_CONTINUOUSLY:
2346                 flag.setOnOff(lyxrc.spellcheck_continuously);
2347                 break;
2348
2349         case LFUN_CITATION_OPEN:
2350                 enable = true;
2351                 break;
2352
2353         default:
2354                 return false;
2355         }
2356
2357         if (!enable)
2358                 flag.setEnabled(false);
2359
2360         return true;
2361 }
2362
2363
2364 static FileName selectTemplateFile()
2365 {
2366         FileDialog dlg(qt_("Select template file"));
2367         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2368         dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2369
2370         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2371                                  QStringList(qt_("LyX Documents (*.lyx)")));
2372
2373         if (result.first == FileDialog::Later)
2374                 return FileName();
2375         if (result.second.isEmpty())
2376                 return FileName();
2377         return FileName(fromqstr(result.second));
2378 }
2379
2380
2381 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2382 {
2383         setBusy(true);
2384
2385         Buffer * newBuffer = nullptr;
2386         try {
2387                 newBuffer = checkAndLoadLyXFile(filename);
2388         } catch (ExceptionMessage const &) {
2389                 setBusy(false);
2390                 throw;
2391         }
2392         setBusy(false);
2393
2394         if (!newBuffer) {
2395                 message(_("Document not loaded."));
2396                 return nullptr;
2397         }
2398
2399         setBuffer(newBuffer);
2400         newBuffer->errors("Parse");
2401
2402         if (tolastfiles) {
2403                 theSession().lastFiles().add(filename);
2404                 theSession().writeFile();
2405   }
2406
2407         return newBuffer;
2408 }
2409
2410
2411 void GuiView::openDocument(string const & fname)
2412 {
2413         string initpath = lyxrc.document_path;
2414
2415         if (documentBufferView()) {
2416                 string const trypath = documentBufferView()->buffer().filePath();
2417                 // If directory is writeable, use this as default.
2418                 if (FileName(trypath).isDirWritable())
2419                         initpath = trypath;
2420         }
2421
2422         string filename;
2423
2424         if (fname.empty()) {
2425                 FileDialog dlg(qt_("Select document to open"));
2426                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2427                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2428
2429                 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2430                 FileDialog::Result result =
2431                         dlg.open(toqstr(initpath), filter);
2432
2433                 if (result.first == FileDialog::Later)
2434                         return;
2435
2436                 filename = fromqstr(result.second);
2437
2438                 // check selected filename
2439                 if (filename.empty()) {
2440                         message(_("Canceled."));
2441                         return;
2442                 }
2443         } else
2444                 filename = fname;
2445
2446         // get absolute path of file and add ".lyx" to the filename if
2447         // necessary.
2448         FileName const fullname =
2449                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2450         if (!fullname.empty())
2451                 filename = fullname.absFileName();
2452
2453         if (!fullname.onlyPath().isDirectory()) {
2454                 Alert::warning(_("Invalid filename"),
2455                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2456                                 from_utf8(fullname.absFileName())));
2457                 return;
2458         }
2459
2460         // if the file doesn't exist and isn't already open (bug 6645),
2461         // let the user create one
2462         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2463             !LyXVC::file_not_found_hook(fullname)) {
2464                 // the user specifically chose this name. Believe him.
2465                 Buffer * const b = newFile(filename, string(), true);
2466                 if (b)
2467                         setBuffer(b);
2468                 return;
2469         }
2470
2471         docstring const disp_fn = makeDisplayPath(filename);
2472         message(bformat(_("Opening document %1$s..."), disp_fn));
2473
2474         docstring str2;
2475         Buffer * buf = loadDocument(fullname);
2476         if (buf) {
2477                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2478                 if (buf->lyxvc().inUse())
2479                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2480                                 " " + _("Version control detected.");
2481         } else {
2482                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2483         }
2484         message(str2);
2485 }
2486
2487 // FIXME: clean that
2488 static bool import(GuiView * lv, FileName const & filename,
2489         string const & format, ErrorList & errorList)
2490 {
2491         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2492
2493         string loader_format;
2494         vector<string> loaders = theConverters().loaders();
2495         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2496                 vector<string>::const_iterator it = loaders.begin();
2497                 vector<string>::const_iterator en = loaders.end();
2498                 for (string const & loader : loaders) {
2499                         if (!theConverters().isReachable(format, loader))
2500                                 continue;
2501
2502                         string const tofile =
2503                                 support::changeExtension(filename.absFileName(),
2504                                 theFormats().extension(*it));
2505                         if (theConverters().convert(nullptr, filename, FileName(tofile),
2506                                 filename, format, *it, errorList) != Converters::SUCCESS)
2507                                 return false;
2508                         loader_format = *it;
2509                         break;
2510                 }
2511                 if (loader_format.empty()) {
2512                         frontend::Alert::error(_("Couldn't import file"),
2513                                          bformat(_("No information for importing the format %1$s."),
2514                                          translateIfPossible(theFormats().prettyName(format))));
2515                         return false;
2516                 }
2517         } else
2518                 loader_format = format;
2519
2520         if (loader_format == "lyx") {
2521                 Buffer * buf = lv->loadDocument(lyxfile);
2522                 if (!buf)
2523                         return false;
2524         } else {
2525                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2526                 if (!b)
2527                         return false;
2528                 lv->setBuffer(b);
2529                 bool as_paragraphs = loader_format == "textparagraph";
2530                 string filename2 = (loader_format == format) ? filename.absFileName()
2531                         : support::changeExtension(filename.absFileName(),
2532                                           theFormats().extension(loader_format));
2533                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2534                         as_paragraphs);
2535                 guiApp->setCurrentView(lv);
2536                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2537         }
2538
2539         return true;
2540 }
2541
2542
2543 void GuiView::importDocument(string const & argument)
2544 {
2545         string format;
2546         string filename = split(argument, format, ' ');
2547
2548         LYXERR(Debug::INFO, format << " file: " << filename);
2549
2550         // need user interaction
2551         if (filename.empty()) {
2552                 string initpath = lyxrc.document_path;
2553                 if (documentBufferView()) {
2554                         string const trypath = documentBufferView()->buffer().filePath();
2555                         // If directory is writeable, use this as default.
2556                         if (FileName(trypath).isDirWritable())
2557                                 initpath = trypath;
2558                 }
2559
2560                 docstring const text = bformat(_("Select %1$s file to import"),
2561                         translateIfPossible(theFormats().prettyName(format)));
2562
2563                 FileDialog dlg(toqstr(text));
2564                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2565                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2566
2567                 docstring filter = translateIfPossible(theFormats().prettyName(format));
2568                 filter += " (*.{";
2569                 // FIXME UNICODE
2570                 filter += from_utf8(theFormats().extensions(format));
2571                 filter += "})";
2572
2573                 FileDialog::Result result =
2574                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2575
2576                 if (result.first == FileDialog::Later)
2577                         return;
2578
2579                 filename = fromqstr(result.second);
2580
2581                 // check selected filename
2582                 if (filename.empty())
2583                         message(_("Canceled."));
2584         }
2585
2586         if (filename.empty())
2587                 return;
2588
2589         // get absolute path of file
2590         FileName const fullname(support::makeAbsPath(filename));
2591
2592         // Can happen if the user entered a path into the dialog
2593         // (see bug #7437)
2594         if (fullname.onlyFileName().empty()) {
2595                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2596                                           "Aborting import."),
2597                                         from_utf8(fullname.absFileName()));
2598                 frontend::Alert::error(_("File name error"), msg);
2599                 message(_("Canceled."));
2600                 return;
2601         }
2602
2603
2604         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2605
2606         // Check if the document already is open
2607         Buffer * buf = theBufferList().getBuffer(lyxfile);
2608         if (buf) {
2609                 setBuffer(buf);
2610                 if (!closeBuffer()) {
2611                         message(_("Canceled."));
2612                         return;
2613                 }
2614         }
2615
2616         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2617
2618         // if the file exists already, and we didn't do
2619         // -i lyx thefile.lyx, warn
2620         if (lyxfile.exists() && fullname != lyxfile) {
2621
2622                 docstring text = bformat(_("The document %1$s already exists.\n\n"
2623                         "Do you want to overwrite that document?"), displaypath);
2624                 int const ret = Alert::prompt(_("Overwrite document?"),
2625                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
2626
2627                 if (ret == 1) {
2628                         message(_("Canceled."));
2629                         return;
2630                 }
2631         }
2632
2633         message(bformat(_("Importing %1$s..."), displaypath));
2634         ErrorList errorList;
2635         if (import(this, fullname, format, errorList))
2636                 message(_("imported."));
2637         else
2638                 message(_("file not imported!"));
2639
2640         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2641 }
2642
2643
2644 void GuiView::newDocument(string const & filename, string templatefile,
2645                           bool from_template)
2646 {
2647         FileName initpath(lyxrc.document_path);
2648         if (documentBufferView()) {
2649                 FileName const trypath(documentBufferView()->buffer().filePath());
2650                 // If directory is writeable, use this as default.
2651                 if (trypath.isDirWritable())
2652                         initpath = trypath;
2653         }
2654
2655         if (from_template) {
2656                 if (templatefile.empty())
2657                         templatefile =  selectTemplateFile().absFileName();
2658                 if (templatefile.empty())
2659                         return;
2660         }
2661
2662         Buffer * b;
2663         if (filename.empty())
2664                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2665         else
2666                 b = newFile(filename, templatefile, true);
2667
2668         if (b)
2669                 setBuffer(b);
2670
2671         // If no new document could be created, it is unsure
2672         // whether there is a valid BufferView.
2673         if (currentBufferView())
2674                 // Ensure the cursor is correctly positioned on screen.
2675                 currentBufferView()->showCursor();
2676 }
2677
2678
2679 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2680 {
2681         BufferView * bv = documentBufferView();
2682         if (!bv)
2683                 return;
2684
2685         // FIXME UNICODE
2686         FileName filename(to_utf8(fname));
2687         if (filename.empty()) {
2688                 // Launch a file browser
2689                 // FIXME UNICODE
2690                 string initpath = lyxrc.document_path;
2691                 string const trypath = bv->buffer().filePath();
2692                 // If directory is writeable, use this as default.
2693                 if (FileName(trypath).isDirWritable())
2694                         initpath = trypath;
2695
2696                 // FIXME UNICODE
2697                 FileDialog dlg(qt_("Select LyX document to insert"));
2698                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2699                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2700
2701                 FileDialog::Result result = dlg.open(toqstr(initpath),
2702                                          QStringList(qt_("LyX Documents (*.lyx)")));
2703
2704                 if (result.first == FileDialog::Later)
2705                         return;
2706
2707                 // FIXME UNICODE
2708                 filename.set(fromqstr(result.second));
2709
2710                 // check selected filename
2711                 if (filename.empty()) {
2712                         // emit message signal.
2713                         message(_("Canceled."));
2714                         return;
2715                 }
2716         }
2717
2718         bv->insertLyXFile(filename, ignorelang);
2719         bv->buffer().errors("Parse");
2720 }
2721
2722
2723 string const GuiView::getTemplatesPath(Buffer & b)
2724 {
2725         // We start off with the user's templates path
2726         string result = addPath(package().user_support().absFileName(), "templates");
2727         // Check for the document language
2728         string const langcode = b.params().language->code();
2729         string const shortcode = langcode.substr(0, 2);
2730         if (!langcode.empty() && shortcode != "en") {
2731                 string subpath = addPath(result, shortcode);
2732                 string subpath_long = addPath(result, langcode);
2733                 // If we have a subdirectory for the language already,
2734                 // navigate there
2735                 FileName sp = FileName(subpath);
2736                 if (sp.isDirectory())
2737                         result = subpath;
2738                 else if (FileName(subpath_long).isDirectory())
2739                         result = subpath_long;
2740                 else {
2741                         // Ask whether we should create such a subdirectory
2742                         docstring const text =
2743                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2744                                           "appropriate to the document language (%1$s).\n"
2745                                           "This subdirectory does not exists yet.\n"
2746                                           "Do you want to create it?"),
2747                                         _(b.params().language->display()));
2748                         if (Alert::prompt(_("Create Language Directory?"),
2749                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2750                                 // If the user agreed, we try to create it and report if this failed.
2751                                 if (!sp.createDirectory(0777))
2752                                         Alert::error(_("Subdirectory creation failed!"),
2753                                                      _("Could not create subdirectory.\n"
2754                                                        "The template will be saved in the parent directory."));
2755                                 else
2756                                         result = subpath;
2757                         }
2758                 }
2759         }
2760         // Do we have a layout category?
2761         string const cat = b.params().baseClass() ?
2762                                 b.params().baseClass()->category()
2763                               : string();
2764         if (!cat.empty()) {
2765                 string subpath = addPath(result, cat);
2766                 // If we have a subdirectory for the category already,
2767                 // navigate there
2768                 FileName sp = FileName(subpath);
2769                 if (sp.isDirectory())
2770                         result = subpath;
2771                 else {
2772                         // Ask whether we should create such a subdirectory
2773                         docstring const text =
2774                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2775                                           "appropriate to the layout category (%1$s).\n"
2776                                           "This subdirectory does not exists yet.\n"
2777                                           "Do you want to create it?"),
2778                                         _(cat));
2779                         if (Alert::prompt(_("Create Category Directory?"),
2780                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2781                                 // If the user agreed, we try to create it and report if this failed.
2782                                 if (!sp.createDirectory(0777))
2783                                         Alert::error(_("Subdirectory creation failed!"),
2784                                                      _("Could not create subdirectory.\n"
2785                                                        "The template will be saved in the parent directory."));
2786                                 else
2787                                         result = subpath;
2788                         }
2789                 }
2790         }
2791         return result;
2792 }
2793
2794
2795 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2796 {
2797         FileName fname = b.fileName();
2798         FileName const oldname = fname;
2799         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2800
2801         if (!newname.empty()) {
2802                 // FIXME UNICODE
2803                 if (as_template)
2804                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2805                 else
2806                         fname = support::makeAbsPath(to_utf8(newname),
2807                                                      oldname.onlyPath().absFileName());
2808         } else {
2809                 // Switch to this Buffer.
2810                 setBuffer(&b);
2811
2812                 // No argument? Ask user through dialog.
2813                 // FIXME UNICODE
2814                 QString const title = as_template ? qt_("Choose a filename to save template as")
2815                                                   : qt_("Choose a filename to save document as");
2816                 FileDialog dlg(title);
2817                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2818                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2819
2820                 if (!isLyXFileName(fname.absFileName()))
2821                         fname.changeExtension(".lyx");
2822
2823                 string const path = as_template ?
2824                                         getTemplatesPath(b)
2825                                       : fname.onlyPath().absFileName();
2826                 FileDialog::Result result =
2827                         dlg.save(toqstr(path),
2828                                    QStringList(qt_("LyX Documents (*.lyx)")),
2829                                          toqstr(fname.onlyFileName()));
2830
2831                 if (result.first == FileDialog::Later)
2832                         return false;
2833
2834                 fname.set(fromqstr(result.second));
2835
2836                 if (fname.empty())
2837                         return false;
2838
2839                 if (!isLyXFileName(fname.absFileName()))
2840                         fname.changeExtension(".lyx");
2841         }
2842
2843         // fname is now the new Buffer location.
2844
2845         // if there is already a Buffer open with this name, we do not want
2846         // to have another one. (the second test makes sure we're not just
2847         // trying to overwrite ourselves, which is fine.)
2848         if (theBufferList().exists(fname) && fname != oldname
2849                   && theBufferList().getBuffer(fname) != &b) {
2850                 docstring const text =
2851                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
2852                             "Please close it before attempting to overwrite it.\n"
2853                             "Do you want to choose a new filename?"),
2854                                 from_utf8(fname.absFileName()));
2855                 int const ret = Alert::prompt(_("Chosen File Already Open"),
2856                         text, 0, 1, _("&Rename"), _("&Cancel"));
2857                 switch (ret) {
2858                 case 0: return renameBuffer(b, docstring(), kind);
2859                 case 1: return false;
2860                 }
2861                 //return false;
2862         }
2863
2864         bool const existsLocal = fname.exists();
2865         bool const existsInVC = LyXVC::fileInVC(fname);
2866         if (existsLocal || existsInVC) {
2867                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2868                 if (kind != LV_WRITE_AS && existsInVC) {
2869                         // renaming to a name that is already in VC
2870                         // would not work
2871                         docstring text = bformat(_("The document %1$s "
2872                                         "is already registered.\n\n"
2873                                         "Do you want to choose a new name?"),
2874                                 file);
2875                         docstring const title = (kind == LV_VC_RENAME) ?
2876                                 _("Rename document?") : _("Copy document?");
2877                         docstring const button = (kind == LV_VC_RENAME) ?
2878                                 _("&Rename") : _("&Copy");
2879                         int const ret = Alert::prompt(title, text, 0, 1,
2880                                 button, _("&Cancel"));
2881                         switch (ret) {
2882                         case 0: return renameBuffer(b, docstring(), kind);
2883                         case 1: return false;
2884                         }
2885                 }
2886
2887                 if (existsLocal) {
2888                         docstring text = bformat(_("The document %1$s "
2889                                         "already exists.\n\n"
2890                                         "Do you want to overwrite that document?"),
2891                                 file);
2892                         int const ret = Alert::prompt(_("Overwrite document?"),
2893                                         text, 0, 2, _("&Overwrite"),
2894                                         _("&Rename"), _("&Cancel"));
2895                         switch (ret) {
2896                         case 0: break;
2897                         case 1: return renameBuffer(b, docstring(), kind);
2898                         case 2: return false;
2899                         }
2900                 }
2901         }
2902
2903         switch (kind) {
2904         case LV_VC_RENAME: {
2905                 string msg = b.lyxvc().rename(fname);
2906                 if (msg.empty())
2907                         return false;
2908                 message(from_utf8(msg));
2909                 break;
2910         }
2911         case LV_VC_COPY: {
2912                 string msg = b.lyxvc().copy(fname);
2913                 if (msg.empty())
2914                         return false;
2915                 message(from_utf8(msg));
2916                 break;
2917         }
2918         case LV_WRITE_AS:
2919         case LV_WRITE_AS_TEMPLATE:
2920                 break;
2921         }
2922         // LyXVC created the file already in case of LV_VC_RENAME or
2923         // LV_VC_COPY, but call saveBuffer() nevertheless to get
2924         // relative paths of included stuff right if we moved e.g. from
2925         // /a/b.lyx to /a/c/b.lyx.
2926
2927         bool const saved = saveBuffer(b, fname);
2928         if (saved)
2929                 b.reload();
2930         return saved;
2931 }
2932
2933
2934 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2935 {
2936         FileName fname = b.fileName();
2937
2938         FileDialog dlg(qt_("Choose a filename to export the document as"));
2939         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2940
2941         QStringList types;
2942         QString const anyformat = qt_("Guess from extension (*.*)");
2943         types << anyformat;
2944
2945         vector<Format const *> export_formats;
2946         for (Format const & f : theFormats())
2947                 if (f.documentFormat())
2948                         export_formats.push_back(&f);
2949         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2950         map<QString, string> fmap;
2951         QString filter;
2952         string ext;
2953         for (Format const * f : export_formats) {
2954                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2955                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2956                                                      loc_prettyname,
2957                                                      from_ascii(f->extension())));
2958                 types << loc_filter;
2959                 fmap[loc_filter] = f->name();
2960                 if (from_ascii(f->name()) == iformat) {
2961                         filter = loc_filter;
2962                         ext = f->extension();
2963                 }
2964         }
2965         string ofname = fname.onlyFileName();
2966         if (!ext.empty())
2967                 ofname = support::changeExtension(ofname, ext);
2968         FileDialog::Result result =
2969                 dlg.save(toqstr(fname.onlyPath().absFileName()),
2970                          types,
2971                          toqstr(ofname),
2972                          &filter);
2973         if (result.first != FileDialog::Chosen)
2974                 return false;
2975
2976         string fmt_name;
2977         fname.set(fromqstr(result.second));
2978         if (filter == anyformat)
2979                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2980         else
2981                 fmt_name = fmap[filter];
2982         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2983                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2984
2985         if (fmt_name.empty() || fname.empty())
2986                 return false;
2987
2988         // fname is now the new Buffer location.
2989         if (FileName(fname).exists()) {
2990                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2991                 docstring text = bformat(_("The document %1$s already "
2992                                            "exists.\n\nDo you want to "
2993                                            "overwrite that document?"),
2994                                          file);
2995                 int const ret = Alert::prompt(_("Overwrite document?"),
2996                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2997                 switch (ret) {
2998                 case 0: break;
2999                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3000                 case 2: return false;
3001                 }
3002         }
3003
3004         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3005         DispatchResult dr;
3006         dispatch(cmd, dr);
3007         return dr.dispatched();
3008 }
3009
3010
3011 bool GuiView::saveBuffer(Buffer & b)
3012 {
3013         return saveBuffer(b, FileName());
3014 }
3015
3016
3017 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3018 {
3019         if (workArea(b) && workArea(b)->inDialogMode())
3020                 return true;
3021
3022         if (fn.empty() && b.isUnnamed())
3023                 return renameBuffer(b, docstring());
3024
3025         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3026         if (success) {
3027                 theSession().lastFiles().add(b.fileName());
3028                 theSession().writeFile();
3029                 return true;
3030         }
3031
3032         // Switch to this Buffer.
3033         setBuffer(&b);
3034
3035         // FIXME: we don't tell the user *WHY* the save failed !!
3036         docstring const file = makeDisplayPath(b.absFileName(), 30);
3037         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3038                                    "Do you want to rename the document and "
3039                                    "try again?"), file);
3040         int const ret = Alert::prompt(_("Rename and save?"),
3041                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3042         switch (ret) {
3043         case 0:
3044                 if (!renameBuffer(b, docstring()))
3045                         return false;
3046                 break;
3047         case 1:
3048                 break;
3049         case 2:
3050                 return false;
3051         }
3052
3053         return saveBuffer(b, fn);
3054 }
3055
3056
3057 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3058 {
3059         return closeWorkArea(wa, false);
3060 }
3061
3062
3063 // We only want to close the buffer if it is not visible in other workareas
3064 // of the same view, nor in other views, and if this is not a child
3065 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3066 {
3067         Buffer & buf = wa->bufferView().buffer();
3068
3069         bool last_wa = d.countWorkAreasOf(buf) == 1
3070                 && !inOtherView(buf) && !buf.parent();
3071
3072         bool close_buffer = last_wa;
3073
3074         if (last_wa) {
3075                 if (lyxrc.close_buffer_with_last_view == "yes")
3076                         ; // Nothing to do
3077                 else if (lyxrc.close_buffer_with_last_view == "no")
3078                         close_buffer = false;
3079                 else {
3080                         docstring file;
3081                         if (buf.isUnnamed())
3082                                 file = from_utf8(buf.fileName().onlyFileName());
3083                         else
3084                                 file = buf.fileName().displayName(30);
3085                         docstring const text = bformat(
3086                                 _("Last view on document %1$s is being closed.\n"
3087                                   "Would you like to close or hide the document?\n"
3088                                   "\n"
3089                                   "Hidden documents can be displayed back through\n"
3090                                   "the menu: View->Hidden->...\n"
3091                                   "\n"
3092                                   "To remove this question, set your preference in:\n"
3093                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3094                                 ), file);
3095                         int ret = Alert::prompt(_("Close or hide document?"),
3096                                 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3097                         if (ret == 2)
3098                                 return false;
3099                         close_buffer = (ret == 0);
3100                 }
3101         }
3102
3103         return closeWorkArea(wa, close_buffer);
3104 }
3105
3106
3107 bool GuiView::closeBuffer()
3108 {
3109         GuiWorkArea * wa = currentMainWorkArea();
3110         // coverity complained about this
3111         // it seems unnecessary, but perhaps is worth the check
3112         LASSERT(wa, return false);
3113
3114         setCurrentWorkArea(wa);
3115         Buffer & buf = wa->bufferView().buffer();
3116         return closeWorkArea(wa, !buf.parent());
3117 }
3118
3119
3120 void GuiView::writeSession() const {
3121         GuiWorkArea const * active_wa = currentMainWorkArea();
3122         for (int i = 0; i < d.splitter_->count(); ++i) {
3123                 TabWorkArea * twa = d.tabWorkArea(i);
3124                 for (int j = 0; j < twa->count(); ++j) {
3125                         GuiWorkArea * wa = twa->workArea(j);
3126                         Buffer & buf = wa->bufferView().buffer();
3127                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3128                 }
3129         }
3130 }
3131
3132
3133 bool GuiView::closeBufferAll()
3134 {
3135
3136         for (auto & buf : theBufferList()) {
3137                 if (!saveBufferIfNeeded(*buf, false)) {
3138                         // Closing has been cancelled, so abort.
3139                         return false;
3140                 }
3141         }
3142
3143         // Close the workareas in all other views
3144         QList<int> const ids = guiApp->viewIds();
3145         for (int i = 0; i != ids.size(); ++i) {
3146                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3147                         return false;
3148         }
3149
3150         // Close our own workareas
3151         if (!closeWorkAreaAll())
3152                 return false;
3153
3154         return true;
3155 }
3156
3157
3158 bool GuiView::closeWorkAreaAll()
3159 {
3160         setCurrentWorkArea(currentMainWorkArea());
3161
3162         // We might be in a situation that there is still a tabWorkArea, but
3163         // there are no tabs anymore. This can happen when we get here after a
3164         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3165         // many TabWorkArea's have no documents anymore.
3166         int empty_twa = 0;
3167
3168         // We have to call count() each time, because it can happen that
3169         // more than one splitter will disappear in one iteration (bug 5998).
3170         while (d.splitter_->count() > empty_twa) {
3171                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3172
3173                 if (twa->count() == 0)
3174                         ++empty_twa;
3175                 else {
3176                         setCurrentWorkArea(twa->currentWorkArea());
3177                         if (!closeTabWorkArea(twa))
3178                                 return false;
3179                 }
3180         }
3181         return true;
3182 }
3183
3184
3185 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3186 {
3187         if (!wa)
3188                 return false;
3189
3190         Buffer & buf = wa->bufferView().buffer();
3191
3192         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3193                 Alert::warning(_("Close document"),
3194                         _("Document could not be closed because it is being processed by LyX."));
3195                 return false;
3196         }
3197
3198         if (close_buffer)
3199                 return closeBuffer(buf);
3200         else {
3201                 if (!inMultiTabs(wa))
3202                         if (!saveBufferIfNeeded(buf, true))
3203                                 return false;
3204                 removeWorkArea(wa);
3205                 return true;
3206         }
3207 }
3208
3209
3210 bool GuiView::closeBuffer(Buffer & buf)
3211 {
3212         bool success = true;
3213         for (Buffer * child_buf : buf.getChildren()) {
3214                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3215                         child_buf->setParent(nullptr);
3216                         continue;
3217                 }
3218
3219                 // FIXME: should we look in other tabworkareas?
3220                 // ANSWER: I don't think so. I've tested, and if the child is
3221                 // open in some other window, it closes without a problem.
3222                 GuiWorkArea * child_wa = workArea(*child_buf);
3223                 if (child_wa) {
3224                         if (closing_)
3225                                 // If we are in a close_event all children will be closed in some time,
3226                                 // so no need to do it here. This will ensure that the children end up
3227                                 // in the session file in the correct order. If we close the master
3228                                 // buffer, we can close or release the child buffers here too.
3229                                 continue;
3230                         success = closeWorkArea(child_wa, true);
3231                         if (!success)
3232                                 break;
3233                 } else {
3234                         // In this case the child buffer is open but hidden.
3235                         // Even in this case, children can be dirty (e.g.,
3236                         // after a label change in the master, see #11405).
3237                         // Therefore, check this
3238                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3239                                 // If we are in a close_event all children will be closed in some time,
3240                                 // so no need to do it here. This will ensure that the children end up
3241                                 // in the session file in the correct order. If we close the master
3242                                 // buffer, we can close or release the child buffers here too.
3243                                 continue;
3244                         }
3245                         // Save dirty buffers also if closing_!
3246                         if (saveBufferIfNeeded(*child_buf, false)) {
3247                                 child_buf->removeAutosaveFile();
3248                                 theBufferList().release(child_buf);
3249                         } else {
3250                                 // Saving of dirty children has been cancelled.
3251                                 // Cancel the whole process.
3252                                 success = false;
3253                                 break;
3254                         }
3255                 }
3256         }
3257         if (success) {
3258                 // goto bookmark to update bookmark pit.
3259                 // FIXME: we should update only the bookmarks related to this buffer!
3260                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3261                 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3262                         guiApp->gotoBookmark(i + 1, false, false);
3263
3264                 if (saveBufferIfNeeded(buf, false)) {
3265                         buf.removeAutosaveFile();
3266                         theBufferList().release(&buf);
3267                         return true;
3268                 }
3269         }
3270         // open all children again to avoid a crash because of dangling
3271         // pointers (bug 6603)
3272         buf.updateBuffer();
3273         return false;
3274 }
3275
3276
3277 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3278 {
3279         while (twa == d.currentTabWorkArea()) {
3280                 twa->setCurrentIndex(twa->count() - 1);
3281
3282                 GuiWorkArea * wa = twa->currentWorkArea();
3283                 Buffer & b = wa->bufferView().buffer();
3284
3285                 // We only want to close the buffer if the same buffer is not visible
3286                 // in another view, and if this is not a child and if we are closing
3287                 // a view (not a tabgroup).
3288                 bool const close_buffer =
3289                         !inOtherView(b) && !b.parent() && closing_;
3290
3291                 if (!closeWorkArea(wa, close_buffer))
3292                         return false;
3293         }
3294         return true;
3295 }
3296
3297
3298 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3299 {
3300         if (buf.isClean() || buf.paragraphs().empty())
3301                 return true;
3302
3303         // Switch to this Buffer.
3304         setBuffer(&buf);
3305
3306         docstring file;
3307         bool exists;
3308         // FIXME: Unicode?
3309         if (buf.isUnnamed()) {
3310                 file = from_utf8(buf.fileName().onlyFileName());
3311                 exists = false;
3312         } else {
3313                 FileName filename = buf.fileName();
3314                 filename.refresh();
3315                 file = filename.displayName(30);
3316                 exists = filename.exists();
3317         }
3318
3319         // Bring this window to top before asking questions.
3320         raise();
3321         activateWindow();
3322
3323         int ret;
3324         if (hiding && buf.isUnnamed()) {
3325                 docstring const text = bformat(_("The document %1$s has not been "
3326                                                  "saved yet.\n\nDo you want to save "
3327                                                  "the document?"), file);
3328                 ret = Alert::prompt(_("Save new document?"),
3329                         text, 0, 1, _("&Save"), _("&Cancel"));
3330                 if (ret == 1)
3331                         ++ret;
3332         } else {
3333                 docstring const text = exists ?
3334                         bformat(_("The document %1$s has unsaved changes."
3335                                   "\n\nDo you want to save the document or "
3336                                   "discard the changes?"), file) :
3337                         bformat(_("The document %1$s has not been saved yet."
3338                                   "\n\nDo you want to save the document or "
3339                                   "discard it entirely?"), file);
3340                 docstring const title = exists ?
3341                         _("Save changed document?") : _("Save document?");
3342                 ret = Alert::prompt(title, text, 0, 2,
3343                                     _("&Save"), _("&Discard"), _("&Cancel"));
3344         }
3345
3346         switch (ret) {
3347         case 0:
3348                 if (!saveBuffer(buf))
3349                         return false;
3350                 break;
3351         case 1:
3352                 // If we crash after this we could have no autosave file
3353                 // but I guess this is really improbable (Jug).
3354                 // Sometimes improbable things happen:
3355                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3356                 // buf.removeAutosaveFile();
3357                 if (hiding)
3358                         // revert all changes
3359                         reloadBuffer(buf);
3360                 buf.markClean();
3361                 break;
3362         case 2:
3363                 return false;
3364         }
3365         return true;
3366 }
3367
3368
3369 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3370 {
3371         Buffer & buf = wa->bufferView().buffer();
3372
3373         for (int i = 0; i != d.splitter_->count(); ++i) {
3374                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3375                 if (wa_ && wa_ != wa)
3376                         return true;
3377         }
3378         return inOtherView(buf);
3379 }
3380
3381
3382 bool GuiView::inOtherView(Buffer & buf)
3383 {
3384         QList<int> const ids = guiApp->viewIds();
3385
3386         for (int i = 0; i != ids.size(); ++i) {
3387                 if (id_ == ids[i])
3388                         continue;
3389
3390                 if (guiApp->view(ids[i]).workArea(buf))
3391                         return true;
3392         }
3393         return false;
3394 }
3395
3396
3397 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3398 {
3399         if (!documentBufferView())
3400                 return;
3401
3402         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3403                 Buffer * const curbuf = &documentBufferView()->buffer();
3404                 int nwa = twa->count();
3405                 for (int i = 0; i < nwa; ++i) {
3406                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3407                                 int next_index;
3408                                 if (np == NEXTBUFFER)
3409                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3410                                 else
3411                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3412                                 if (move)
3413                                         twa->moveTab(i, next_index);
3414                                 else
3415                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3416                                 break;
3417                         }
3418                 }
3419         }
3420 }
3421
3422
3423 /// make sure the document is saved
3424 static bool ensureBufferClean(Buffer * buffer)
3425 {
3426         LASSERT(buffer, return false);
3427         if (buffer->isClean() && !buffer->isUnnamed())
3428                 return true;
3429
3430         docstring const file = buffer->fileName().displayName(30);
3431         docstring title;
3432         docstring text;
3433         if (!buffer->isUnnamed()) {
3434                 text = bformat(_("The document %1$s has unsaved "
3435                                                  "changes.\n\nDo you want to save "
3436                                                  "the document?"), file);
3437                 title = _("Save changed document?");
3438
3439         } else {
3440                 text = bformat(_("The document %1$s has not been "
3441                                                  "saved yet.\n\nDo you want to save "
3442                                                  "the document?"), file);
3443                 title = _("Save new document?");
3444         }
3445         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3446
3447         if (ret == 0)
3448                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3449
3450         return buffer->isClean() && !buffer->isUnnamed();
3451 }
3452
3453
3454 bool GuiView::reloadBuffer(Buffer & buf)
3455 {
3456         currentBufferView()->cursor().reset();
3457         Buffer::ReadStatus status = buf.reload();
3458         return status == Buffer::ReadSuccess;
3459 }
3460
3461
3462 void GuiView::checkExternallyModifiedBuffers()
3463 {
3464         for (Buffer * buf : theBufferList()) {
3465                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3466                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3467                                         " Reload now? Any local changes will be lost."),
3468                                         from_utf8(buf->absFileName()));
3469                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3470                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3471                         if (!ret)
3472                                 reloadBuffer(*buf);
3473                 }
3474         }
3475 }
3476
3477
3478 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3479 {
3480         Buffer * buffer = documentBufferView()
3481                 ? &(documentBufferView()->buffer()) : nullptr;
3482
3483         switch (cmd.action()) {
3484         case LFUN_VC_REGISTER:
3485                 if (!buffer || !ensureBufferClean(buffer))
3486                         break;
3487                 if (!buffer->lyxvc().inUse()) {
3488                         if (buffer->lyxvc().registrer()) {
3489                                 reloadBuffer(*buffer);
3490                                 dr.clearMessageUpdate();
3491                         }
3492                 }
3493                 break;
3494
3495         case LFUN_VC_RENAME:
3496         case LFUN_VC_COPY: {
3497                 if (!buffer || !ensureBufferClean(buffer))
3498                         break;
3499                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3500                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3501                                 // Some changes are not yet committed.
3502                                 // We test here and not in getStatus(), since
3503                                 // this test is expensive.
3504                                 string log;
3505                                 LyXVC::CommandResult ret =
3506                                         buffer->lyxvc().checkIn(log);
3507                                 dr.setMessage(log);
3508                                 if (ret == LyXVC::ErrorCommand ||
3509                                     ret == LyXVC::VCSuccess)
3510                                         reloadBuffer(*buffer);
3511                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3512                                         frontend::Alert::error(
3513                                                 _("Revision control error."),
3514                                                 _("Document could not be checked in."));
3515                                         break;
3516                                 }
3517                         }
3518                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3519                                 LV_VC_RENAME : LV_VC_COPY;
3520                         renameBuffer(*buffer, cmd.argument(), kind);
3521                 }
3522                 break;
3523         }
3524
3525         case LFUN_VC_CHECK_IN:
3526                 if (!buffer || !ensureBufferClean(buffer))
3527                         break;
3528                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3529                         string log;
3530                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3531                         dr.setMessage(log);
3532                         // Only skip reloading if the checkin was cancelled or
3533                         // an error occurred before the real checkin VCS command
3534                         // was executed, since the VCS might have changed the
3535                         // file even if it could not checkin successfully.
3536                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3537                                 reloadBuffer(*buffer);
3538                 }
3539                 break;
3540
3541         case LFUN_VC_CHECK_OUT:
3542                 if (!buffer || !ensureBufferClean(buffer))
3543                         break;
3544                 if (buffer->lyxvc().inUse()) {
3545                         dr.setMessage(buffer->lyxvc().checkOut());
3546                         reloadBuffer(*buffer);
3547                 }
3548                 break;
3549
3550         case LFUN_VC_LOCKING_TOGGLE:
3551                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3552                         break;
3553                 if (buffer->lyxvc().inUse()) {
3554                         string res = buffer->lyxvc().lockingToggle();
3555                         if (res.empty()) {
3556                                 frontend::Alert::error(_("Revision control error."),
3557                                 _("Error when setting the locking property."));
3558                         } else {
3559                                 dr.setMessage(res);
3560                                 reloadBuffer(*buffer);
3561                         }
3562                 }
3563                 break;
3564
3565         case LFUN_VC_REVERT:
3566                 if (!buffer)
3567                         break;
3568                 if (buffer->lyxvc().revert()) {
3569                         reloadBuffer(*buffer);
3570                         dr.clearMessageUpdate();
3571                 }
3572                 break;
3573
3574         case LFUN_VC_UNDO_LAST:
3575                 if (!buffer)
3576                         break;
3577                 buffer->lyxvc().undoLast();
3578                 reloadBuffer(*buffer);
3579                 dr.clearMessageUpdate();
3580                 break;
3581
3582         case LFUN_VC_REPO_UPDATE:
3583                 if (!buffer)
3584                         break;
3585                 if (ensureBufferClean(buffer)) {
3586                         dr.setMessage(buffer->lyxvc().repoUpdate());
3587                         checkExternallyModifiedBuffers();
3588                 }
3589                 break;
3590
3591         case LFUN_VC_COMMAND: {
3592                 string flag = cmd.getArg(0);
3593                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3594                         break;
3595                 docstring message;
3596                 if (contains(flag, 'M')) {
3597                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3598                                 break;
3599                 }
3600                 string path = cmd.getArg(1);
3601                 if (contains(path, "$$p") && buffer)
3602                         path = subst(path, "$$p", buffer->filePath());
3603                 LYXERR(Debug::LYXVC, "Directory: " << path);
3604                 FileName pp(path);
3605                 if (!pp.isReadableDirectory()) {
3606                         lyxerr << _("Directory is not accessible.") << endl;
3607                         break;
3608                 }
3609                 support::PathChanger p(pp);
3610
3611                 string command = cmd.getArg(2);
3612                 if (command.empty())
3613                         break;
3614                 if (buffer) {
3615                         command = subst(command, "$$i", buffer->absFileName());
3616                         command = subst(command, "$$p", buffer->filePath());
3617                 }
3618                 command = subst(command, "$$m", to_utf8(message));
3619                 LYXERR(Debug::LYXVC, "Command: " << command);
3620                 Systemcall one;
3621                 one.startscript(Systemcall::Wait, command);
3622
3623                 if (!buffer)
3624                         break;
3625                 if (contains(flag, 'I'))
3626                         buffer->markDirty();
3627                 if (contains(flag, 'R'))
3628                         reloadBuffer(*buffer);
3629
3630                 break;
3631                 }
3632
3633         case LFUN_VC_COMPARE: {
3634                 if (cmd.argument().empty()) {
3635                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3636                         break;
3637                 }
3638                 if (!buffer)
3639                         break;
3640
3641                 string rev1 = cmd.getArg(0);
3642                 string f1, f2;
3643
3644                 // f1
3645                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3646                         break;
3647
3648                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3649                         f2 = buffer->absFileName();
3650                 } else {
3651                         string rev2 = cmd.getArg(1);
3652                         if (rev2.empty())
3653                                 break;
3654                         // f2
3655                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3656                                 break;
3657                 }
3658
3659                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3660                                         f1 << "\n"  << f2 << "\n" );
3661                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3662                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3663                 break;
3664         }
3665
3666         default:
3667                 break;
3668         }
3669 }
3670
3671
3672 void GuiView::openChildDocument(string const & fname)
3673 {
3674         LASSERT(documentBufferView(), return);
3675         Buffer & buffer = documentBufferView()->buffer();
3676         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3677         documentBufferView()->saveBookmark(false);
3678         Buffer * child = nullptr;
3679         if (theBufferList().exists(filename)) {
3680                 child = theBufferList().getBuffer(filename);
3681                 setBuffer(child);
3682         } else {
3683                 message(bformat(_("Opening child document %1$s..."),
3684                         makeDisplayPath(filename.absFileName())));
3685                 child = loadDocument(filename, false);
3686         }
3687         // Set the parent name of the child document.
3688         // This makes insertion of citations and references in the child work,
3689         // when the target is in the parent or another child document.
3690         if (child)
3691                 child->setParent(&buffer);
3692 }
3693
3694
3695 bool GuiView::goToFileRow(string const & argument)
3696 {
3697         string file_name;
3698         int row = -1;
3699         size_t i = argument.find_last_of(' ');
3700         if (i != string::npos) {
3701                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3702                 istringstream is(argument.substr(i + 1));
3703                 is >> row;
3704                 if (is.fail())
3705                         i = string::npos;
3706         }
3707         if (i == string::npos) {
3708                 LYXERR0("Wrong argument: " << argument);
3709                 return false;
3710         }
3711         Buffer * buf = nullptr;
3712         string const realtmp = package().temp_dir().realPath();
3713         // We have to use os::path_prefix_is() here, instead of
3714         // simply prefixIs(), because the file name comes from
3715         // an external application and may need case adjustment.
3716         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3717                 buf = theBufferList().getBufferFromTmp(file_name, true);
3718                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3719                            << (buf ? " success" : " failed"));
3720         } else {
3721                 // Must replace extension of the file to be .lyx
3722                 // and get full path
3723                 FileName const s = fileSearch(string(),
3724                                                   support::changeExtension(file_name, ".lyx"), "lyx");
3725                 // Either change buffer or load the file
3726                 if (theBufferList().exists(s))
3727                         buf = theBufferList().getBuffer(s);
3728                 else if (s.exists()) {
3729                         buf = loadDocument(s);
3730                         if (!buf)
3731                                 return false;
3732                 } else {
3733                         message(bformat(
3734                                         _("File does not exist: %1$s"),
3735                                         makeDisplayPath(file_name)));
3736                         return false;
3737                 }
3738         }
3739         if (!buf) {
3740                 message(bformat(
3741                         _("No buffer for file: %1$s."),
3742                         makeDisplayPath(file_name))
3743                 );
3744                 return false;
3745         }
3746         setBuffer(buf);
3747         bool success = documentBufferView()->setCursorFromRow(row);
3748         if (!success) {
3749                 LYXERR(Debug::LATEX,
3750                        "setCursorFromRow: invalid position for row " << row);
3751                 frontend::Alert::error(_("Inverse Search Failed"),
3752                                        _("Invalid position requested by inverse search.\n"
3753                                          "You may need to update the viewed document."));
3754         }
3755         return success;
3756 }
3757
3758
3759 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3760 {
3761         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3762         menu->exec(QCursor::pos());
3763 }
3764
3765
3766 template<class T>
3767 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3768                 Buffer const * orig, Buffer * clone, string const & format)
3769 {
3770         Buffer::ExportStatus const status = func(format);
3771
3772         // the cloning operation will have produced a clone of the entire set of
3773         // documents, starting from the master. so we must delete those.
3774         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3775         delete mbuf;
3776         busyBuffers.remove(orig);
3777         return status;
3778 }
3779
3780
3781 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3782                 Buffer const * orig, Buffer * clone, string const & format)
3783 {
3784         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3785                         &Buffer::doExport;
3786         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3787 }
3788
3789
3790 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3791                 Buffer const * orig, Buffer * clone, string const & format)
3792 {
3793         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3794                         &Buffer::doExport;
3795         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3796 }
3797
3798
3799 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3800                 Buffer const * orig, Buffer * clone, string const & format)
3801 {
3802         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3803                         &Buffer::preview;
3804         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3805 }
3806
3807
3808 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3809                            string const & argument,
3810                            Buffer const * used_buffer,
3811                            docstring const & msg,
3812                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3813                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3814                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3815                            bool allow_async)
3816 {
3817         if (!used_buffer)
3818                 return false;
3819
3820         string format = argument;
3821         if (format.empty())
3822                 format = used_buffer->params().getDefaultOutputFormat();
3823         processing_format = format;
3824         if (!msg.empty()) {
3825                 progress_->clearMessages();
3826                 gv_->message(msg);
3827         }
3828 #if EXPORT_in_THREAD
3829         if (allow_async) {
3830                 GuiViewPrivate::busyBuffers.insert(used_buffer);
3831                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3832                 if (!cloned_buffer) {
3833                         Alert::error(_("Export Error"),
3834                                      _("Error cloning the Buffer."));
3835                         return false;
3836                 }
3837                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3838                                         asyncFunc,
3839                                         used_buffer,
3840                                         cloned_buffer,
3841                                         format);
3842                 setPreviewFuture(f);
3843                 last_export_format = used_buffer->params().bufferFormat();
3844                 (void) syncFunc;
3845                 (void) previewFunc;
3846                 // We are asynchronous, so we don't know here anything about the success
3847                 return true;
3848         } else {
3849                 Buffer::ExportStatus status;
3850                 if (syncFunc) {
3851                         status = (used_buffer->*syncFunc)(format, false);
3852                 } else if (previewFunc) {
3853                         status = (used_buffer->*previewFunc)(format);
3854                 } else
3855                         return false;
3856                 handleExportStatus(gv_, status, format);
3857                 (void) asyncFunc;
3858                 return (status == Buffer::ExportSuccess
3859                                 || status == Buffer::PreviewSuccess);
3860         }
3861 #else
3862         (void) allow_async;
3863         Buffer::ExportStatus status;
3864         if (syncFunc) {
3865                 status = (used_buffer->*syncFunc)(format, true);
3866         } else if (previewFunc) {
3867                 status = (used_buffer->*previewFunc)(format);
3868         } else
3869                 return false;
3870         handleExportStatus(gv_, status, format);
3871         (void) asyncFunc;
3872         return (status == Buffer::ExportSuccess
3873                         || status == Buffer::PreviewSuccess);
3874 #endif
3875 }
3876
3877 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3878 {
3879         BufferView * bv = currentBufferView();
3880         LASSERT(bv, return);
3881
3882         // Let the current BufferView dispatch its own actions.
3883         bv->dispatch(cmd, dr);
3884         if (dr.dispatched()) {
3885                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3886                         updateDialog("document", "");
3887                 return;
3888         }
3889
3890         // Try with the document BufferView dispatch if any.
3891         BufferView * doc_bv = documentBufferView();
3892         if (doc_bv && doc_bv != bv) {
3893                 doc_bv->dispatch(cmd, dr);
3894                 if (dr.dispatched()) {
3895                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3896                                 updateDialog("document", "");
3897                         return;
3898                 }
3899         }
3900
3901         // Then let the current Cursor dispatch its own actions.
3902         bv->cursor().dispatch(cmd);
3903
3904         // update completion. We do it here and not in
3905         // processKeySym to avoid another redraw just for a
3906         // changed inline completion
3907         if (cmd.origin() == FuncRequest::KEYBOARD) {
3908                 if (cmd.action() == LFUN_SELF_INSERT
3909                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3910                         updateCompletion(bv->cursor(), true, true);
3911                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3912                         updateCompletion(bv->cursor(), false, true);
3913                 else
3914                         updateCompletion(bv->cursor(), false, false);
3915         }
3916
3917         dr = bv->cursor().result();
3918 }
3919
3920
3921 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3922 {
3923         BufferView * bv = currentBufferView();
3924         // By default we won't need any update.
3925         dr.screenUpdate(Update::None);
3926         // assume cmd will be dispatched
3927         dr.dispatched(true);
3928
3929         Buffer * doc_buffer = documentBufferView()
3930                 ? &(documentBufferView()->buffer()) : nullptr;
3931
3932         if (cmd.origin() == FuncRequest::TOC) {
3933                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3934                 toc->doDispatch(bv->cursor(), cmd, dr);
3935                 return;
3936         }
3937
3938         string const argument = to_utf8(cmd.argument());
3939
3940         switch(cmd.action()) {
3941                 case LFUN_BUFFER_CHILD_OPEN:
3942                         openChildDocument(to_utf8(cmd.argument()));
3943                         break;
3944
3945                 case LFUN_BUFFER_IMPORT:
3946                         importDocument(to_utf8(cmd.argument()));
3947                         break;
3948
3949                 case LFUN_MASTER_BUFFER_EXPORT:
3950                         if (doc_buffer)
3951                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3952                         // fall through
3953                 case LFUN_BUFFER_EXPORT: {
3954                         if (!doc_buffer)
3955                                 break;
3956                         // GCC only sees strfwd.h when building merged
3957                         if (::lyx::operator==(cmd.argument(), "custom")) {
3958                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3959                                 // so the following test should not be needed.
3960                                 // In principle, we could try to switch to such a view...
3961                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3962                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3963                                 break;
3964                         }
3965
3966                         string const dest = cmd.getArg(1);
3967                         FileName target_dir;
3968                         if (!dest.empty() && FileName::isAbsolute(dest))
3969                                 target_dir = FileName(support::onlyPath(dest));
3970                         else
3971                                 target_dir = doc_buffer->fileName().onlyPath();
3972
3973                         string const format = (argument.empty() || argument == "default") ?
3974                                 doc_buffer->params().getDefaultOutputFormat() : argument;
3975
3976                         if ((dest.empty() && doc_buffer->isUnnamed())
3977                             || !target_dir.isDirWritable()) {
3978                                 exportBufferAs(*doc_buffer, from_utf8(format));
3979                                 break;
3980                         }
3981                         /* TODO/Review: Is it a problem to also export the children?
3982                                         See the update_unincluded flag */
3983                         d.asyncBufferProcessing(format,
3984                                                 doc_buffer,
3985                                                 _("Exporting ..."),
3986                                                 &GuiViewPrivate::exportAndDestroy,
3987                                                 &Buffer::doExport,
3988                                                 nullptr, cmd.allowAsync());
3989                         // TODO Inform user about success
3990                         break;
3991                 }
3992
3993                 case LFUN_BUFFER_EXPORT_AS: {
3994                         LASSERT(doc_buffer, break);
3995                         docstring f = cmd.argument();
3996                         if (f.empty())
3997                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3998                         exportBufferAs(*doc_buffer, f);
3999                         break;
4000                 }
4001
4002                 case LFUN_BUFFER_UPDATE: {
4003                         d.asyncBufferProcessing(argument,
4004                                                 doc_buffer,
4005                                                 _("Exporting ..."),
4006                                                 &GuiViewPrivate::compileAndDestroy,
4007                                                 &Buffer::doExport,
4008                                                 nullptr, cmd.allowAsync());
4009                         break;
4010                 }
4011                 case LFUN_BUFFER_VIEW: {
4012                         d.asyncBufferProcessing(argument,
4013                                                 doc_buffer,
4014                                                 _("Previewing ..."),
4015                                                 &GuiViewPrivate::previewAndDestroy,
4016                                                 nullptr,
4017                                                 &Buffer::preview, cmd.allowAsync());
4018                         break;
4019                 }
4020                 case LFUN_MASTER_BUFFER_UPDATE: {
4021                         d.asyncBufferProcessing(argument,
4022                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4023                                                 docstring(),
4024                                                 &GuiViewPrivate::compileAndDestroy,
4025                                                 &Buffer::doExport,
4026                                                 nullptr, cmd.allowAsync());
4027                         break;
4028                 }
4029                 case LFUN_MASTER_BUFFER_VIEW: {
4030                         d.asyncBufferProcessing(argument,
4031                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4032                                                 docstring(),
4033                                                 &GuiViewPrivate::previewAndDestroy,
4034                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4035                         break;
4036                 }
4037                 case LFUN_EXPORT_CANCEL: {
4038                         Systemcall::killscript();
4039                         break;
4040                 }
4041                 case LFUN_BUFFER_SWITCH: {
4042                         string const file_name = to_utf8(cmd.argument());
4043                         if (!FileName::isAbsolute(file_name)) {
4044                                 dr.setError(true);
4045                                 dr.setMessage(_("Absolute filename expected."));
4046                                 break;
4047                         }
4048
4049                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4050                         if (!buffer) {
4051                                 dr.setError(true);
4052                                 dr.setMessage(_("Document not loaded"));
4053                                 break;
4054                         }
4055
4056                         // Do we open or switch to the buffer in this view ?
4057                         if (workArea(*buffer)
4058                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4059                                 setBuffer(buffer);
4060                                 break;
4061                         }
4062
4063                         // Look for the buffer in other views
4064                         QList<int> const ids = guiApp->viewIds();
4065                         int i = 0;
4066                         for (; i != ids.size(); ++i) {
4067                                 GuiView & gv = guiApp->view(ids[i]);
4068                                 if (gv.workArea(*buffer)) {
4069                                         gv.raise();
4070                                         gv.activateWindow();
4071                                         gv.setFocus();
4072                                         gv.setBuffer(buffer);
4073                                         break;
4074                                 }
4075                         }
4076
4077                         // If necessary, open a new window as a last resort
4078                         if (i == ids.size()) {
4079                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4080                                 lyx::dispatch(cmd);
4081                         }
4082                         break;
4083                 }
4084
4085                 case LFUN_BUFFER_NEXT:
4086                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4087                         break;
4088
4089                 case LFUN_BUFFER_MOVE_NEXT:
4090                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4091                         break;
4092
4093                 case LFUN_BUFFER_PREVIOUS:
4094                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
4095                         break;
4096
4097                 case LFUN_BUFFER_MOVE_PREVIOUS:
4098                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
4099                         break;
4100
4101                 case LFUN_BUFFER_CHKTEX:
4102                         LASSERT(doc_buffer, break);
4103                         doc_buffer->runChktex();
4104                         break;
4105
4106                 case LFUN_COMMAND_EXECUTE: {
4107                         command_execute_ = true;
4108                         minibuffer_focus_ = true;
4109                         break;
4110                 }
4111                 case LFUN_DROP_LAYOUTS_CHOICE:
4112                         d.layout_->showPopup();
4113                         break;
4114
4115                 case LFUN_MENU_OPEN:
4116                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4117                                 menu->exec(QCursor::pos());
4118                         break;
4119
4120                 case LFUN_FILE_INSERT: {
4121                         if (cmd.getArg(1) == "ignorelang")
4122                                 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4123                         else
4124                                 insertLyXFile(cmd.argument());
4125                         break;
4126                 }
4127
4128                 case LFUN_FILE_INSERT_PLAINTEXT:
4129                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4130                         string const fname = to_utf8(cmd.argument());
4131                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4132                                 dr.setMessage(_("Absolute filename expected."));
4133                                 break;
4134                         }
4135
4136                         FileName filename(fname);
4137                         if (fname.empty()) {
4138                                 FileDialog dlg(qt_("Select file to insert"));
4139
4140                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4141                                         QStringList(qt_("All Files (*)")));
4142
4143                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4144                                         dr.setMessage(_("Canceled."));
4145                                         break;
4146                                 }
4147
4148                                 filename.set(fromqstr(result.second));
4149                         }
4150
4151                         if (bv) {
4152                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4153                                 bv->dispatch(new_cmd, dr);
4154                         }
4155                         break;
4156                 }
4157
4158                 case LFUN_BUFFER_RELOAD: {
4159                         LASSERT(doc_buffer, break);
4160
4161                         // drop changes?
4162                         bool drop = (cmd.argument() == "dump");
4163
4164                         int ret = 0;
4165                         if (!drop && !doc_buffer->isClean()) {
4166                                 docstring const file =
4167                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4168                                 if (doc_buffer->notifiesExternalModification()) {
4169                                         docstring text = _("The current version will be lost. "
4170                                             "Are you sure you want to load the version on disk "
4171                                             "of the document %1$s?");
4172                                         ret = Alert::prompt(_("Reload saved document?"),
4173                                                             bformat(text, file), 1, 1,
4174                                                             _("&Reload"), _("&Cancel"));
4175                                 } else {
4176                                         docstring text = _("Any changes will be lost. "
4177                                             "Are you sure you want to revert to the saved version "
4178                                             "of the document %1$s?");
4179                                         ret = Alert::prompt(_("Revert to saved document?"),
4180                                                             bformat(text, file), 1, 1,
4181                                                             _("&Revert"), _("&Cancel"));
4182                                 }
4183                         }
4184
4185                         if (ret == 0) {
4186                                 doc_buffer->markClean();
4187                                 reloadBuffer(*doc_buffer);
4188                                 dr.forceBufferUpdate();
4189                         }
4190                         break;
4191                 }
4192
4193                 case LFUN_BUFFER_RESET_EXPORT:
4194                         LASSERT(doc_buffer, break);
4195                         doc_buffer->requireFreshStart(true);
4196                         dr.setMessage(_("Buffer export reset."));
4197                         break;
4198
4199                 case LFUN_BUFFER_WRITE:
4200                         LASSERT(doc_buffer, break);
4201                         saveBuffer(*doc_buffer);
4202                         break;
4203
4204                 case LFUN_BUFFER_WRITE_AS:
4205                         LASSERT(doc_buffer, break);
4206                         renameBuffer(*doc_buffer, cmd.argument());
4207                         break;
4208
4209                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4210                         LASSERT(doc_buffer, break);
4211                         renameBuffer(*doc_buffer, cmd.argument(),
4212                                      LV_WRITE_AS_TEMPLATE);
4213                         break;
4214
4215                 case LFUN_BUFFER_WRITE_ALL: {
4216                         Buffer * first = theBufferList().first();
4217                         if (!first)
4218                                 break;
4219                         message(_("Saving all documents..."));
4220                         // We cannot use a for loop as the buffer list cycles.
4221                         Buffer * b = first;
4222                         do {
4223                                 if (!b->isClean()) {
4224                                         saveBuffer(*b);
4225                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4226                                 }
4227                                 b = theBufferList().next(b);
4228                         } while (b != first);
4229                         dr.setMessage(_("All documents saved."));
4230                         break;
4231                 }
4232
4233                 case LFUN_MASTER_BUFFER_FORALL: {
4234                         if (!doc_buffer)
4235                                 break;
4236
4237                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4238                         funcToRun.allowAsync(false);
4239
4240                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4241                                 // Switch to other buffer view and resend cmd
4242                                 lyx::dispatch(FuncRequest(
4243                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4244                                 lyx::dispatch(funcToRun);
4245                         }
4246                         // switch back
4247                         lyx::dispatch(FuncRequest(
4248                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4249                         break;
4250                 }
4251
4252                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4253                         LASSERT(doc_buffer, break);
4254                         doc_buffer->clearExternalModification();
4255                         break;
4256
4257                 case LFUN_BUFFER_CLOSE:
4258                         closeBuffer();
4259                         break;
4260
4261                 case LFUN_BUFFER_CLOSE_ALL:
4262                         closeBufferAll();
4263                         break;
4264
4265                 case LFUN_DEVEL_MODE_TOGGLE:
4266                         devel_mode_ = !devel_mode_;
4267                         if (devel_mode_)
4268                                 dr.setMessage(_("Developer mode is now enabled."));
4269                         else
4270                                 dr.setMessage(_("Developer mode is now disabled."));
4271                         break;
4272
4273                 case LFUN_TOOLBAR_TOGGLE: {
4274                         string const name = cmd.getArg(0);
4275                         if (GuiToolbar * t = toolbar(name))
4276                                 t->toggle();
4277                         break;
4278                 }
4279
4280                 case LFUN_TOOLBAR_MOVABLE: {
4281                         string const name = cmd.getArg(0);
4282                         if (name == "*") {
4283                                 // toggle (all) toolbars movablility
4284                                 toolbarsMovable_ = !toolbarsMovable_;
4285                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4286                                         GuiToolbar * tb = toolbar(ti.name);
4287                                         if (tb && tb->isMovable() != toolbarsMovable_)
4288                                                 // toggle toolbar movablity if it does not fit lock
4289                                                 // (all) toolbars positions state silent = true, since
4290                                                 // status bar notifications are slow
4291                                                 tb->movable(true);
4292                                 }
4293                                 if (toolbarsMovable_)
4294                                         dr.setMessage(_("Toolbars unlocked."));
4295                                 else
4296                                         dr.setMessage(_("Toolbars locked."));
4297                         } else if (GuiToolbar * t = toolbar(name)) {
4298                                 // toggle current toolbar movablity
4299                                 t->movable();
4300                                 // update lock (all) toolbars positions
4301                                 updateLockToolbars();
4302                         }
4303                         break;
4304                 }
4305
4306                 case LFUN_ICON_SIZE: {
4307                         QSize size = d.iconSize(cmd.argument());
4308                         setIconSize(size);
4309                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4310                                                 size.width(), size.height()));
4311                         break;
4312                 }
4313
4314                 case LFUN_DIALOG_UPDATE: {
4315                         string const name = to_utf8(cmd.argument());
4316                         if (name == "prefs" || name == "document")
4317                                 updateDialog(name, string());
4318                         else if (name == "paragraph")
4319                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4320                         else if (currentBufferView()) {
4321                                 Inset * inset = currentBufferView()->editedInset(name);
4322                                 // Can only update a dialog connected to an existing inset
4323                                 if (inset) {
4324                                         // FIXME: get rid of this indirection; GuiView ask the inset
4325                                         // if he is kind enough to update itself...
4326                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4327                                         //FIXME: pass DispatchResult here?
4328                                         inset->dispatch(currentBufferView()->cursor(), fr);
4329                                 }
4330                         }
4331                         break;
4332                 }
4333
4334                 case LFUN_DIALOG_TOGGLE: {
4335                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4336                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4337                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4338                         break;
4339                 }
4340
4341                 case LFUN_DIALOG_DISCONNECT_INSET:
4342                         disconnectDialog(to_utf8(cmd.argument()));
4343                         break;
4344
4345                 case LFUN_DIALOG_HIDE: {
4346                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4347                         break;
4348                 }
4349
4350                 case LFUN_DIALOG_SHOW: {
4351                         string const name = cmd.getArg(0);
4352                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4353
4354                         if (name == "latexlog") {
4355                                 // getStatus checks that
4356                                 LASSERT(doc_buffer, break);
4357                                 Buffer::LogType type;
4358                                 string const logfile = doc_buffer->logName(&type);
4359                                 switch (type) {
4360                                 case Buffer::latexlog:
4361                                         sdata = "latex ";
4362                                         break;
4363                                 case Buffer::buildlog:
4364                                         sdata = "literate ";
4365                                         break;
4366                                 }
4367                                 sdata += Lexer::quoteString(logfile);
4368                                 showDialog("log", sdata);
4369                         } else if (name == "vclog") {
4370                                 // getStatus checks that
4371                                 LASSERT(doc_buffer, break);
4372                                 string const sdata2 = "vc " +
4373                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4374                                 showDialog("log", sdata2);
4375                         } else if (name == "symbols") {
4376                                 sdata = bv->cursor().getEncoding()->name();
4377                                 if (!sdata.empty())
4378                                         showDialog("symbols", sdata);
4379                         // bug 5274
4380                         } else if (name == "prefs" && isFullScreen()) {
4381                                 lfunUiToggle("fullscreen");
4382                                 showDialog("prefs", sdata);
4383                         } else
4384                                 showDialog(name, sdata);
4385                         break;
4386                 }
4387
4388                 case LFUN_MESSAGE:
4389                         dr.setMessage(cmd.argument());
4390                         break;
4391
4392                 case LFUN_UI_TOGGLE: {
4393                         string arg = cmd.getArg(0);
4394                         if (!lfunUiToggle(arg)) {
4395                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4396                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4397                         }
4398                         // Make sure the keyboard focus stays in the work area.
4399                         setFocus();
4400                         break;
4401                 }
4402
4403                 case LFUN_VIEW_SPLIT: {
4404                         LASSERT(doc_buffer, break);
4405                         string const orientation = cmd.getArg(0);
4406                         d.splitter_->setOrientation(orientation == "vertical"
4407                                 ? Qt::Vertical : Qt::Horizontal);
4408                         TabWorkArea * twa = addTabWorkArea();
4409                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4410                         setCurrentWorkArea(wa);
4411                         break;
4412                 }
4413                 case LFUN_TAB_GROUP_CLOSE:
4414                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4415                                 closeTabWorkArea(twa);
4416                                 d.current_work_area_ = nullptr;
4417                                 twa = d.currentTabWorkArea();
4418                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4419                                 if (twa) {
4420                                         // Make sure the work area is up to date.
4421                                         setCurrentWorkArea(twa->currentWorkArea());
4422                                 } else {
4423                                         setCurrentWorkArea(nullptr);
4424                                 }
4425                         }
4426                         break;
4427
4428                 case LFUN_VIEW_CLOSE:
4429                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4430                                 closeWorkArea(twa->currentWorkArea());
4431                                 d.current_work_area_ = nullptr;
4432                                 twa = d.currentTabWorkArea();
4433                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4434                                 if (twa) {
4435                                         // Make sure the work area is up to date.
4436                                         setCurrentWorkArea(twa->currentWorkArea());
4437                                 } else {
4438                                         setCurrentWorkArea(nullptr);
4439                                 }
4440                         }
4441                         break;
4442
4443                 case LFUN_COMPLETION_INLINE:
4444                         if (d.current_work_area_)
4445                                 d.current_work_area_->completer().showInline();
4446                         break;
4447
4448                 case LFUN_COMPLETION_POPUP:
4449                         if (d.current_work_area_)
4450                                 d.current_work_area_->completer().showPopup();
4451                         break;
4452
4453
4454                 case LFUN_COMPLETE:
4455                         if (d.current_work_area_)
4456                                 d.current_work_area_->completer().tab();
4457                         break;
4458
4459                 case LFUN_COMPLETION_CANCEL:
4460                         if (d.current_work_area_) {
4461                                 if (d.current_work_area_->completer().popupVisible())
4462                                         d.current_work_area_->completer().hidePopup();
4463                                 else
4464                                         d.current_work_area_->completer().hideInline();
4465                         }
4466                         break;
4467
4468                 case LFUN_COMPLETION_ACCEPT:
4469                         if (d.current_work_area_)
4470                                 d.current_work_area_->completer().activate();
4471                         break;
4472
4473                 case LFUN_BUFFER_ZOOM_IN:
4474                 case LFUN_BUFFER_ZOOM_OUT:
4475                 case LFUN_BUFFER_ZOOM: {
4476                         if (cmd.argument().empty()) {
4477                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4478                                         zoom_ratio_ = 1.0;
4479                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4480                                         zoom_ratio_ += 0.1;
4481                                 else
4482                                         zoom_ratio_ -= 0.1;
4483                         } else {
4484                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4485                                         zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4486                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4487                                         zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4488                                 else
4489                                         zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4490                         }
4491
4492                         // Actual zoom value: default zoom + fractional extra value
4493                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4494                         if (zoom < static_cast<int>(zoom_min_))
4495                                 zoom = zoom_min_;
4496
4497                         lyxrc.currentZoom = zoom;
4498
4499                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4500                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4501
4502                         guiApp->fontLoader().update();
4503                         dr.screenUpdate(Update::Force | Update::FitCursor);
4504                         break;
4505                 }
4506
4507                 case LFUN_VC_REGISTER:
4508                 case LFUN_VC_RENAME:
4509                 case LFUN_VC_COPY:
4510                 case LFUN_VC_CHECK_IN:
4511                 case LFUN_VC_CHECK_OUT:
4512                 case LFUN_VC_REPO_UPDATE:
4513                 case LFUN_VC_LOCKING_TOGGLE:
4514                 case LFUN_VC_REVERT:
4515                 case LFUN_VC_UNDO_LAST:
4516                 case LFUN_VC_COMMAND:
4517                 case LFUN_VC_COMPARE:
4518                         dispatchVC(cmd, dr);
4519                         break;
4520
4521                 case LFUN_SERVER_GOTO_FILE_ROW:
4522                         if(goToFileRow(to_utf8(cmd.argument())))
4523                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4524                         break;
4525
4526                 case LFUN_LYX_ACTIVATE:
4527                         activateWindow();
4528                         break;
4529
4530                 case LFUN_WINDOW_RAISE:
4531                         raise();
4532                         activateWindow();
4533                         showNormal();
4534                         break;
4535
4536                 case LFUN_FORWARD_SEARCH: {
4537                         // it seems safe to assume we have a document buffer, since
4538                         // getStatus wants one.
4539                         LASSERT(doc_buffer, break);
4540                         Buffer const * doc_master = doc_buffer->masterBuffer();
4541                         FileName const path(doc_master->temppath());
4542                         string const texname = doc_master->isChild(doc_buffer)
4543                                 ? DocFileName(changeExtension(
4544                                         doc_buffer->absFileName(),
4545                                                 "tex")).mangledFileName()
4546                                 : doc_buffer->latexName();
4547                         string const fulltexname =
4548                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4549                         string const mastername =
4550                                 removeExtension(doc_master->latexName());
4551                         FileName const dviname(addName(path.absFileName(),
4552                                         addExtension(mastername, "dvi")));
4553                         FileName const pdfname(addName(path.absFileName(),
4554                                         addExtension(mastername, "pdf")));
4555                         bool const have_dvi = dviname.exists();
4556                         bool const have_pdf = pdfname.exists();
4557                         if (!have_dvi && !have_pdf) {
4558                                 dr.setMessage(_("Please, preview the document first."));
4559                                 break;
4560                         }
4561                         string outname = dviname.onlyFileName();
4562                         string command = lyxrc.forward_search_dvi;
4563                         if (!have_dvi || (have_pdf &&
4564                             pdfname.lastModified() > dviname.lastModified())) {
4565                                 outname = pdfname.onlyFileName();
4566                                 command = lyxrc.forward_search_pdf;
4567                         }
4568
4569                         DocIterator cur = bv->cursor();
4570                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4571                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4572                                    << " cur:" << cur);
4573                         if (row == -1 || command.empty()) {
4574                                 dr.setMessage(_("Couldn't proceed."));
4575                                 break;
4576                         }
4577                         string texrow = convert<string>(row);
4578
4579                         command = subst(command, "$$n", texrow);
4580                         command = subst(command, "$$f", fulltexname);
4581                         command = subst(command, "$$t", texname);
4582                         command = subst(command, "$$o", outname);
4583
4584                         volatile PathChanger p(path);
4585                         Systemcall one;
4586                         one.startscript(Systemcall::DontWait, command);
4587                         break;
4588                 }
4589
4590                 case LFUN_SPELLING_CONTINUOUSLY:
4591                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4592                         dr.screenUpdate(Update::Force);
4593                         break;
4594
4595                 case LFUN_CITATION_OPEN: {
4596                         string pdfv, psv;
4597                         if (theFormats().getFormat("pdf"))
4598                                 pdfv = theFormats().getFormat("pdf")->viewer();
4599                         if (theFormats().getFormat("ps"))
4600                                 psv = theFormats().getFormat("ps")->viewer();
4601                         frontend::showTarget(argument, pdfv, psv);
4602                         break;
4603                 }
4604
4605                 default:
4606                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4607                         // let's try that:
4608                         dispatchToBufferView(cmd, dr);
4609                         break;
4610         }
4611
4612         // Part of automatic menu appearance feature.
4613         if (isFullScreen()) {
4614                 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4615                         menuBar()->hide();
4616         }
4617
4618         // Need to update bv because many LFUNs here might have destroyed it
4619         bv = currentBufferView();
4620
4621         // Clear non-empty selections
4622         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4623         if (bv) {
4624                 Cursor & cur = bv->cursor();
4625                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4626                         cur.clearSelection();
4627                 }
4628         }
4629 }
4630
4631
4632 bool GuiView::lfunUiToggle(string const & ui_component)
4633 {
4634         if (ui_component == "scrollbar") {
4635                 // hide() is of no help
4636                 if (d.current_work_area_->verticalScrollBarPolicy() ==
4637                         Qt::ScrollBarAlwaysOff)
4638
4639                         d.current_work_area_->setVerticalScrollBarPolicy(
4640                                 Qt::ScrollBarAsNeeded);
4641                 else
4642                         d.current_work_area_->setVerticalScrollBarPolicy(
4643                                 Qt::ScrollBarAlwaysOff);
4644         } else if (ui_component == "statusbar") {
4645                 statusBar()->setVisible(!statusBar()->isVisible());
4646         } else if (ui_component == "menubar") {
4647                 menuBar()->setVisible(!menuBar()->isVisible());
4648         } else
4649         if (ui_component == "frame") {
4650                 int const l = contentsMargins().left();
4651
4652                 //are the frames in default state?
4653                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4654                 if (l == 0) {
4655                         setContentsMargins(-2, -2, -2, -2);
4656                 } else {
4657                         setContentsMargins(0, 0, 0, 0);
4658                 }
4659         } else
4660         if (ui_component == "fullscreen") {
4661                 toggleFullScreen();
4662         } else
4663                 return false;
4664         return true;
4665 }
4666
4667
4668 void GuiView::toggleFullScreen()
4669 {
4670         setWindowState(windowState() ^ Qt::WindowFullScreen);
4671 }
4672
4673
4674 Buffer const * GuiView::updateInset(Inset const * inset)
4675 {
4676         if (!inset)
4677                 return nullptr;
4678
4679         Buffer const * inset_buffer = &(inset->buffer());
4680
4681         for (int i = 0; i != d.splitter_->count(); ++i) {
4682                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4683                 if (!wa)
4684                         continue;
4685                 Buffer const * buffer = &(wa->bufferView().buffer());
4686                 if (inset_buffer == buffer)
4687                         wa->scheduleRedraw(true);
4688         }
4689         return inset_buffer;
4690 }
4691
4692
4693 void GuiView::restartCaret()
4694 {
4695         /* When we move around, or type, it's nice to be able to see
4696          * the caret immediately after the keypress.
4697          */
4698         if (d.current_work_area_)
4699                 d.current_work_area_->startBlinkingCaret();
4700
4701         // Take this occasion to update the other GUI elements.
4702         updateDialogs();
4703         updateStatusBar();
4704 }
4705
4706
4707 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4708 {
4709         if (d.current_work_area_)
4710                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4711 }
4712
4713 namespace {
4714
4715 // This list should be kept in sync with the list of insets in
4716 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
4717 // dialog should have the same name as the inset.
4718 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4719 // docs in LyXAction.cpp.
4720
4721 char const * const dialognames[] = {
4722
4723 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4724 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4725 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4726 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4727 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4728 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4729 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4730 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4731
4732 char const * const * const end_dialognames =
4733         dialognames + (sizeof(dialognames) / sizeof(char *));
4734
4735 class cmpCStr {
4736 public:
4737         cmpCStr(char const * name) : name_(name) {}
4738         bool operator()(char const * other) {
4739                 return strcmp(other, name_) == 0;
4740         }
4741 private:
4742         char const * name_;
4743 };
4744
4745
4746 bool isValidName(string const & name)
4747 {
4748         return find_if(dialognames, end_dialognames,
4749                 cmpCStr(name.c_str())) != end_dialognames;
4750 }
4751
4752 } // namespace
4753
4754
4755 void GuiView::resetDialogs()
4756 {
4757         // Make sure that no LFUN uses any GuiView.
4758         guiApp->setCurrentView(nullptr);
4759         saveLayout();
4760         saveUISettings();
4761         menuBar()->clear();
4762         constructToolbars();
4763         guiApp->menus().fillMenuBar(menuBar(), this, false);
4764         d.layout_->updateContents(true);
4765         // Now update controls with current buffer.
4766         guiApp->setCurrentView(this);
4767         restoreLayout();
4768         restartCaret();
4769 }
4770
4771
4772 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4773 {
4774         for (QObject * child: widget->children()) {
4775                 if (child->inherits("QGroupBox")) {
4776                         QGroupBox * box = (QGroupBox*) child;
4777                         box->setFlat(flag);
4778                 } else {
4779                         flatGroupBoxes(child, flag);
4780                 }
4781         }
4782 }
4783
4784
4785 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4786 {
4787         if (!isValidName(name))
4788                 return nullptr;
4789
4790         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4791
4792         if (it != d.dialogs_.end()) {
4793                 if (hide_it)
4794                         it->second->hideView();
4795                 return it->second.get();
4796         }
4797
4798         Dialog * dialog = build(name);
4799         d.dialogs_[name].reset(dialog);
4800 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4801         // Force a uniform style for group boxes
4802         // On Mac non-flat works better, on Linux flat is standard
4803         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4804 #endif
4805         if (lyxrc.allow_geometry_session)
4806                 dialog->restoreSession();
4807         if (hide_it)
4808                 dialog->hideView();
4809         return dialog;
4810 }
4811
4812
4813 void GuiView::showDialog(string const & name, string const & sdata,
4814         Inset * inset)
4815 {
4816         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4817 }
4818
4819
4820 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4821         Inset * inset)
4822 {
4823         if (d.in_show_)
4824                 return;
4825
4826         const string name = fromqstr(qname);
4827         const string sdata = fromqstr(qdata);
4828
4829         d.in_show_ = true;
4830         try {
4831                 Dialog * dialog = findOrBuild(name, false);
4832                 if (dialog) {
4833                         bool const visible = dialog->isVisibleView();
4834                         dialog->showData(sdata);
4835                         if (currentBufferView())
4836                                 currentBufferView()->editInset(name, inset);
4837                         // We only set the focus to the new dialog if it was not yet
4838                         // visible in order not to change the existing previous behaviour
4839                         if (visible) {
4840                                 // activateWindow is needed for floating dockviews
4841                                 dialog->asQWidget()->raise();
4842                                 dialog->asQWidget()->activateWindow();
4843                                 dialog->asQWidget()->setFocus();
4844                         }
4845                 }
4846         }
4847         catch (ExceptionMessage const &) {
4848                 d.in_show_ = false;
4849                 throw;
4850         }
4851         d.in_show_ = false;
4852 }
4853
4854
4855 bool GuiView::isDialogVisible(string const & name) const
4856 {
4857         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4858         if (it == d.dialogs_.end())
4859                 return false;
4860         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4861 }
4862
4863
4864 void GuiView::hideDialog(string const & name, Inset * inset)
4865 {
4866         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4867         if (it == d.dialogs_.end())
4868                 return;
4869
4870         if (inset) {
4871                 if (!currentBufferView())
4872                         return;
4873                 if (inset != currentBufferView()->editedInset(name))
4874                         return;
4875         }
4876
4877         Dialog * const dialog = it->second.get();
4878         if (dialog->isVisibleView())
4879                 dialog->hideView();
4880         if (currentBufferView())
4881                 currentBufferView()->editInset(name, nullptr);
4882 }
4883
4884
4885 void GuiView::disconnectDialog(string const & name)
4886 {
4887         if (!isValidName(name))
4888                 return;
4889         if (currentBufferView())
4890                 currentBufferView()->editInset(name, nullptr);
4891 }
4892
4893
4894 void GuiView::hideAll() const
4895 {
4896         for(auto const & dlg_p : d.dialogs_)
4897                 dlg_p.second->hideView();
4898 }
4899
4900
4901 void GuiView::updateDialogs()
4902 {
4903         for(auto const & dlg_p : d.dialogs_) {
4904                 Dialog * dialog = dlg_p.second.get();
4905                 if (dialog) {
4906                         if (dialog->needBufferOpen() && !documentBufferView())
4907                                 hideDialog(fromqstr(dialog->name()), nullptr);
4908                         else if (dialog->isVisibleView())
4909                                 dialog->checkStatus();
4910                 }
4911         }
4912         updateToolbars();
4913         updateLayoutList();
4914 }
4915
4916 Dialog * createDialog(GuiView & lv, string const & name);
4917
4918 // will be replaced by a proper factory...
4919 Dialog * createGuiAbout(GuiView & lv);
4920 Dialog * createGuiBibtex(GuiView & lv);
4921 Dialog * createGuiChanges(GuiView & lv);
4922 Dialog * createGuiCharacter(GuiView & lv);
4923 Dialog * createGuiCitation(GuiView & lv);
4924 Dialog * createGuiCompare(GuiView & lv);
4925 Dialog * createGuiCompareHistory(GuiView & lv);
4926 Dialog * createGuiDelimiter(GuiView & lv);
4927 Dialog * createGuiDocument(GuiView & lv);
4928 Dialog * createGuiErrorList(GuiView & lv);
4929 Dialog * createGuiExternal(GuiView & lv);
4930 Dialog * createGuiGraphics(GuiView & lv);
4931 Dialog * createGuiInclude(GuiView & lv);
4932 Dialog * createGuiIndex(GuiView & lv);
4933 Dialog * createGuiListings(GuiView & lv);
4934 Dialog * createGuiLog(GuiView & lv);
4935 Dialog * createGuiLyXFiles(GuiView & lv);
4936 Dialog * createGuiMathMatrix(GuiView & lv);
4937 Dialog * createGuiNote(GuiView & lv);
4938 Dialog * createGuiParagraph(GuiView & lv);
4939 Dialog * createGuiPhantom(GuiView & lv);
4940 Dialog * createGuiPreferences(GuiView & lv);
4941 Dialog * createGuiPrint(GuiView & lv);
4942 Dialog * createGuiPrintindex(GuiView & lv);
4943 Dialog * createGuiRef(GuiView & lv);
4944 Dialog * createGuiSearch(GuiView & lv);
4945 Dialog * createGuiSearchAdv(GuiView & lv);
4946 Dialog * createGuiSendTo(GuiView & lv);
4947 Dialog * createGuiShowFile(GuiView & lv);
4948 Dialog * createGuiSpellchecker(GuiView & lv);
4949 Dialog * createGuiSymbols(GuiView & lv);
4950 Dialog * createGuiTabularCreate(GuiView & lv);
4951 Dialog * createGuiTexInfo(GuiView & lv);
4952 Dialog * createGuiToc(GuiView & lv);
4953 Dialog * createGuiThesaurus(GuiView & lv);
4954 Dialog * createGuiViewSource(GuiView & lv);
4955 Dialog * createGuiWrap(GuiView & lv);
4956 Dialog * createGuiProgressView(GuiView & lv);
4957
4958
4959
4960 Dialog * GuiView::build(string const & name)
4961 {
4962         LASSERT(isValidName(name), return nullptr);
4963
4964         Dialog * dialog = createDialog(*this, name);
4965         if (dialog)
4966                 return dialog;
4967
4968         if (name == "aboutlyx")
4969                 return createGuiAbout(*this);
4970         if (name == "bibtex")
4971                 return createGuiBibtex(*this);
4972         if (name == "changes")
4973                 return createGuiChanges(*this);
4974         if (name == "character")
4975                 return createGuiCharacter(*this);
4976         if (name == "citation")
4977                 return createGuiCitation(*this);
4978         if (name == "compare")
4979                 return createGuiCompare(*this);
4980         if (name == "comparehistory")
4981                 return createGuiCompareHistory(*this);
4982         if (name == "document")
4983                 return createGuiDocument(*this);
4984         if (name == "errorlist")
4985                 return createGuiErrorList(*this);
4986         if (name == "external")
4987                 return createGuiExternal(*this);
4988         if (name == "file")
4989                 return createGuiShowFile(*this);
4990         if (name == "findreplace")
4991                 return createGuiSearch(*this);
4992         if (name == "findreplaceadv")
4993                 return createGuiSearchAdv(*this);
4994         if (name == "graphics")
4995                 return createGuiGraphics(*this);
4996         if (name == "include")
4997                 return createGuiInclude(*this);
4998         if (name == "index")
4999                 return createGuiIndex(*this);
5000         if (name == "index_print")
5001                 return createGuiPrintindex(*this);
5002         if (name == "listings")
5003                 return createGuiListings(*this);
5004         if (name == "log")
5005                 return createGuiLog(*this);
5006         if (name == "lyxfiles")
5007                 return createGuiLyXFiles(*this);
5008         if (name == "mathdelimiter")
5009                 return createGuiDelimiter(*this);
5010         if (name == "mathmatrix")
5011                 return createGuiMathMatrix(*this);
5012         if (name == "note")
5013                 return createGuiNote(*this);
5014         if (name == "paragraph")
5015                 return createGuiParagraph(*this);
5016         if (name == "phantom")
5017                 return createGuiPhantom(*this);
5018         if (name == "prefs")
5019                 return createGuiPreferences(*this);
5020         if (name == "ref")
5021                 return createGuiRef(*this);
5022         if (name == "sendto")
5023                 return createGuiSendTo(*this);
5024         if (name == "spellchecker")
5025                 return createGuiSpellchecker(*this);
5026         if (name == "symbols")
5027                 return createGuiSymbols(*this);
5028         if (name == "tabularcreate")
5029                 return createGuiTabularCreate(*this);
5030         if (name == "texinfo")
5031                 return createGuiTexInfo(*this);
5032         if (name == "thesaurus")
5033                 return createGuiThesaurus(*this);
5034         if (name == "toc")
5035                 return createGuiToc(*this);
5036         if (name == "view-source")
5037                 return createGuiViewSource(*this);
5038         if (name == "wrap")
5039                 return createGuiWrap(*this);
5040         if (name == "progress")
5041                 return createGuiProgressView(*this);
5042
5043         return nullptr;
5044 }
5045
5046
5047 SEMenu::SEMenu(QWidget * parent)
5048 {
5049         QAction * action = addAction(qt_("Disable Shell Escape"));
5050         connect(action, SIGNAL(triggered()),
5051                 parent, SLOT(disableShellEscape()));
5052 }
5053
5054 } // namespace frontend
5055 } // namespace lyx
5056
5057 #include "moc_GuiView.cpp"