]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiView.cpp
Make spellchecker work in read-only document
[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->text().empty();
2197                 else if (name == "vclog")
2198                         enable = doc_buffer->lyxvc().inUse();
2199                 break;
2200         }
2201
2202         case LFUN_DIALOG_UPDATE: {
2203                 string const name = cmd.getArg(0);
2204                 if (!buf)
2205                         enable = name == "prefs";
2206                 break;
2207         }
2208
2209         case LFUN_COMMAND_EXECUTE:
2210         case LFUN_MESSAGE:
2211         case LFUN_MENU_OPEN:
2212                 // Nothing to check.
2213                 break;
2214
2215         case LFUN_COMPLETION_INLINE:
2216                 if (!d.current_work_area_
2217                         || !d.current_work_area_->completer().inlinePossible(
2218                         currentBufferView()->cursor()))
2219                         enable = false;
2220                 break;
2221
2222         case LFUN_COMPLETION_POPUP:
2223                 if (!d.current_work_area_
2224                         || !d.current_work_area_->completer().popupPossible(
2225                         currentBufferView()->cursor()))
2226                         enable = false;
2227                 break;
2228
2229         case LFUN_COMPLETE:
2230                 if (!d.current_work_area_
2231                         || !d.current_work_area_->completer().inlinePossible(
2232                         currentBufferView()->cursor()))
2233                         enable = false;
2234                 break;
2235
2236         case LFUN_COMPLETION_ACCEPT:
2237                 if (!d.current_work_area_
2238                         || (!d.current_work_area_->completer().popupVisible()
2239                         && !d.current_work_area_->completer().inlineVisible()
2240                         && !d.current_work_area_->completer().completionAvailable()))
2241                         enable = false;
2242                 break;
2243
2244         case LFUN_COMPLETION_CANCEL:
2245                 if (!d.current_work_area_
2246                         || (!d.current_work_area_->completer().popupVisible()
2247                         && !d.current_work_area_->completer().inlineVisible()))
2248                         enable = false;
2249                 break;
2250
2251         case LFUN_BUFFER_ZOOM_OUT:
2252         case LFUN_BUFFER_ZOOM_IN: {
2253                 // only diff between these two is that the default for ZOOM_OUT
2254                 // is a neg. number
2255                 bool const neg_zoom =
2256                         convert<int>(cmd.argument()) < 0 ||
2257                         (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2258                 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2259                         docstring const msg =
2260                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2261                         flag.message(msg);
2262                         enable = false;
2263                 } else
2264                         enable = doc_buffer;
2265                 break;
2266         }
2267
2268         case LFUN_BUFFER_ZOOM: {
2269                 bool const less_than_min_zoom =
2270                         !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2271                 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2272                         docstring const msg =
2273                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2274                         flag.message(msg);
2275                         enable = false;
2276                 }
2277                 else
2278                         enable = doc_buffer;
2279                 break;
2280         }
2281
2282         case LFUN_BUFFER_MOVE_NEXT:
2283         case LFUN_BUFFER_MOVE_PREVIOUS:
2284                 // we do not cycle when moving
2285         case LFUN_BUFFER_NEXT:
2286         case LFUN_BUFFER_PREVIOUS:
2287                 // because we cycle, it doesn't matter whether on first or last
2288                 enable = (d.currentTabWorkArea()->count() > 1);
2289                 break;
2290         case LFUN_BUFFER_SWITCH:
2291                 // toggle on the current buffer, but do not toggle off
2292                 // the other ones (is that a good idea?)
2293                 if (doc_buffer
2294                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2295                         flag.setOnOff(true);
2296                 break;
2297
2298         case LFUN_VC_REGISTER:
2299                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2300                 break;
2301         case LFUN_VC_RENAME:
2302                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2303                 break;
2304         case LFUN_VC_COPY:
2305                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2306                 break;
2307         case LFUN_VC_CHECK_IN:
2308                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2309                 break;
2310         case LFUN_VC_CHECK_OUT:
2311                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2312                 break;
2313         case LFUN_VC_LOCKING_TOGGLE:
2314                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2315                         && doc_buffer->lyxvc().lockingToggleEnabled();
2316                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2317                 break;
2318         case LFUN_VC_REVERT:
2319                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2320                         && !doc_buffer->hasReadonlyFlag();
2321                 break;
2322         case LFUN_VC_UNDO_LAST:
2323                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2324                 break;
2325         case LFUN_VC_REPO_UPDATE:
2326                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2327                 break;
2328         case LFUN_VC_COMMAND: {
2329                 if (cmd.argument().empty())
2330                         enable = false;
2331                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2332                         enable = false;
2333                 break;
2334         }
2335         case LFUN_VC_COMPARE:
2336                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2337                 break;
2338
2339         case LFUN_SERVER_GOTO_FILE_ROW:
2340         case LFUN_LYX_ACTIVATE:
2341         case LFUN_WINDOW_RAISE:
2342                 break;
2343         case LFUN_FORWARD_SEARCH:
2344                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2345                 break;
2346
2347         case LFUN_FILE_INSERT_PLAINTEXT:
2348         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2349                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2350                 break;
2351
2352         case LFUN_SPELLING_CONTINUOUSLY:
2353                 flag.setOnOff(lyxrc.spellcheck_continuously);
2354                 break;
2355
2356         case LFUN_CITATION_OPEN:
2357                 enable = true;
2358                 break;
2359
2360         default:
2361                 return false;
2362         }
2363
2364         if (!enable)
2365                 flag.setEnabled(false);
2366
2367         return true;
2368 }
2369
2370
2371 static FileName selectTemplateFile()
2372 {
2373         FileDialog dlg(qt_("Select template file"));
2374         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2375         dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2376
2377         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2378                                  QStringList(qt_("LyX Documents (*.lyx)")));
2379
2380         if (result.first == FileDialog::Later)
2381                 return FileName();
2382         if (result.second.isEmpty())
2383                 return FileName();
2384         return FileName(fromqstr(result.second));
2385 }
2386
2387
2388 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2389 {
2390         setBusy(true);
2391
2392         Buffer * newBuffer = nullptr;
2393         try {
2394                 newBuffer = checkAndLoadLyXFile(filename);
2395         } catch (ExceptionMessage const &) {
2396                 setBusy(false);
2397                 throw;
2398         }
2399         setBusy(false);
2400
2401         if (!newBuffer) {
2402                 message(_("Document not loaded."));
2403                 return nullptr;
2404         }
2405
2406         setBuffer(newBuffer);
2407         newBuffer->errors("Parse");
2408
2409         if (tolastfiles) {
2410                 theSession().lastFiles().add(filename);
2411                 theSession().writeFile();
2412   }
2413
2414         return newBuffer;
2415 }
2416
2417
2418 void GuiView::openDocument(string const & fname)
2419 {
2420         string initpath = lyxrc.document_path;
2421
2422         if (documentBufferView()) {
2423                 string const trypath = documentBufferView()->buffer().filePath();
2424                 // If directory is writeable, use this as default.
2425                 if (FileName(trypath).isDirWritable())
2426                         initpath = trypath;
2427         }
2428
2429         string filename;
2430
2431         if (fname.empty()) {
2432                 FileDialog dlg(qt_("Select document to open"));
2433                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2434                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2435
2436                 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2437                 FileDialog::Result result =
2438                         dlg.open(toqstr(initpath), filter);
2439
2440                 if (result.first == FileDialog::Later)
2441                         return;
2442
2443                 filename = fromqstr(result.second);
2444
2445                 // check selected filename
2446                 if (filename.empty()) {
2447                         message(_("Canceled."));
2448                         return;
2449                 }
2450         } else
2451                 filename = fname;
2452
2453         // get absolute path of file and add ".lyx" to the filename if
2454         // necessary.
2455         FileName const fullname =
2456                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2457         if (!fullname.empty())
2458                 filename = fullname.absFileName();
2459
2460         if (!fullname.onlyPath().isDirectory()) {
2461                 Alert::warning(_("Invalid filename"),
2462                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2463                                 from_utf8(fullname.absFileName())));
2464                 return;
2465         }
2466
2467         // if the file doesn't exist and isn't already open (bug 6645),
2468         // let the user create one
2469         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2470             !LyXVC::file_not_found_hook(fullname)) {
2471                 // the user specifically chose this name. Believe him.
2472                 Buffer * const b = newFile(filename, string(), true);
2473                 if (b)
2474                         setBuffer(b);
2475                 return;
2476         }
2477
2478         docstring const disp_fn = makeDisplayPath(filename);
2479         message(bformat(_("Opening document %1$s..."), disp_fn));
2480
2481         docstring str2;
2482         Buffer * buf = loadDocument(fullname);
2483         if (buf) {
2484                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2485                 if (buf->lyxvc().inUse())
2486                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2487                                 " " + _("Version control detected.");
2488         } else {
2489                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2490         }
2491         message(str2);
2492 }
2493
2494 // FIXME: clean that
2495 static bool import(GuiView * lv, FileName const & filename,
2496         string const & format, ErrorList & errorList)
2497 {
2498         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2499
2500         string loader_format;
2501         vector<string> loaders = theConverters().loaders();
2502         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2503                 for (string const & loader : loaders) {
2504                         if (!theConverters().isReachable(format, loader))
2505                                 continue;
2506
2507                         string const tofile =
2508                                 support::changeExtension(filename.absFileName(),
2509                                 theFormats().extension(loader));
2510                         if (theConverters().convert(nullptr, filename, FileName(tofile),
2511                                 filename, format, loader, errorList) != Converters::SUCCESS)
2512                                 return false;
2513                         loader_format = loader;
2514                         break;
2515                 }
2516                 if (loader_format.empty()) {
2517                         frontend::Alert::error(_("Couldn't import file"),
2518                                          bformat(_("No information for importing the format %1$s."),
2519                                          translateIfPossible(theFormats().prettyName(format))));
2520                         return false;
2521                 }
2522         } else
2523                 loader_format = format;
2524
2525         if (loader_format == "lyx") {
2526                 Buffer * buf = lv->loadDocument(lyxfile);
2527                 if (!buf)
2528                         return false;
2529         } else {
2530                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2531                 if (!b)
2532                         return false;
2533                 lv->setBuffer(b);
2534                 bool as_paragraphs = loader_format == "textparagraph";
2535                 string filename2 = (loader_format == format) ? filename.absFileName()
2536                         : support::changeExtension(filename.absFileName(),
2537                                           theFormats().extension(loader_format));
2538                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2539                         as_paragraphs);
2540                 guiApp->setCurrentView(lv);
2541                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2542         }
2543
2544         return true;
2545 }
2546
2547
2548 void GuiView::importDocument(string const & argument)
2549 {
2550         string format;
2551         string filename = split(argument, format, ' ');
2552
2553         LYXERR(Debug::INFO, format << " file: " << filename);
2554
2555         // need user interaction
2556         if (filename.empty()) {
2557                 string initpath = lyxrc.document_path;
2558                 if (documentBufferView()) {
2559                         string const trypath = documentBufferView()->buffer().filePath();
2560                         // If directory is writeable, use this as default.
2561                         if (FileName(trypath).isDirWritable())
2562                                 initpath = trypath;
2563                 }
2564
2565                 docstring const text = bformat(_("Select %1$s file to import"),
2566                         translateIfPossible(theFormats().prettyName(format)));
2567
2568                 FileDialog dlg(toqstr(text));
2569                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2570                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2571
2572                 docstring filter = translateIfPossible(theFormats().prettyName(format));
2573                 filter += " (*.{";
2574                 // FIXME UNICODE
2575                 filter += from_utf8(theFormats().extensions(format));
2576                 filter += "})";
2577
2578                 FileDialog::Result result =
2579                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2580
2581                 if (result.first == FileDialog::Later)
2582                         return;
2583
2584                 filename = fromqstr(result.second);
2585
2586                 // check selected filename
2587                 if (filename.empty())
2588                         message(_("Canceled."));
2589         }
2590
2591         if (filename.empty())
2592                 return;
2593
2594         // get absolute path of file
2595         FileName const fullname(support::makeAbsPath(filename));
2596
2597         // Can happen if the user entered a path into the dialog
2598         // (see bug #7437)
2599         if (fullname.onlyFileName().empty()) {
2600                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2601                                           "Aborting import."),
2602                                         from_utf8(fullname.absFileName()));
2603                 frontend::Alert::error(_("File name error"), msg);
2604                 message(_("Canceled."));
2605                 return;
2606         }
2607
2608
2609         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2610
2611         // Check if the document already is open
2612         Buffer * buf = theBufferList().getBuffer(lyxfile);
2613         if (buf) {
2614                 setBuffer(buf);
2615                 if (!closeBuffer()) {
2616                         message(_("Canceled."));
2617                         return;
2618                 }
2619         }
2620
2621         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2622
2623         // if the file exists already, and we didn't do
2624         // -i lyx thefile.lyx, warn
2625         if (lyxfile.exists() && fullname != lyxfile) {
2626
2627                 docstring text = bformat(_("The document %1$s already exists.\n\n"
2628                         "Do you want to overwrite that document?"), displaypath);
2629                 int const ret = Alert::prompt(_("Overwrite document?"),
2630                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
2631
2632                 if (ret == 1) {
2633                         message(_("Canceled."));
2634                         return;
2635                 }
2636         }
2637
2638         message(bformat(_("Importing %1$s..."), displaypath));
2639         ErrorList errorList;
2640         if (import(this, fullname, format, errorList))
2641                 message(_("imported."));
2642         else
2643                 message(_("file not imported!"));
2644
2645         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2646 }
2647
2648
2649 void GuiView::newDocument(string const & filename, string templatefile,
2650                           bool from_template)
2651 {
2652         FileName initpath(lyxrc.document_path);
2653         if (documentBufferView()) {
2654                 FileName const trypath(documentBufferView()->buffer().filePath());
2655                 // If directory is writeable, use this as default.
2656                 if (trypath.isDirWritable())
2657                         initpath = trypath;
2658         }
2659
2660         if (from_template) {
2661                 if (templatefile.empty())
2662                         templatefile =  selectTemplateFile().absFileName();
2663                 if (templatefile.empty())
2664                         return;
2665         }
2666
2667         Buffer * b;
2668         if (filename.empty())
2669                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2670         else
2671                 b = newFile(filename, templatefile, true);
2672
2673         if (b)
2674                 setBuffer(b);
2675
2676         // If no new document could be created, it is unsure
2677         // whether there is a valid BufferView.
2678         if (currentBufferView())
2679                 // Ensure the cursor is correctly positioned on screen.
2680                 currentBufferView()->showCursor();
2681 }
2682
2683
2684 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2685 {
2686         BufferView * bv = documentBufferView();
2687         if (!bv)
2688                 return;
2689
2690         // FIXME UNICODE
2691         FileName filename(to_utf8(fname));
2692         if (filename.empty()) {
2693                 // Launch a file browser
2694                 // FIXME UNICODE
2695                 string initpath = lyxrc.document_path;
2696                 string const trypath = bv->buffer().filePath();
2697                 // If directory is writeable, use this as default.
2698                 if (FileName(trypath).isDirWritable())
2699                         initpath = trypath;
2700
2701                 // FIXME UNICODE
2702                 FileDialog dlg(qt_("Select LyX document to insert"));
2703                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2704                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2705
2706                 FileDialog::Result result = dlg.open(toqstr(initpath),
2707                                          QStringList(qt_("LyX Documents (*.lyx)")));
2708
2709                 if (result.first == FileDialog::Later)
2710                         return;
2711
2712                 // FIXME UNICODE
2713                 filename.set(fromqstr(result.second));
2714
2715                 // check selected filename
2716                 if (filename.empty()) {
2717                         // emit message signal.
2718                         message(_("Canceled."));
2719                         return;
2720                 }
2721         }
2722
2723         bv->insertLyXFile(filename, ignorelang);
2724         bv->buffer().errors("Parse");
2725 }
2726
2727
2728 string const GuiView::getTemplatesPath(Buffer & b)
2729 {
2730         // We start off with the user's templates path
2731         string result = addPath(package().user_support().absFileName(), "templates");
2732         // Check for the document language
2733         string const langcode = b.params().language->code();
2734         string const shortcode = langcode.substr(0, 2);
2735         if (!langcode.empty() && shortcode != "en") {
2736                 string subpath = addPath(result, shortcode);
2737                 string subpath_long = addPath(result, langcode);
2738                 // If we have a subdirectory for the language already,
2739                 // navigate there
2740                 FileName sp = FileName(subpath);
2741                 if (sp.isDirectory())
2742                         result = subpath;
2743                 else if (FileName(subpath_long).isDirectory())
2744                         result = subpath_long;
2745                 else {
2746                         // Ask whether we should create such a subdirectory
2747                         docstring const text =
2748                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2749                                           "appropriate to the document language (%1$s).\n"
2750                                           "This subdirectory does not exists yet.\n"
2751                                           "Do you want to create it?"),
2752                                         _(b.params().language->display()));
2753                         if (Alert::prompt(_("Create Language Directory?"),
2754                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2755                                 // If the user agreed, we try to create it and report if this failed.
2756                                 if (!sp.createDirectory(0777))
2757                                         Alert::error(_("Subdirectory creation failed!"),
2758                                                      _("Could not create subdirectory.\n"
2759                                                        "The template will be saved in the parent directory."));
2760                                 else
2761                                         result = subpath;
2762                         }
2763                 }
2764         }
2765         // Do we have a layout category?
2766         string const cat = b.params().baseClass() ?
2767                                 b.params().baseClass()->category()
2768                               : string();
2769         if (!cat.empty()) {
2770                 string subpath = addPath(result, cat);
2771                 // If we have a subdirectory for the category already,
2772                 // navigate there
2773                 FileName sp = FileName(subpath);
2774                 if (sp.isDirectory())
2775                         result = subpath;
2776                 else {
2777                         // Ask whether we should create such a subdirectory
2778                         docstring const text =
2779                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2780                                           "appropriate to the layout category (%1$s).\n"
2781                                           "This subdirectory does not exists yet.\n"
2782                                           "Do you want to create it?"),
2783                                         _(cat));
2784                         if (Alert::prompt(_("Create Category Directory?"),
2785                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2786                                 // If the user agreed, we try to create it and report if this failed.
2787                                 if (!sp.createDirectory(0777))
2788                                         Alert::error(_("Subdirectory creation failed!"),
2789                                                      _("Could not create subdirectory.\n"
2790                                                        "The template will be saved in the parent directory."));
2791                                 else
2792                                         result = subpath;
2793                         }
2794                 }
2795         }
2796         return result;
2797 }
2798
2799
2800 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2801 {
2802         FileName fname = b.fileName();
2803         FileName const oldname = fname;
2804         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2805
2806         if (!newname.empty()) {
2807                 // FIXME UNICODE
2808                 if (as_template)
2809                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2810                 else
2811                         fname = support::makeAbsPath(to_utf8(newname),
2812                                                      oldname.onlyPath().absFileName());
2813         } else {
2814                 // Switch to this Buffer.
2815                 setBuffer(&b);
2816
2817                 // No argument? Ask user through dialog.
2818                 // FIXME UNICODE
2819                 QString const title = as_template ? qt_("Choose a filename to save template as")
2820                                                   : qt_("Choose a filename to save document as");
2821                 FileDialog dlg(title);
2822                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2823                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2824
2825                 if (!isLyXFileName(fname.absFileName()))
2826                         fname.changeExtension(".lyx");
2827
2828                 string const path = as_template ?
2829                                         getTemplatesPath(b)
2830                                       : fname.onlyPath().absFileName();
2831                 FileDialog::Result result =
2832                         dlg.save(toqstr(path),
2833                                    QStringList(qt_("LyX Documents (*.lyx)")),
2834                                          toqstr(fname.onlyFileName()));
2835
2836                 if (result.first == FileDialog::Later)
2837                         return false;
2838
2839                 fname.set(fromqstr(result.second));
2840
2841                 if (fname.empty())
2842                         return false;
2843
2844                 if (!isLyXFileName(fname.absFileName()))
2845                         fname.changeExtension(".lyx");
2846         }
2847
2848         // fname is now the new Buffer location.
2849
2850         // if there is already a Buffer open with this name, we do not want
2851         // to have another one. (the second test makes sure we're not just
2852         // trying to overwrite ourselves, which is fine.)
2853         if (theBufferList().exists(fname) && fname != oldname
2854                   && theBufferList().getBuffer(fname) != &b) {
2855                 docstring const text =
2856                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
2857                             "Please close it before attempting to overwrite it.\n"
2858                             "Do you want to choose a new filename?"),
2859                                 from_utf8(fname.absFileName()));
2860                 int const ret = Alert::prompt(_("Chosen File Already Open"),
2861                         text, 0, 1, _("&Rename"), _("&Cancel"));
2862                 switch (ret) {
2863                 case 0: return renameBuffer(b, docstring(), kind);
2864                 case 1: return false;
2865                 }
2866                 //return false;
2867         }
2868
2869         bool const existsLocal = fname.exists();
2870         bool const existsInVC = LyXVC::fileInVC(fname);
2871         if (existsLocal || existsInVC) {
2872                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2873                 if (kind != LV_WRITE_AS && existsInVC) {
2874                         // renaming to a name that is already in VC
2875                         // would not work
2876                         docstring text = bformat(_("The document %1$s "
2877                                         "is already registered.\n\n"
2878                                         "Do you want to choose a new name?"),
2879                                 file);
2880                         docstring const title = (kind == LV_VC_RENAME) ?
2881                                 _("Rename document?") : _("Copy document?");
2882                         docstring const button = (kind == LV_VC_RENAME) ?
2883                                 _("&Rename") : _("&Copy");
2884                         int const ret = Alert::prompt(title, text, 0, 1,
2885                                 button, _("&Cancel"));
2886                         switch (ret) {
2887                         case 0: return renameBuffer(b, docstring(), kind);
2888                         case 1: return false;
2889                         }
2890                 }
2891
2892                 if (existsLocal) {
2893                         docstring text = bformat(_("The document %1$s "
2894                                         "already exists.\n\n"
2895                                         "Do you want to overwrite that document?"),
2896                                 file);
2897                         int const ret = Alert::prompt(_("Overwrite document?"),
2898                                         text, 0, 2, _("&Overwrite"),
2899                                         _("&Rename"), _("&Cancel"));
2900                         switch (ret) {
2901                         case 0: break;
2902                         case 1: return renameBuffer(b, docstring(), kind);
2903                         case 2: return false;
2904                         }
2905                 }
2906         }
2907
2908         switch (kind) {
2909         case LV_VC_RENAME: {
2910                 string msg = b.lyxvc().rename(fname);
2911                 if (msg.empty())
2912                         return false;
2913                 message(from_utf8(msg));
2914                 break;
2915         }
2916         case LV_VC_COPY: {
2917                 string msg = b.lyxvc().copy(fname);
2918                 if (msg.empty())
2919                         return false;
2920                 message(from_utf8(msg));
2921                 break;
2922         }
2923         case LV_WRITE_AS:
2924         case LV_WRITE_AS_TEMPLATE:
2925                 break;
2926         }
2927         // LyXVC created the file already in case of LV_VC_RENAME or
2928         // LV_VC_COPY, but call saveBuffer() nevertheless to get
2929         // relative paths of included stuff right if we moved e.g. from
2930         // /a/b.lyx to /a/c/b.lyx.
2931
2932         bool const saved = saveBuffer(b, fname);
2933         if (saved)
2934                 b.reload();
2935         return saved;
2936 }
2937
2938
2939 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2940 {
2941         FileName fname = b.fileName();
2942
2943         FileDialog dlg(qt_("Choose a filename to export the document as"));
2944         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2945
2946         QStringList types;
2947         QString const anyformat = qt_("Guess from extension (*.*)");
2948         types << anyformat;
2949
2950         vector<Format const *> export_formats;
2951         for (Format const & f : theFormats())
2952                 if (f.documentFormat())
2953                         export_formats.push_back(&f);
2954         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2955         map<QString, string> fmap;
2956         QString filter;
2957         string ext;
2958         for (Format const * f : export_formats) {
2959                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2960                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2961                                                      loc_prettyname,
2962                                                      from_ascii(f->extension())));
2963                 types << loc_filter;
2964                 fmap[loc_filter] = f->name();
2965                 if (from_ascii(f->name()) == iformat) {
2966                         filter = loc_filter;
2967                         ext = f->extension();
2968                 }
2969         }
2970         string ofname = fname.onlyFileName();
2971         if (!ext.empty())
2972                 ofname = support::changeExtension(ofname, ext);
2973         FileDialog::Result result =
2974                 dlg.save(toqstr(fname.onlyPath().absFileName()),
2975                          types,
2976                          toqstr(ofname),
2977                          &filter);
2978         if (result.first != FileDialog::Chosen)
2979                 return false;
2980
2981         string fmt_name;
2982         fname.set(fromqstr(result.second));
2983         if (filter == anyformat)
2984                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2985         else
2986                 fmt_name = fmap[filter];
2987         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2988                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2989
2990         if (fmt_name.empty() || fname.empty())
2991                 return false;
2992
2993         // fname is now the new Buffer location.
2994         if (FileName(fname).exists()) {
2995                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2996                 docstring text = bformat(_("The document %1$s already "
2997                                            "exists.\n\nDo you want to "
2998                                            "overwrite that document?"),
2999                                          file);
3000                 int const ret = Alert::prompt(_("Overwrite document?"),
3001                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3002                 switch (ret) {
3003                 case 0: break;
3004                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3005                 case 2: return false;
3006                 }
3007         }
3008
3009         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3010         DispatchResult dr;
3011         dispatch(cmd, dr);
3012         return dr.dispatched();
3013 }
3014
3015
3016 bool GuiView::saveBuffer(Buffer & b)
3017 {
3018         return saveBuffer(b, FileName());
3019 }
3020
3021
3022 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3023 {
3024         if (workArea(b) && workArea(b)->inDialogMode())
3025                 return true;
3026
3027         if (fn.empty() && b.isUnnamed())
3028                 return renameBuffer(b, docstring());
3029
3030         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3031         if (success) {
3032                 theSession().lastFiles().add(b.fileName());
3033                 theSession().writeFile();
3034                 return true;
3035         }
3036
3037         // Switch to this Buffer.
3038         setBuffer(&b);
3039
3040         // FIXME: we don't tell the user *WHY* the save failed !!
3041         docstring const file = makeDisplayPath(b.absFileName(), 30);
3042         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3043                                    "Do you want to rename the document and "
3044                                    "try again?"), file);
3045         int const ret = Alert::prompt(_("Rename and save?"),
3046                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3047         switch (ret) {
3048         case 0:
3049                 if (!renameBuffer(b, docstring()))
3050                         return false;
3051                 break;
3052         case 1:
3053                 break;
3054         case 2:
3055                 return false;
3056         }
3057
3058         return saveBuffer(b, fn);
3059 }
3060
3061
3062 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3063 {
3064         return closeWorkArea(wa, false);
3065 }
3066
3067
3068 // We only want to close the buffer if it is not visible in other workareas
3069 // of the same view, nor in other views, and if this is not a child
3070 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3071 {
3072         Buffer & buf = wa->bufferView().buffer();
3073
3074         bool last_wa = d.countWorkAreasOf(buf) == 1
3075                 && !inOtherView(buf) && !buf.parent();
3076
3077         bool close_buffer = last_wa;
3078
3079         if (last_wa) {
3080                 if (lyxrc.close_buffer_with_last_view == "yes")
3081                         ; // Nothing to do
3082                 else if (lyxrc.close_buffer_with_last_view == "no")
3083                         close_buffer = false;
3084                 else {
3085                         docstring file;
3086                         if (buf.isUnnamed())
3087                                 file = from_utf8(buf.fileName().onlyFileName());
3088                         else
3089                                 file = buf.fileName().displayName(30);
3090                         docstring const text = bformat(
3091                                 _("Last view on document %1$s is being closed.\n"
3092                                   "Would you like to close or hide the document?\n"
3093                                   "\n"
3094                                   "Hidden documents can be displayed back through\n"
3095                                   "the menu: View->Hidden->...\n"
3096                                   "\n"
3097                                   "To remove this question, set your preference in:\n"
3098                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3099                                 ), file);
3100                         int ret = Alert::prompt(_("Close or hide document?"),
3101                                 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3102                         if (ret == 2)
3103                                 return false;
3104                         close_buffer = (ret == 0);
3105                 }
3106         }
3107
3108         return closeWorkArea(wa, close_buffer);
3109 }
3110
3111
3112 bool GuiView::closeBuffer()
3113 {
3114         GuiWorkArea * wa = currentMainWorkArea();
3115         // coverity complained about this
3116         // it seems unnecessary, but perhaps is worth the check
3117         LASSERT(wa, return false);
3118
3119         setCurrentWorkArea(wa);
3120         Buffer & buf = wa->bufferView().buffer();
3121         return closeWorkArea(wa, !buf.parent());
3122 }
3123
3124
3125 void GuiView::writeSession() const {
3126         GuiWorkArea const * active_wa = currentMainWorkArea();
3127         for (int i = 0; i < d.splitter_->count(); ++i) {
3128                 TabWorkArea * twa = d.tabWorkArea(i);
3129                 for (int j = 0; j < twa->count(); ++j) {
3130                         GuiWorkArea * wa = twa->workArea(j);
3131                         Buffer & buf = wa->bufferView().buffer();
3132                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3133                 }
3134         }
3135 }
3136
3137
3138 bool GuiView::closeBufferAll()
3139 {
3140
3141         for (auto & buf : theBufferList()) {
3142                 if (!saveBufferIfNeeded(*buf, false)) {
3143                         // Closing has been cancelled, so abort.
3144                         return false;
3145                 }
3146         }
3147
3148         // Close the workareas in all other views
3149         QList<int> const ids = guiApp->viewIds();
3150         for (int i = 0; i != ids.size(); ++i) {
3151                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3152                         return false;
3153         }
3154
3155         // Close our own workareas
3156         if (!closeWorkAreaAll())
3157                 return false;
3158
3159         return true;
3160 }
3161
3162
3163 bool GuiView::closeWorkAreaAll()
3164 {
3165         setCurrentWorkArea(currentMainWorkArea());
3166
3167         // We might be in a situation that there is still a tabWorkArea, but
3168         // there are no tabs anymore. This can happen when we get here after a
3169         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3170         // many TabWorkArea's have no documents anymore.
3171         int empty_twa = 0;
3172
3173         // We have to call count() each time, because it can happen that
3174         // more than one splitter will disappear in one iteration (bug 5998).
3175         while (d.splitter_->count() > empty_twa) {
3176                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3177
3178                 if (twa->count() == 0)
3179                         ++empty_twa;
3180                 else {
3181                         setCurrentWorkArea(twa->currentWorkArea());
3182                         if (!closeTabWorkArea(twa))
3183                                 return false;
3184                 }
3185         }
3186         return true;
3187 }
3188
3189
3190 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3191 {
3192         if (!wa)
3193                 return false;
3194
3195         Buffer & buf = wa->bufferView().buffer();
3196
3197         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3198                 Alert::warning(_("Close document"),
3199                         _("Document could not be closed because it is being processed by LyX."));
3200                 return false;
3201         }
3202
3203         if (close_buffer)
3204                 return closeBuffer(buf);
3205         else {
3206                 if (!inMultiTabs(wa))
3207                         if (!saveBufferIfNeeded(buf, true))
3208                                 return false;
3209                 removeWorkArea(wa);
3210                 return true;
3211         }
3212 }
3213
3214
3215 bool GuiView::closeBuffer(Buffer & buf)
3216 {
3217         bool success = true;
3218         for (Buffer * child_buf : buf.getChildren()) {
3219                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3220                         child_buf->setParent(nullptr);
3221                         continue;
3222                 }
3223
3224                 // FIXME: should we look in other tabworkareas?
3225                 // ANSWER: I don't think so. I've tested, and if the child is
3226                 // open in some other window, it closes without a problem.
3227                 GuiWorkArea * child_wa = workArea(*child_buf);
3228                 if (child_wa) {
3229                         if (closing_)
3230                                 // If we are in a close_event all children will be closed in some time,
3231                                 // so no need to do it here. This will ensure that the children end up
3232                                 // in the session file in the correct order. If we close the master
3233                                 // buffer, we can close or release the child buffers here too.
3234                                 continue;
3235                         success = closeWorkArea(child_wa, true);
3236                         if (!success)
3237                                 break;
3238                 } else {
3239                         // In this case the child buffer is open but hidden.
3240                         // Even in this case, children can be dirty (e.g.,
3241                         // after a label change in the master, see #11405).
3242                         // Therefore, check this
3243                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3244                                 // If we are in a close_event all children will be closed in some time,
3245                                 // so no need to do it here. This will ensure that the children end up
3246                                 // in the session file in the correct order. If we close the master
3247                                 // buffer, we can close or release the child buffers here too.
3248                                 continue;
3249                         }
3250                         // Save dirty buffers also if closing_!
3251                         if (saveBufferIfNeeded(*child_buf, false)) {
3252                                 child_buf->removeAutosaveFile();
3253                                 theBufferList().release(child_buf);
3254                         } else {
3255                                 // Saving of dirty children has been cancelled.
3256                                 // Cancel the whole process.
3257                                 success = false;
3258                                 break;
3259                         }
3260                 }
3261         }
3262         if (success) {
3263                 // goto bookmark to update bookmark pit.
3264                 // FIXME: we should update only the bookmarks related to this buffer!
3265                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3266                 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3267                         guiApp->gotoBookmark(i + 1, false, false);
3268
3269                 if (saveBufferIfNeeded(buf, false)) {
3270                         buf.removeAutosaveFile();
3271                         theBufferList().release(&buf);
3272                         return true;
3273                 }
3274         }
3275         // open all children again to avoid a crash because of dangling
3276         // pointers (bug 6603)
3277         buf.updateBuffer();
3278         return false;
3279 }
3280
3281
3282 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3283 {
3284         while (twa == d.currentTabWorkArea()) {
3285                 twa->setCurrentIndex(twa->count() - 1);
3286
3287                 GuiWorkArea * wa = twa->currentWorkArea();
3288                 Buffer & b = wa->bufferView().buffer();
3289
3290                 // We only want to close the buffer if the same buffer is not visible
3291                 // in another view, and if this is not a child and if we are closing
3292                 // a view (not a tabgroup).
3293                 bool const close_buffer =
3294                         !inOtherView(b) && !b.parent() && closing_;
3295
3296                 if (!closeWorkArea(wa, close_buffer))
3297                         return false;
3298         }
3299         return true;
3300 }
3301
3302
3303 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3304 {
3305         if (buf.isClean() || buf.paragraphs().empty())
3306                 return true;
3307
3308         // Switch to this Buffer.
3309         setBuffer(&buf);
3310
3311         docstring file;
3312         bool exists;
3313         // FIXME: Unicode?
3314         if (buf.isUnnamed()) {
3315                 file = from_utf8(buf.fileName().onlyFileName());
3316                 exists = false;
3317         } else {
3318                 FileName filename = buf.fileName();
3319                 filename.refresh();
3320                 file = filename.displayName(30);
3321                 exists = filename.exists();
3322         }
3323
3324         // Bring this window to top before asking questions.
3325         raise();
3326         activateWindow();
3327
3328         int ret;
3329         if (hiding && buf.isUnnamed()) {
3330                 docstring const text = bformat(_("The document %1$s has not been "
3331                                                  "saved yet.\n\nDo you want to save "
3332                                                  "the document?"), file);
3333                 ret = Alert::prompt(_("Save new document?"),
3334                         text, 0, 1, _("&Save"), _("&Cancel"));
3335                 if (ret == 1)
3336                         ++ret;
3337         } else {
3338                 docstring const text = exists ?
3339                         bformat(_("The document %1$s has unsaved changes."
3340                                   "\n\nDo you want to save the document or "
3341                                   "discard the changes?"), file) :
3342                         bformat(_("The document %1$s has not been saved yet."
3343                                   "\n\nDo you want to save the document or "
3344                                   "discard it entirely?"), file);
3345                 docstring const title = exists ?
3346                         _("Save changed document?") : _("Save document?");
3347                 ret = Alert::prompt(title, text, 0, 2,
3348                                     _("&Save"), _("&Discard"), _("&Cancel"));
3349         }
3350
3351         switch (ret) {
3352         case 0:
3353                 if (!saveBuffer(buf))
3354                         return false;
3355                 break;
3356         case 1:
3357                 // If we crash after this we could have no autosave file
3358                 // but I guess this is really improbable (Jug).
3359                 // Sometimes improbable things happen:
3360                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3361                 // buf.removeAutosaveFile();
3362                 if (hiding)
3363                         // revert all changes
3364                         reloadBuffer(buf);
3365                 buf.markClean();
3366                 break;
3367         case 2:
3368                 return false;
3369         }
3370         return true;
3371 }
3372
3373
3374 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3375 {
3376         Buffer & buf = wa->bufferView().buffer();
3377
3378         for (int i = 0; i != d.splitter_->count(); ++i) {
3379                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3380                 if (wa_ && wa_ != wa)
3381                         return true;
3382         }
3383         return inOtherView(buf);
3384 }
3385
3386
3387 bool GuiView::inOtherView(Buffer & buf)
3388 {
3389         QList<int> const ids = guiApp->viewIds();
3390
3391         for (int i = 0; i != ids.size(); ++i) {
3392                 if (id_ == ids[i])
3393                         continue;
3394
3395                 if (guiApp->view(ids[i]).workArea(buf))
3396                         return true;
3397         }
3398         return false;
3399 }
3400
3401
3402 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3403 {
3404         if (!documentBufferView())
3405                 return;
3406
3407         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3408                 Buffer * const curbuf = &documentBufferView()->buffer();
3409                 int nwa = twa->count();
3410                 for (int i = 0; i < nwa; ++i) {
3411                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3412                                 int next_index;
3413                                 if (np == NEXTBUFFER)
3414                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3415                                 else
3416                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3417                                 if (move)
3418                                         twa->moveTab(i, next_index);
3419                                 else
3420                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3421                                 break;
3422                         }
3423                 }
3424         }
3425 }
3426
3427
3428 /// make sure the document is saved
3429 static bool ensureBufferClean(Buffer * buffer)
3430 {
3431         LASSERT(buffer, return false);
3432         if (buffer->isClean() && !buffer->isUnnamed())
3433                 return true;
3434
3435         docstring const file = buffer->fileName().displayName(30);
3436         docstring title;
3437         docstring text;
3438         if (!buffer->isUnnamed()) {
3439                 text = bformat(_("The document %1$s has unsaved "
3440                                                  "changes.\n\nDo you want to save "
3441                                                  "the document?"), file);
3442                 title = _("Save changed document?");
3443
3444         } else {
3445                 text = bformat(_("The document %1$s has not been "
3446                                                  "saved yet.\n\nDo you want to save "
3447                                                  "the document?"), file);
3448                 title = _("Save new document?");
3449         }
3450         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3451
3452         if (ret == 0)
3453                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3454
3455         return buffer->isClean() && !buffer->isUnnamed();
3456 }
3457
3458
3459 bool GuiView::reloadBuffer(Buffer & buf)
3460 {
3461         currentBufferView()->cursor().reset();
3462         Buffer::ReadStatus status = buf.reload();
3463         return status == Buffer::ReadSuccess;
3464 }
3465
3466
3467 void GuiView::checkExternallyModifiedBuffers()
3468 {
3469         for (Buffer * buf : theBufferList()) {
3470                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3471                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3472                                         " Reload now? Any local changes will be lost."),
3473                                         from_utf8(buf->absFileName()));
3474                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3475                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3476                         if (!ret)
3477                                 reloadBuffer(*buf);
3478                 }
3479         }
3480 }
3481
3482
3483 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3484 {
3485         Buffer * buffer = documentBufferView()
3486                 ? &(documentBufferView()->buffer()) : nullptr;
3487
3488         switch (cmd.action()) {
3489         case LFUN_VC_REGISTER:
3490                 if (!buffer || !ensureBufferClean(buffer))
3491                         break;
3492                 if (!buffer->lyxvc().inUse()) {
3493                         if (buffer->lyxvc().registrer()) {
3494                                 reloadBuffer(*buffer);
3495                                 dr.clearMessageUpdate();
3496                         }
3497                 }
3498                 break;
3499
3500         case LFUN_VC_RENAME:
3501         case LFUN_VC_COPY: {
3502                 if (!buffer || !ensureBufferClean(buffer))
3503                         break;
3504                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3505                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3506                                 // Some changes are not yet committed.
3507                                 // We test here and not in getStatus(), since
3508                                 // this test is expensive.
3509                                 string log;
3510                                 LyXVC::CommandResult ret =
3511                                         buffer->lyxvc().checkIn(log);
3512                                 dr.setMessage(log);
3513                                 if (ret == LyXVC::ErrorCommand ||
3514                                     ret == LyXVC::VCSuccess)
3515                                         reloadBuffer(*buffer);
3516                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3517                                         frontend::Alert::error(
3518                                                 _("Revision control error."),
3519                                                 _("Document could not be checked in."));
3520                                         break;
3521                                 }
3522                         }
3523                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3524                                 LV_VC_RENAME : LV_VC_COPY;
3525                         renameBuffer(*buffer, cmd.argument(), kind);
3526                 }
3527                 break;
3528         }
3529
3530         case LFUN_VC_CHECK_IN:
3531                 if (!buffer || !ensureBufferClean(buffer))
3532                         break;
3533                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3534                         string log;
3535                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3536                         dr.setMessage(log);
3537                         // Only skip reloading if the checkin was cancelled or
3538                         // an error occurred before the real checkin VCS command
3539                         // was executed, since the VCS might have changed the
3540                         // file even if it could not checkin successfully.
3541                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3542                                 reloadBuffer(*buffer);
3543                 }
3544                 break;
3545
3546         case LFUN_VC_CHECK_OUT:
3547                 if (!buffer || !ensureBufferClean(buffer))
3548                         break;
3549                 if (buffer->lyxvc().inUse()) {
3550                         dr.setMessage(buffer->lyxvc().checkOut());
3551                         reloadBuffer(*buffer);
3552                 }
3553                 break;
3554
3555         case LFUN_VC_LOCKING_TOGGLE:
3556                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3557                         break;
3558                 if (buffer->lyxvc().inUse()) {
3559                         string res = buffer->lyxvc().lockingToggle();
3560                         if (res.empty()) {
3561                                 frontend::Alert::error(_("Revision control error."),
3562                                 _("Error when setting the locking property."));
3563                         } else {
3564                                 dr.setMessage(res);
3565                                 reloadBuffer(*buffer);
3566                         }
3567                 }
3568                 break;
3569
3570         case LFUN_VC_REVERT:
3571                 if (!buffer)
3572                         break;
3573                 if (buffer->lyxvc().revert()) {
3574                         reloadBuffer(*buffer);
3575                         dr.clearMessageUpdate();
3576                 }
3577                 break;
3578
3579         case LFUN_VC_UNDO_LAST:
3580                 if (!buffer)
3581                         break;
3582                 buffer->lyxvc().undoLast();
3583                 reloadBuffer(*buffer);
3584                 dr.clearMessageUpdate();
3585                 break;
3586
3587         case LFUN_VC_REPO_UPDATE:
3588                 if (!buffer)
3589                         break;
3590                 if (ensureBufferClean(buffer)) {
3591                         dr.setMessage(buffer->lyxvc().repoUpdate());
3592                         checkExternallyModifiedBuffers();
3593                 }
3594                 break;
3595
3596         case LFUN_VC_COMMAND: {
3597                 string flag = cmd.getArg(0);
3598                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3599                         break;
3600                 docstring message;
3601                 if (contains(flag, 'M')) {
3602                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3603                                 break;
3604                 }
3605                 string path = cmd.getArg(1);
3606                 if (contains(path, "$$p") && buffer)
3607                         path = subst(path, "$$p", buffer->filePath());
3608                 LYXERR(Debug::LYXVC, "Directory: " << path);
3609                 FileName pp(path);
3610                 if (!pp.isReadableDirectory()) {
3611                         lyxerr << _("Directory is not accessible.") << endl;
3612                         break;
3613                 }
3614                 support::PathChanger p(pp);
3615
3616                 string command = cmd.getArg(2);
3617                 if (command.empty())
3618                         break;
3619                 if (buffer) {
3620                         command = subst(command, "$$i", buffer->absFileName());
3621                         command = subst(command, "$$p", buffer->filePath());
3622                 }
3623                 command = subst(command, "$$m", to_utf8(message));
3624                 LYXERR(Debug::LYXVC, "Command: " << command);
3625                 Systemcall one;
3626                 one.startscript(Systemcall::Wait, command);
3627
3628                 if (!buffer)
3629                         break;
3630                 if (contains(flag, 'I'))
3631                         buffer->markDirty();
3632                 if (contains(flag, 'R'))
3633                         reloadBuffer(*buffer);
3634
3635                 break;
3636                 }
3637
3638         case LFUN_VC_COMPARE: {
3639                 if (cmd.argument().empty()) {
3640                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3641                         break;
3642                 }
3643                 if (!buffer)
3644                         break;
3645
3646                 string rev1 = cmd.getArg(0);
3647                 string f1, f2;
3648
3649                 // f1
3650                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3651                         break;
3652
3653                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3654                         f2 = buffer->absFileName();
3655                 } else {
3656                         string rev2 = cmd.getArg(1);
3657                         if (rev2.empty())
3658                                 break;
3659                         // f2
3660                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3661                                 break;
3662                 }
3663
3664                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3665                                         f1 << "\n"  << f2 << "\n" );
3666                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3667                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3668                 break;
3669         }
3670
3671         default:
3672                 break;
3673         }
3674 }
3675
3676
3677 void GuiView::openChildDocument(string const & fname)
3678 {
3679         LASSERT(documentBufferView(), return);
3680         Buffer & buffer = documentBufferView()->buffer();
3681         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3682         documentBufferView()->saveBookmark(false);
3683         Buffer * child = nullptr;
3684         if (theBufferList().exists(filename)) {
3685                 child = theBufferList().getBuffer(filename);
3686                 setBuffer(child);
3687         } else {
3688                 message(bformat(_("Opening child document %1$s..."),
3689                         makeDisplayPath(filename.absFileName())));
3690                 child = loadDocument(filename, false);
3691         }
3692         // Set the parent name of the child document.
3693         // This makes insertion of citations and references in the child work,
3694         // when the target is in the parent or another child document.
3695         if (child)
3696                 child->setParent(&buffer);
3697 }
3698
3699
3700 bool GuiView::goToFileRow(string const & argument)
3701 {
3702         string file_name;
3703         int row = -1;
3704         size_t i = argument.find_last_of(' ');
3705         if (i != string::npos) {
3706                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3707                 istringstream is(argument.substr(i + 1));
3708                 is >> row;
3709                 if (is.fail())
3710                         i = string::npos;
3711         }
3712         if (i == string::npos) {
3713                 LYXERR0("Wrong argument: " << argument);
3714                 return false;
3715         }
3716         Buffer * buf = nullptr;
3717         string const realtmp = package().temp_dir().realPath();
3718         // We have to use os::path_prefix_is() here, instead of
3719         // simply prefixIs(), because the file name comes from
3720         // an external application and may need case adjustment.
3721         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3722                 buf = theBufferList().getBufferFromTmp(file_name, true);
3723                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3724                            << (buf ? " success" : " failed"));
3725         } else {
3726                 // Must replace extension of the file to be .lyx
3727                 // and get full path
3728                 FileName const s = fileSearch(string(),
3729                                                   support::changeExtension(file_name, ".lyx"), "lyx");
3730                 // Either change buffer or load the file
3731                 if (theBufferList().exists(s))
3732                         buf = theBufferList().getBuffer(s);
3733                 else if (s.exists()) {
3734                         buf = loadDocument(s);
3735                         if (!buf)
3736                                 return false;
3737                 } else {
3738                         message(bformat(
3739                                         _("File does not exist: %1$s"),
3740                                         makeDisplayPath(file_name)));
3741                         return false;
3742                 }
3743         }
3744         if (!buf) {
3745                 message(bformat(
3746                         _("No buffer for file: %1$s."),
3747                         makeDisplayPath(file_name))
3748                 );
3749                 return false;
3750         }
3751         setBuffer(buf);
3752         bool success = documentBufferView()->setCursorFromRow(row);
3753         if (!success) {
3754                 LYXERR(Debug::LATEX,
3755                        "setCursorFromRow: invalid position for row " << row);
3756                 frontend::Alert::error(_("Inverse Search Failed"),
3757                                        _("Invalid position requested by inverse search.\n"
3758                                          "You may need to update the viewed document."));
3759         }
3760         return success;
3761 }
3762
3763
3764 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3765 {
3766         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3767         menu->exec(QCursor::pos());
3768 }
3769
3770
3771 template<class T>
3772 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3773                 Buffer const * orig, Buffer * clone, string const & format)
3774 {
3775         Buffer::ExportStatus const status = func(format);
3776
3777         // the cloning operation will have produced a clone of the entire set of
3778         // documents, starting from the master. so we must delete those.
3779         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3780         delete mbuf;
3781         busyBuffers.remove(orig);
3782         return status;
3783 }
3784
3785
3786 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3787                 Buffer const * orig, Buffer * clone, string const & format)
3788 {
3789         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3790                         &Buffer::doExport;
3791         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3792 }
3793
3794
3795 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3796                 Buffer const * orig, Buffer * clone, string const & format)
3797 {
3798         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3799                         &Buffer::doExport;
3800         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3801 }
3802
3803
3804 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3805                 Buffer const * orig, Buffer * clone, string const & format)
3806 {
3807         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3808                         &Buffer::preview;
3809         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3810 }
3811
3812
3813 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3814                            string const & argument,
3815                            Buffer const * used_buffer,
3816                            docstring const & msg,
3817                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3818                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3819                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3820                            bool allow_async)
3821 {
3822         if (!used_buffer)
3823                 return false;
3824
3825         string format = argument;
3826         if (format.empty())
3827                 format = used_buffer->params().getDefaultOutputFormat();
3828         processing_format = format;
3829         if (!msg.empty()) {
3830                 progress_->clearMessages();
3831                 gv_->message(msg);
3832         }
3833 #if EXPORT_in_THREAD
3834         if (allow_async) {
3835                 GuiViewPrivate::busyBuffers.insert(used_buffer);
3836                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3837                 if (!cloned_buffer) {
3838                         Alert::error(_("Export Error"),
3839                                      _("Error cloning the Buffer."));
3840                         return false;
3841                 }
3842                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3843                                         asyncFunc,
3844                                         used_buffer,
3845                                         cloned_buffer,
3846                                         format);
3847                 setPreviewFuture(f);
3848                 last_export_format = used_buffer->params().bufferFormat();
3849                 (void) syncFunc;
3850                 (void) previewFunc;
3851                 // We are asynchronous, so we don't know here anything about the success
3852                 return true;
3853         } else {
3854                 Buffer::ExportStatus status;
3855                 if (syncFunc) {
3856                         status = (used_buffer->*syncFunc)(format, false);
3857                 } else if (previewFunc) {
3858                         status = (used_buffer->*previewFunc)(format);
3859                 } else
3860                         return false;
3861                 handleExportStatus(gv_, status, format);
3862                 (void) asyncFunc;
3863                 return (status == Buffer::ExportSuccess
3864                                 || status == Buffer::PreviewSuccess);
3865         }
3866 #else
3867         (void) allow_async;
3868         Buffer::ExportStatus status;
3869         if (syncFunc) {
3870                 status = (used_buffer->*syncFunc)(format, true);
3871         } else if (previewFunc) {
3872                 status = (used_buffer->*previewFunc)(format);
3873         } else
3874                 return false;
3875         handleExportStatus(gv_, status, format);
3876         (void) asyncFunc;
3877         return (status == Buffer::ExportSuccess
3878                         || status == Buffer::PreviewSuccess);
3879 #endif
3880 }
3881
3882 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3883 {
3884         BufferView * bv = currentBufferView();
3885         LASSERT(bv, return);
3886
3887         // Let the current BufferView dispatch its own actions.
3888         bv->dispatch(cmd, dr);
3889         if (dr.dispatched()) {
3890                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3891                         updateDialog("document", "");
3892                 return;
3893         }
3894
3895         // Try with the document BufferView dispatch if any.
3896         BufferView * doc_bv = documentBufferView();
3897         if (doc_bv && doc_bv != bv) {
3898                 doc_bv->dispatch(cmd, dr);
3899                 if (dr.dispatched()) {
3900                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3901                                 updateDialog("document", "");
3902                         return;
3903                 }
3904         }
3905
3906         // Then let the current Cursor dispatch its own actions.
3907         bv->cursor().dispatch(cmd);
3908
3909         // update completion. We do it here and not in
3910         // processKeySym to avoid another redraw just for a
3911         // changed inline completion
3912         if (cmd.origin() == FuncRequest::KEYBOARD) {
3913                 if (cmd.action() == LFUN_SELF_INSERT
3914                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3915                         updateCompletion(bv->cursor(), true, true);
3916                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3917                         updateCompletion(bv->cursor(), false, true);
3918                 else
3919                         updateCompletion(bv->cursor(), false, false);
3920         }
3921
3922         dr = bv->cursor().result();
3923 }
3924
3925
3926 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3927 {
3928         BufferView * bv = currentBufferView();
3929         // By default we won't need any update.
3930         dr.screenUpdate(Update::None);
3931         // assume cmd will be dispatched
3932         dr.dispatched(true);
3933
3934         Buffer * doc_buffer = documentBufferView()
3935                 ? &(documentBufferView()->buffer()) : nullptr;
3936
3937         if (cmd.origin() == FuncRequest::TOC) {
3938                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3939                 toc->doDispatch(bv->cursor(), cmd, dr);
3940                 return;
3941         }
3942
3943         string const argument = to_utf8(cmd.argument());
3944
3945         switch(cmd.action()) {
3946                 case LFUN_BUFFER_CHILD_OPEN:
3947                         openChildDocument(to_utf8(cmd.argument()));
3948                         break;
3949
3950                 case LFUN_BUFFER_IMPORT:
3951                         importDocument(to_utf8(cmd.argument()));
3952                         break;
3953
3954                 case LFUN_MASTER_BUFFER_EXPORT:
3955                         if (doc_buffer)
3956                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3957                         // fall through
3958                 case LFUN_BUFFER_EXPORT: {
3959                         if (!doc_buffer)
3960                                 break;
3961                         // GCC only sees strfwd.h when building merged
3962                         if (::lyx::operator==(cmd.argument(), "custom")) {
3963                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3964                                 // so the following test should not be needed.
3965                                 // In principle, we could try to switch to such a view...
3966                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3967                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3968                                 break;
3969                         }
3970
3971                         string const dest = cmd.getArg(1);
3972                         FileName target_dir;
3973                         if (!dest.empty() && FileName::isAbsolute(dest))
3974                                 target_dir = FileName(support::onlyPath(dest));
3975                         else
3976                                 target_dir = doc_buffer->fileName().onlyPath();
3977
3978                         string const format = (argument.empty() || argument == "default") ?
3979                                 doc_buffer->params().getDefaultOutputFormat() : argument;
3980
3981                         if ((dest.empty() && doc_buffer->isUnnamed())
3982                             || !target_dir.isDirWritable()) {
3983                                 exportBufferAs(*doc_buffer, from_utf8(format));
3984                                 break;
3985                         }
3986                         /* TODO/Review: Is it a problem to also export the children?
3987                                         See the update_unincluded flag */
3988                         d.asyncBufferProcessing(format,
3989                                                 doc_buffer,
3990                                                 _("Exporting ..."),
3991                                                 &GuiViewPrivate::exportAndDestroy,
3992                                                 &Buffer::doExport,
3993                                                 nullptr, cmd.allowAsync());
3994                         // TODO Inform user about success
3995                         break;
3996                 }
3997
3998                 case LFUN_BUFFER_EXPORT_AS: {
3999                         LASSERT(doc_buffer, break);
4000                         docstring f = cmd.argument();
4001                         if (f.empty())
4002                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4003                         exportBufferAs(*doc_buffer, f);
4004                         break;
4005                 }
4006
4007                 case LFUN_BUFFER_UPDATE: {
4008                         d.asyncBufferProcessing(argument,
4009                                                 doc_buffer,
4010                                                 _("Exporting ..."),
4011                                                 &GuiViewPrivate::compileAndDestroy,
4012                                                 &Buffer::doExport,
4013                                                 nullptr, cmd.allowAsync());
4014                         break;
4015                 }
4016                 case LFUN_BUFFER_VIEW: {
4017                         d.asyncBufferProcessing(argument,
4018                                                 doc_buffer,
4019                                                 _("Previewing ..."),
4020                                                 &GuiViewPrivate::previewAndDestroy,
4021                                                 nullptr,
4022                                                 &Buffer::preview, cmd.allowAsync());
4023                         break;
4024                 }
4025                 case LFUN_MASTER_BUFFER_UPDATE: {
4026                         d.asyncBufferProcessing(argument,
4027                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4028                                                 docstring(),
4029                                                 &GuiViewPrivate::compileAndDestroy,
4030                                                 &Buffer::doExport,
4031                                                 nullptr, cmd.allowAsync());
4032                         break;
4033                 }
4034                 case LFUN_MASTER_BUFFER_VIEW: {
4035                         d.asyncBufferProcessing(argument,
4036                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4037                                                 docstring(),
4038                                                 &GuiViewPrivate::previewAndDestroy,
4039                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4040                         break;
4041                 }
4042                 case LFUN_EXPORT_CANCEL: {
4043                         Systemcall::killscript();
4044                         break;
4045                 }
4046                 case LFUN_BUFFER_SWITCH: {
4047                         string const file_name = to_utf8(cmd.argument());
4048                         if (!FileName::isAbsolute(file_name)) {
4049                                 dr.setError(true);
4050                                 dr.setMessage(_("Absolute filename expected."));
4051                                 break;
4052                         }
4053
4054                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4055                         if (!buffer) {
4056                                 dr.setError(true);
4057                                 dr.setMessage(_("Document not loaded"));
4058                                 break;
4059                         }
4060
4061                         // Do we open or switch to the buffer in this view ?
4062                         if (workArea(*buffer)
4063                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4064                                 setBuffer(buffer);
4065                                 break;
4066                         }
4067
4068                         // Look for the buffer in other views
4069                         QList<int> const ids = guiApp->viewIds();
4070                         int i = 0;
4071                         for (; i != ids.size(); ++i) {
4072                                 GuiView & gv = guiApp->view(ids[i]);
4073                                 if (gv.workArea(*buffer)) {
4074                                         gv.raise();
4075                                         gv.activateWindow();
4076                                         gv.setFocus();
4077                                         gv.setBuffer(buffer);
4078                                         break;
4079                                 }
4080                         }
4081
4082                         // If necessary, open a new window as a last resort
4083                         if (i == ids.size()) {
4084                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4085                                 lyx::dispatch(cmd);
4086                         }
4087                         break;
4088                 }
4089
4090                 case LFUN_BUFFER_NEXT:
4091                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4092                         break;
4093
4094                 case LFUN_BUFFER_MOVE_NEXT:
4095                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4096                         break;
4097
4098                 case LFUN_BUFFER_PREVIOUS:
4099                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
4100                         break;
4101
4102                 case LFUN_BUFFER_MOVE_PREVIOUS:
4103                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
4104                         break;
4105
4106                 case LFUN_BUFFER_CHKTEX:
4107                         LASSERT(doc_buffer, break);
4108                         doc_buffer->runChktex();
4109                         break;
4110
4111                 case LFUN_COMMAND_EXECUTE: {
4112                         command_execute_ = true;
4113                         minibuffer_focus_ = true;
4114                         break;
4115                 }
4116                 case LFUN_DROP_LAYOUTS_CHOICE:
4117                         d.layout_->showPopup();
4118                         break;
4119
4120                 case LFUN_MENU_OPEN:
4121                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4122                                 menu->exec(QCursor::pos());
4123                         break;
4124
4125                 case LFUN_FILE_INSERT: {
4126                         if (cmd.getArg(1) == "ignorelang")
4127                                 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4128                         else
4129                                 insertLyXFile(cmd.argument());
4130                         break;
4131                 }
4132
4133                 case LFUN_FILE_INSERT_PLAINTEXT:
4134                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4135                         string const fname = to_utf8(cmd.argument());
4136                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4137                                 dr.setMessage(_("Absolute filename expected."));
4138                                 break;
4139                         }
4140
4141                         FileName filename(fname);
4142                         if (fname.empty()) {
4143                                 FileDialog dlg(qt_("Select file to insert"));
4144
4145                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4146                                         QStringList(qt_("All Files (*)")));
4147
4148                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4149                                         dr.setMessage(_("Canceled."));
4150                                         break;
4151                                 }
4152
4153                                 filename.set(fromqstr(result.second));
4154                         }
4155
4156                         if (bv) {
4157                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4158                                 bv->dispatch(new_cmd, dr);
4159                         }
4160                         break;
4161                 }
4162
4163                 case LFUN_BUFFER_RELOAD: {
4164                         LASSERT(doc_buffer, break);
4165
4166                         // drop changes?
4167                         bool drop = (cmd.argument() == "dump");
4168
4169                         int ret = 0;
4170                         if (!drop && !doc_buffer->isClean()) {
4171                                 docstring const file =
4172                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4173                                 if (doc_buffer->notifiesExternalModification()) {
4174                                         docstring text = _("The current version will be lost. "
4175                                             "Are you sure you want to load the version on disk "
4176                                             "of the document %1$s?");
4177                                         ret = Alert::prompt(_("Reload saved document?"),
4178                                                             bformat(text, file), 1, 1,
4179                                                             _("&Reload"), _("&Cancel"));
4180                                 } else {
4181                                         docstring text = _("Any changes will be lost. "
4182                                             "Are you sure you want to revert to the saved version "
4183                                             "of the document %1$s?");
4184                                         ret = Alert::prompt(_("Revert to saved document?"),
4185                                                             bformat(text, file), 1, 1,
4186                                                             _("&Revert"), _("&Cancel"));
4187                                 }
4188                         }
4189
4190                         if (ret == 0) {
4191                                 doc_buffer->markClean();
4192                                 reloadBuffer(*doc_buffer);
4193                                 dr.forceBufferUpdate();
4194                         }
4195                         break;
4196                 }
4197
4198                 case LFUN_BUFFER_RESET_EXPORT:
4199                         LASSERT(doc_buffer, break);
4200                         doc_buffer->requireFreshStart(true);
4201                         dr.setMessage(_("Buffer export reset."));
4202                         break;
4203
4204                 case LFUN_BUFFER_WRITE:
4205                         LASSERT(doc_buffer, break);
4206                         saveBuffer(*doc_buffer);
4207                         break;
4208
4209                 case LFUN_BUFFER_WRITE_AS:
4210                         LASSERT(doc_buffer, break);
4211                         renameBuffer(*doc_buffer, cmd.argument());
4212                         break;
4213
4214                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4215                         LASSERT(doc_buffer, break);
4216                         renameBuffer(*doc_buffer, cmd.argument(),
4217                                      LV_WRITE_AS_TEMPLATE);
4218                         break;
4219
4220                 case LFUN_BUFFER_WRITE_ALL: {
4221                         Buffer * first = theBufferList().first();
4222                         if (!first)
4223                                 break;
4224                         message(_("Saving all documents..."));
4225                         // We cannot use a for loop as the buffer list cycles.
4226                         Buffer * b = first;
4227                         do {
4228                                 if (!b->isClean()) {
4229                                         saveBuffer(*b);
4230                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4231                                 }
4232                                 b = theBufferList().next(b);
4233                         } while (b != first);
4234                         dr.setMessage(_("All documents saved."));
4235                         break;
4236                 }
4237
4238                 case LFUN_MASTER_BUFFER_FORALL: {
4239                         if (!doc_buffer)
4240                                 break;
4241
4242                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4243                         funcToRun.allowAsync(false);
4244
4245                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4246                                 // Switch to other buffer view and resend cmd
4247                                 lyx::dispatch(FuncRequest(
4248                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4249                                 lyx::dispatch(funcToRun);
4250                         }
4251                         // switch back
4252                         lyx::dispatch(FuncRequest(
4253                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4254                         break;
4255                 }
4256
4257                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4258                         LASSERT(doc_buffer, break);
4259                         doc_buffer->clearExternalModification();
4260                         break;
4261
4262                 case LFUN_BUFFER_CLOSE:
4263                         closeBuffer();
4264                         break;
4265
4266                 case LFUN_BUFFER_CLOSE_ALL:
4267                         closeBufferAll();
4268                         break;
4269
4270                 case LFUN_DEVEL_MODE_TOGGLE:
4271                         devel_mode_ = !devel_mode_;
4272                         if (devel_mode_)
4273                                 dr.setMessage(_("Developer mode is now enabled."));
4274                         else
4275                                 dr.setMessage(_("Developer mode is now disabled."));
4276                         break;
4277
4278                 case LFUN_TOOLBAR_TOGGLE: {
4279                         string const name = cmd.getArg(0);
4280                         if (GuiToolbar * t = toolbar(name))
4281                                 t->toggle();
4282                         break;
4283                 }
4284
4285                 case LFUN_TOOLBAR_MOVABLE: {
4286                         string const name = cmd.getArg(0);
4287                         if (name == "*") {
4288                                 // toggle (all) toolbars movablility
4289                                 toolbarsMovable_ = !toolbarsMovable_;
4290                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4291                                         GuiToolbar * tb = toolbar(ti.name);
4292                                         if (tb && tb->isMovable() != toolbarsMovable_)
4293                                                 // toggle toolbar movablity if it does not fit lock
4294                                                 // (all) toolbars positions state silent = true, since
4295                                                 // status bar notifications are slow
4296                                                 tb->movable(true);
4297                                 }
4298                                 if (toolbarsMovable_)
4299                                         dr.setMessage(_("Toolbars unlocked."));
4300                                 else
4301                                         dr.setMessage(_("Toolbars locked."));
4302                         } else if (GuiToolbar * t = toolbar(name)) {
4303                                 // toggle current toolbar movablity
4304                                 t->movable();
4305                                 // update lock (all) toolbars positions
4306                                 updateLockToolbars();
4307                         }
4308                         break;
4309                 }
4310
4311                 case LFUN_ICON_SIZE: {
4312                         QSize size = d.iconSize(cmd.argument());
4313                         setIconSize(size);
4314                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4315                                                 size.width(), size.height()));
4316                         break;
4317                 }
4318
4319                 case LFUN_DIALOG_UPDATE: {
4320                         string const name = to_utf8(cmd.argument());
4321                         if (name == "prefs" || name == "document")
4322                                 updateDialog(name, string());
4323                         else if (name == "paragraph")
4324                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4325                         else if (currentBufferView()) {
4326                                 Inset * inset = currentBufferView()->editedInset(name);
4327                                 // Can only update a dialog connected to an existing inset
4328                                 if (inset) {
4329                                         // FIXME: get rid of this indirection; GuiView ask the inset
4330                                         // if he is kind enough to update itself...
4331                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4332                                         //FIXME: pass DispatchResult here?
4333                                         inset->dispatch(currentBufferView()->cursor(), fr);
4334                                 }
4335                         }
4336                         break;
4337                 }
4338
4339                 case LFUN_DIALOG_TOGGLE: {
4340                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4341                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4342                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4343                         break;
4344                 }
4345
4346                 case LFUN_DIALOG_DISCONNECT_INSET:
4347                         disconnectDialog(to_utf8(cmd.argument()));
4348                         break;
4349
4350                 case LFUN_DIALOG_HIDE: {
4351                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4352                         break;
4353                 }
4354
4355                 case LFUN_DIALOG_SHOW: {
4356                         string const name = cmd.getArg(0);
4357                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4358
4359                         if (name == "latexlog") {
4360                                 // getStatus checks that
4361                                 LASSERT(doc_buffer, break);
4362                                 Buffer::LogType type;
4363                                 string const logfile = doc_buffer->logName(&type);
4364                                 switch (type) {
4365                                 case Buffer::latexlog:
4366                                         sdata = "latex ";
4367                                         break;
4368                                 case Buffer::buildlog:
4369                                         sdata = "literate ";
4370                                         break;
4371                                 }
4372                                 sdata += Lexer::quoteString(logfile);
4373                                 showDialog("log", sdata);
4374                         } else if (name == "vclog") {
4375                                 // getStatus checks that
4376                                 LASSERT(doc_buffer, break);
4377                                 string const sdata2 = "vc " +
4378                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4379                                 showDialog("log", sdata2);
4380                         } else if (name == "symbols") {
4381                                 sdata = bv->cursor().getEncoding()->name();
4382                                 if (!sdata.empty())
4383                                         showDialog("symbols", sdata);
4384                         // bug 5274
4385                         } else if (name == "prefs" && isFullScreen()) {
4386                                 lfunUiToggle("fullscreen");
4387                                 showDialog("prefs", sdata);
4388                         } else
4389                                 showDialog(name, sdata);
4390                         break;
4391                 }
4392
4393                 case LFUN_MESSAGE:
4394                         dr.setMessage(cmd.argument());
4395                         break;
4396
4397                 case LFUN_UI_TOGGLE: {
4398                         string arg = cmd.getArg(0);
4399                         if (!lfunUiToggle(arg)) {
4400                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4401                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4402                         }
4403                         // Make sure the keyboard focus stays in the work area.
4404                         setFocus();
4405                         break;
4406                 }
4407
4408                 case LFUN_VIEW_SPLIT: {
4409                         LASSERT(doc_buffer, break);
4410                         string const orientation = cmd.getArg(0);
4411                         d.splitter_->setOrientation(orientation == "vertical"
4412                                 ? Qt::Vertical : Qt::Horizontal);
4413                         TabWorkArea * twa = addTabWorkArea();
4414                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4415                         setCurrentWorkArea(wa);
4416                         break;
4417                 }
4418                 case LFUN_TAB_GROUP_CLOSE:
4419                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4420                                 closeTabWorkArea(twa);
4421                                 d.current_work_area_ = nullptr;
4422                                 twa = d.currentTabWorkArea();
4423                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4424                                 if (twa) {
4425                                         // Make sure the work area is up to date.
4426                                         setCurrentWorkArea(twa->currentWorkArea());
4427                                 } else {
4428                                         setCurrentWorkArea(nullptr);
4429                                 }
4430                         }
4431                         break;
4432
4433                 case LFUN_VIEW_CLOSE:
4434                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4435                                 closeWorkArea(twa->currentWorkArea());
4436                                 d.current_work_area_ = nullptr;
4437                                 twa = d.currentTabWorkArea();
4438                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4439                                 if (twa) {
4440                                         // Make sure the work area is up to date.
4441                                         setCurrentWorkArea(twa->currentWorkArea());
4442                                 } else {
4443                                         setCurrentWorkArea(nullptr);
4444                                 }
4445                         }
4446                         break;
4447
4448                 case LFUN_COMPLETION_INLINE:
4449                         if (d.current_work_area_)
4450                                 d.current_work_area_->completer().showInline();
4451                         break;
4452
4453                 case LFUN_COMPLETION_POPUP:
4454                         if (d.current_work_area_)
4455                                 d.current_work_area_->completer().showPopup();
4456                         break;
4457
4458
4459                 case LFUN_COMPLETE:
4460                         if (d.current_work_area_)
4461                                 d.current_work_area_->completer().tab();
4462                         break;
4463
4464                 case LFUN_COMPLETION_CANCEL:
4465                         if (d.current_work_area_) {
4466                                 if (d.current_work_area_->completer().popupVisible())
4467                                         d.current_work_area_->completer().hidePopup();
4468                                 else
4469                                         d.current_work_area_->completer().hideInline();
4470                         }
4471                         break;
4472
4473                 case LFUN_COMPLETION_ACCEPT:
4474                         if (d.current_work_area_)
4475                                 d.current_work_area_->completer().activate();
4476                         break;
4477
4478                 case LFUN_BUFFER_ZOOM_IN:
4479                 case LFUN_BUFFER_ZOOM_OUT:
4480                 case LFUN_BUFFER_ZOOM: {
4481                         if (cmd.argument().empty()) {
4482                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4483                                         zoom_ratio_ = 1.0;
4484                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4485                                         zoom_ratio_ += 0.1;
4486                                 else
4487                                         zoom_ratio_ -= 0.1;
4488                         } else {
4489                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4490                                         zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4491                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4492                                         zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4493                                 else
4494                                         zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4495                         }
4496
4497                         // Actual zoom value: default zoom + fractional extra value
4498                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4499                         if (zoom < static_cast<int>(zoom_min_))
4500                                 zoom = zoom_min_;
4501
4502                         lyxrc.currentZoom = zoom;
4503
4504                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4505                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4506
4507                         guiApp->fontLoader().update();
4508                         dr.screenUpdate(Update::Force | Update::FitCursor);
4509                         break;
4510                 }
4511
4512                 case LFUN_VC_REGISTER:
4513                 case LFUN_VC_RENAME:
4514                 case LFUN_VC_COPY:
4515                 case LFUN_VC_CHECK_IN:
4516                 case LFUN_VC_CHECK_OUT:
4517                 case LFUN_VC_REPO_UPDATE:
4518                 case LFUN_VC_LOCKING_TOGGLE:
4519                 case LFUN_VC_REVERT:
4520                 case LFUN_VC_UNDO_LAST:
4521                 case LFUN_VC_COMMAND:
4522                 case LFUN_VC_COMPARE:
4523                         dispatchVC(cmd, dr);
4524                         break;
4525
4526                 case LFUN_SERVER_GOTO_FILE_ROW:
4527                         if(goToFileRow(to_utf8(cmd.argument())))
4528                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4529                         break;
4530
4531                 case LFUN_LYX_ACTIVATE:
4532                         activateWindow();
4533                         break;
4534
4535                 case LFUN_WINDOW_RAISE:
4536                         raise();
4537                         activateWindow();
4538                         showNormal();
4539                         break;
4540
4541                 case LFUN_FORWARD_SEARCH: {
4542                         // it seems safe to assume we have a document buffer, since
4543                         // getStatus wants one.
4544                         LASSERT(doc_buffer, break);
4545                         Buffer const * doc_master = doc_buffer->masterBuffer();
4546                         FileName const path(doc_master->temppath());
4547                         string const texname = doc_master->isChild(doc_buffer)
4548                                 ? DocFileName(changeExtension(
4549                                         doc_buffer->absFileName(),
4550                                                 "tex")).mangledFileName()
4551                                 : doc_buffer->latexName();
4552                         string const fulltexname =
4553                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4554                         string const mastername =
4555                                 removeExtension(doc_master->latexName());
4556                         FileName const dviname(addName(path.absFileName(),
4557                                         addExtension(mastername, "dvi")));
4558                         FileName const pdfname(addName(path.absFileName(),
4559                                         addExtension(mastername, "pdf")));
4560                         bool const have_dvi = dviname.exists();
4561                         bool const have_pdf = pdfname.exists();
4562                         if (!have_dvi && !have_pdf) {
4563                                 dr.setMessage(_("Please, preview the document first."));
4564                                 break;
4565                         }
4566                         string outname = dviname.onlyFileName();
4567                         string command = lyxrc.forward_search_dvi;
4568                         if (!have_dvi || (have_pdf &&
4569                             pdfname.lastModified() > dviname.lastModified())) {
4570                                 outname = pdfname.onlyFileName();
4571                                 command = lyxrc.forward_search_pdf;
4572                         }
4573
4574                         DocIterator cur = bv->cursor();
4575                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4576                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4577                                    << " cur:" << cur);
4578                         if (row == -1 || command.empty()) {
4579                                 dr.setMessage(_("Couldn't proceed."));
4580                                 break;
4581                         }
4582                         string texrow = convert<string>(row);
4583
4584                         command = subst(command, "$$n", texrow);
4585                         command = subst(command, "$$f", fulltexname);
4586                         command = subst(command, "$$t", texname);
4587                         command = subst(command, "$$o", outname);
4588
4589                         volatile PathChanger p(path);
4590                         Systemcall one;
4591                         one.startscript(Systemcall::DontWait, command);
4592                         break;
4593                 }
4594
4595                 case LFUN_SPELLING_CONTINUOUSLY:
4596                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4597                         dr.screenUpdate(Update::Force);
4598                         break;
4599
4600                 case LFUN_CITATION_OPEN: {
4601                         string pdfv, psv;
4602                         if (theFormats().getFormat("pdf"))
4603                                 pdfv = theFormats().getFormat("pdf")->viewer();
4604                         if (theFormats().getFormat("ps"))
4605                                 psv = theFormats().getFormat("ps")->viewer();
4606                         frontend::showTarget(argument, pdfv, psv);
4607                         break;
4608                 }
4609
4610                 default:
4611                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4612                         // let's try that:
4613                         dispatchToBufferView(cmd, dr);
4614                         break;
4615         }
4616
4617         // Part of automatic menu appearance feature.
4618         if (isFullScreen()) {
4619                 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4620                         menuBar()->hide();
4621         }
4622
4623         // Need to update bv because many LFUNs here might have destroyed it
4624         bv = currentBufferView();
4625
4626         // Clear non-empty selections
4627         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4628         if (bv) {
4629                 Cursor & cur = bv->cursor();
4630                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4631                         cur.clearSelection();
4632                 }
4633         }
4634 }
4635
4636
4637 bool GuiView::lfunUiToggle(string const & ui_component)
4638 {
4639         if (ui_component == "scrollbar") {
4640                 // hide() is of no help
4641                 if (d.current_work_area_->verticalScrollBarPolicy() ==
4642                         Qt::ScrollBarAlwaysOff)
4643
4644                         d.current_work_area_->setVerticalScrollBarPolicy(
4645                                 Qt::ScrollBarAsNeeded);
4646                 else
4647                         d.current_work_area_->setVerticalScrollBarPolicy(
4648                                 Qt::ScrollBarAlwaysOff);
4649         } else if (ui_component == "statusbar") {
4650                 statusBar()->setVisible(!statusBar()->isVisible());
4651         } else if (ui_component == "menubar") {
4652                 menuBar()->setVisible(!menuBar()->isVisible());
4653         } else
4654         if (ui_component == "frame") {
4655                 int const l = contentsMargins().left();
4656
4657                 //are the frames in default state?
4658                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4659                 if (l == 0) {
4660 #if QT_VERSION >  0x050903
4661                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4662 #endif
4663                         setContentsMargins(-2, -2, -2, -2);
4664                 } else {
4665 #if QT_VERSION >  0x050903
4666                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4667 #endif
4668                         setContentsMargins(0, 0, 0, 0);
4669                 }
4670         } else
4671         if (ui_component == "fullscreen") {
4672                 toggleFullScreen();
4673         } else
4674                 return false;
4675         return true;
4676 }
4677
4678
4679 void GuiView::toggleFullScreen()
4680 {
4681         setWindowState(windowState() ^ Qt::WindowFullScreen);
4682 }
4683
4684
4685 Buffer const * GuiView::updateInset(Inset const * inset)
4686 {
4687         if (!inset)
4688                 return nullptr;
4689
4690         Buffer const * inset_buffer = &(inset->buffer());
4691
4692         for (int i = 0; i != d.splitter_->count(); ++i) {
4693                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4694                 if (!wa)
4695                         continue;
4696                 Buffer const * buffer = &(wa->bufferView().buffer());
4697                 if (inset_buffer == buffer)
4698                         wa->scheduleRedraw(true);
4699         }
4700         return inset_buffer;
4701 }
4702
4703
4704 void GuiView::restartCaret()
4705 {
4706         /* When we move around, or type, it's nice to be able to see
4707          * the caret immediately after the keypress.
4708          */
4709         if (d.current_work_area_)
4710                 d.current_work_area_->startBlinkingCaret();
4711
4712         // Take this occasion to update the other GUI elements.
4713         updateDialogs();
4714         updateStatusBar();
4715 }
4716
4717
4718 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4719 {
4720         if (d.current_work_area_)
4721                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4722 }
4723
4724 namespace {
4725
4726 // This list should be kept in sync with the list of insets in
4727 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
4728 // dialog should have the same name as the inset.
4729 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4730 // docs in LyXAction.cpp.
4731
4732 char const * const dialognames[] = {
4733
4734 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4735 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4736 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4737 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4738 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4739 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4740 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4741 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4742
4743 char const * const * const end_dialognames =
4744         dialognames + (sizeof(dialognames) / sizeof(char *));
4745
4746 class cmpCStr {
4747 public:
4748         cmpCStr(char const * name) : name_(name) {}
4749         bool operator()(char const * other) {
4750                 return strcmp(other, name_) == 0;
4751         }
4752 private:
4753         char const * name_;
4754 };
4755
4756
4757 bool isValidName(string const & name)
4758 {
4759         return find_if(dialognames, end_dialognames,
4760                 cmpCStr(name.c_str())) != end_dialognames;
4761 }
4762
4763 } // namespace
4764
4765
4766 void GuiView::resetDialogs()
4767 {
4768         // Make sure that no LFUN uses any GuiView.
4769         guiApp->setCurrentView(nullptr);
4770         saveLayout();
4771         saveUISettings();
4772         menuBar()->clear();
4773         constructToolbars();
4774         guiApp->menus().fillMenuBar(menuBar(), this, false);
4775         d.layout_->updateContents(true);
4776         // Now update controls with current buffer.
4777         guiApp->setCurrentView(this);
4778         restoreLayout();
4779         restartCaret();
4780 }
4781
4782
4783 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4784 {
4785         for (QObject * child: widget->children()) {
4786                 if (child->inherits("QGroupBox")) {
4787                         QGroupBox * box = (QGroupBox*) child;
4788                         box->setFlat(flag);
4789                 } else {
4790                         flatGroupBoxes(child, flag);
4791                 }
4792         }
4793 }
4794
4795
4796 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4797 {
4798         if (!isValidName(name))
4799                 return nullptr;
4800
4801         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4802
4803         if (it != d.dialogs_.end()) {
4804                 if (hide_it)
4805                         it->second->hideView();
4806                 return it->second.get();
4807         }
4808
4809         Dialog * dialog = build(name);
4810         d.dialogs_[name].reset(dialog);
4811 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4812         // Force a uniform style for group boxes
4813         // On Mac non-flat works better, on Linux flat is standard
4814         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4815 #endif
4816         if (lyxrc.allow_geometry_session)
4817                 dialog->restoreSession();
4818         if (hide_it)
4819                 dialog->hideView();
4820         return dialog;
4821 }
4822
4823
4824 void GuiView::showDialog(string const & name, string const & sdata,
4825         Inset * inset)
4826 {
4827         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4828 }
4829
4830
4831 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4832         Inset * inset)
4833 {
4834         if (d.in_show_)
4835                 return;
4836
4837         const string name = fromqstr(qname);
4838         const string sdata = fromqstr(qdata);
4839
4840         d.in_show_ = true;
4841         try {
4842                 Dialog * dialog = findOrBuild(name, false);
4843                 if (dialog) {
4844                         bool const visible = dialog->isVisibleView();
4845                         dialog->showData(sdata);
4846                         if (currentBufferView())
4847                                 currentBufferView()->editInset(name, inset);
4848                         // We only set the focus to the new dialog if it was not yet
4849                         // visible in order not to change the existing previous behaviour
4850                         if (visible) {
4851                                 // activateWindow is needed for floating dockviews
4852                                 dialog->asQWidget()->raise();
4853                                 dialog->asQWidget()->activateWindow();
4854                                 dialog->asQWidget()->setFocus();
4855                         }
4856                 }
4857         }
4858         catch (ExceptionMessage const &) {
4859                 d.in_show_ = false;
4860                 throw;
4861         }
4862         d.in_show_ = false;
4863 }
4864
4865
4866 bool GuiView::isDialogVisible(string const & name) const
4867 {
4868         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4869         if (it == d.dialogs_.end())
4870                 return false;
4871         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4872 }
4873
4874
4875 void GuiView::hideDialog(string const & name, Inset * inset)
4876 {
4877         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4878         if (it == d.dialogs_.end())
4879                 return;
4880
4881         if (inset) {
4882                 if (!currentBufferView())
4883                         return;
4884                 if (inset != currentBufferView()->editedInset(name))
4885                         return;
4886         }
4887
4888         Dialog * const dialog = it->second.get();
4889         if (dialog->isVisibleView())
4890                 dialog->hideView();
4891         if (currentBufferView())
4892                 currentBufferView()->editInset(name, nullptr);
4893 }
4894
4895
4896 void GuiView::disconnectDialog(string const & name)
4897 {
4898         if (!isValidName(name))
4899                 return;
4900         if (currentBufferView())
4901                 currentBufferView()->editInset(name, nullptr);
4902 }
4903
4904
4905 void GuiView::hideAll() const
4906 {
4907         for(auto const & dlg_p : d.dialogs_)
4908                 dlg_p.second->hideView();
4909 }
4910
4911
4912 void GuiView::updateDialogs()
4913 {
4914         for(auto const & dlg_p : d.dialogs_) {
4915                 Dialog * dialog = dlg_p.second.get();
4916                 if (dialog) {
4917                         if (dialog->needBufferOpen() && !documentBufferView())
4918                                 hideDialog(fromqstr(dialog->name()), nullptr);
4919                         else if (dialog->isVisibleView())
4920                                 dialog->checkStatus();
4921                 }
4922         }
4923         updateToolbars();
4924         updateLayoutList();
4925 }
4926
4927
4928 Dialog * GuiView::build(string const & name)
4929 {
4930         return createDialog(*this, name);
4931 }
4932
4933
4934 SEMenu::SEMenu(QWidget * parent)
4935 {
4936         QAction * action = addAction(qt_("Disable Shell Escape"));
4937         connect(action, SIGNAL(triggered()),
4938                 parent, SLOT(disableShellEscape()));
4939 }
4940
4941 } // namespace frontend
4942 } // namespace lyx
4943
4944 #include "moc_GuiView.cpp"