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