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