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