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