]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiView.cpp
Get rid of setSectionResizeMode helper functions.
[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 #ifndef Q_WS_MAC
1497         title += from_ascii(" - LyX");
1498 #endif
1499         setWindowTitle(toqstr(title));
1500         // Sets the path for the window: this is used by OSX to
1501         // allow a context click on the title bar showing a menu
1502         // with the path up to the file
1503         setWindowFilePath(toqstr(buf.absFileName()));
1504         // Tell Qt whether the current document is changed
1505         setWindowModified(!buf.isClean());
1506
1507         if (buf.params().shell_escape)
1508                 shell_escape_->show();
1509         else
1510                 shell_escape_->hide();
1511
1512         if (buf.hasReadonlyFlag())
1513                 read_only_->show();
1514         else
1515                 read_only_->hide();
1516
1517         if (buf.lyxvc().inUse()) {
1518                 version_control_->show();
1519                 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1520         } else
1521                 version_control_->hide();
1522 }
1523
1524
1525 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1526 {
1527         if (d.current_work_area_)
1528                 // disconnect the current work area from all slots
1529                 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1530         disconnectBuffer();
1531         disconnectBufferView();
1532         connectBufferView(wa->bufferView());
1533         connectBuffer(wa->bufferView().buffer());
1534         d.current_work_area_ = wa;
1535         QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1536                          this, SLOT(updateWindowTitle(GuiWorkArea *)));
1537         QObject::connect(wa, SIGNAL(busy(bool)),
1538                          this, SLOT(setBusy(bool)));
1539         // connection of a signal to a signal
1540         QObject::connect(wa, SIGNAL(bufferViewChanged()),
1541                          this, SIGNAL(bufferViewChanged()));
1542         Q_EMIT updateWindowTitle(wa);
1543         Q_EMIT bufferViewChanged();
1544 }
1545
1546
1547 void GuiView::onBufferViewChanged()
1548 {
1549         structureChanged();
1550         // Buffer-dependent dialogs must be updated. This is done here because
1551         // some dialogs require buffer()->text.
1552         updateDialogs();
1553         zoom_slider_->setEnabled(currentBufferView());
1554         zoom_value_->setEnabled(currentBufferView());
1555         zoom_in_->setEnabled(currentBufferView());
1556         zoom_out_->setEnabled(currentBufferView());
1557 }
1558
1559
1560 void GuiView::on_lastWorkAreaRemoved()
1561 {
1562         if (closing_)
1563                 // We already are in a close event. Nothing more to do.
1564                 return;
1565
1566         if (d.splitter_->count() > 1)
1567                 // We have a splitter so don't close anything.
1568                 return;
1569
1570         // Reset and updates the dialogs.
1571         Q_EMIT bufferViewChanged();
1572
1573         resetWindowTitle();
1574         updateStatusBar();
1575
1576         if (lyxrc.open_buffers_in_tabs)
1577                 // Nothing more to do, the window should stay open.
1578                 return;
1579
1580         if (guiApp->viewIds().size() > 1) {
1581                 close();
1582                 return;
1583         }
1584
1585 #ifdef Q_OS_MAC
1586         // On Mac we also close the last window because the application stay
1587         // resident in memory. On other platforms we don't close the last
1588         // window because this would quit the application.
1589         close();
1590 #endif
1591 }
1592
1593
1594 void GuiView::updateStatusBar()
1595 {
1596         // let the user see the explicit message
1597         if (d.statusbar_timer_.isActive())
1598                 return;
1599
1600         showMessage();
1601 }
1602
1603
1604 void GuiView::showMessage()
1605 {
1606         if (busy_)
1607                 return;
1608         QString msg = toqstr(theGuiApp()->viewStatusMessage());
1609         if (msg.isEmpty()) {
1610                 BufferView const * bv = currentBufferView();
1611                 if (bv)
1612                         msg = toqstr(bv->cursor().currentState(devel_mode_));
1613                 else
1614                         msg = qt_("Welcome to LyX!");
1615         }
1616         statusBar()->showMessage(msg);
1617 }
1618
1619
1620 bool GuiView::statsEnabled() const
1621 {
1622         return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1623 }
1624
1625
1626 bool GuiView::event(QEvent * e)
1627 {
1628         switch (e->type())
1629         {
1630         // Useful debug code:
1631         //case QEvent::ActivationChange:
1632         //case QEvent::WindowDeactivate:
1633         //case QEvent::Paint:
1634         //case QEvent::Enter:
1635         //case QEvent::Leave:
1636         //case QEvent::HoverEnter:
1637         //case QEvent::HoverLeave:
1638         //case QEvent::HoverMove:
1639         //case QEvent::StatusTip:
1640         //case QEvent::DragEnter:
1641         //case QEvent::DragLeave:
1642         //case QEvent::Drop:
1643         //      break;
1644
1645         case QEvent::WindowStateChange: {
1646                 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1647                 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1648                 bool result = QMainWindow::event(e);
1649                 bool nfstate = (windowState() & Qt::WindowFullScreen);
1650                 if (!ofstate && nfstate) {
1651                         LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1652                         // switch to full-screen state
1653                         if (lyxrc.full_screen_statusbar)
1654                                 statusBar()->hide();
1655                         if (lyxrc.full_screen_menubar)
1656                                 menuBar()->hide();
1657                         if (lyxrc.full_screen_toolbars) {
1658                                 for (auto const & tb_p  : d.toolbars_)
1659                                         if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1660                                                 tb_p.second->hide();
1661                         }
1662                         for (int i = 0; i != d.splitter_->count(); ++i)
1663                                 d.tabWorkArea(i)->setFullScreen(true);
1664 #if QT_VERSION > 0x050903
1665                         //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1666                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1667 #endif
1668                         setContentsMargins(-2, -2, -2, -2);
1669                         // bug 5274
1670                         hideDialogs("prefs", nullptr);
1671                 } else if (ofstate && !nfstate) {
1672                         LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1673                         // switch back from full-screen state
1674                         if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1675                                 statusBar()->show();
1676                         if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1677                                 menuBar()->show();
1678                         if (lyxrc.full_screen_toolbars) {
1679                                 for (auto const & tb_p  : d.toolbars_)
1680                                         if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1681                                                 tb_p.second->show();
1682                                 //updateToolbars();
1683                         }
1684                         for (int i = 0; i != d.splitter_->count(); ++i)
1685                                 d.tabWorkArea(i)->setFullScreen(false);
1686 #if QT_VERSION > 0x050903
1687                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1688 #endif
1689                         setContentsMargins(0, 0, 0, 0);
1690                 }
1691                 return result;
1692         }
1693
1694         case QEvent::WindowActivate: {
1695                 GuiView * old_view = guiApp->currentView();
1696                 if (this == old_view) {
1697                         setFocus();
1698                         return QMainWindow::event(e);
1699                 }
1700                 if (old_view && old_view->currentBufferView()) {
1701                         // save current selection to the selection buffer to allow
1702                         // middle-button paste in this window.
1703                         cap::saveSelection(old_view->currentBufferView()->cursor());
1704                 }
1705                 guiApp->setCurrentView(this);
1706                 if (d.current_work_area_)
1707                         on_currentWorkAreaChanged(d.current_work_area_);
1708                 else
1709                         resetWindowTitle();
1710                 setFocus();
1711                 return QMainWindow::event(e);
1712         }
1713
1714         case QEvent::ShortcutOverride: {
1715                 // See bug 4888
1716                 if (isFullScreen() && menuBar()->isHidden()) {
1717                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1718                         // FIXME: we should also try to detect special LyX shortcut such as
1719                         // Alt-P and Alt-M. Right now there is a hack in
1720                         // GuiWorkArea::processKeySym() that hides again the menubar for
1721                         // those cases.
1722                         if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1723                                 menuBar()->show();
1724                                 return QMainWindow::event(e);
1725                         }
1726                 }
1727                 return QMainWindow::event(e);
1728         }
1729
1730         case QEvent::ApplicationPaletteChange: {
1731                 // runtime switch from/to dark mode
1732                 refillToolbars();
1733                 return QMainWindow::event(e);
1734         }
1735
1736         case QEvent::Gesture: {
1737                 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1738                 QGesture *gp = ge->gesture(Qt::PinchGesture);
1739                 if (gp) {
1740                         QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1741                         QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1742                         qreal totalScaleFactor = pinch->totalScaleFactor();
1743                         LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1744                         if (pinch->state() == Qt::GestureStarted) {
1745                                 initialZoom_ = lyxrc.currentZoom;
1746                                 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1747                         }
1748                         if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1749                                 qreal factor = initialZoom_ * totalScaleFactor;
1750                                 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1751                                 zoomValueChanged(factor);
1752                         }
1753                 }
1754                 return QMainWindow::event(e);
1755         }
1756
1757         default:
1758                 return QMainWindow::event(e);
1759         }
1760 }
1761
1762 void GuiView::resetWindowTitle()
1763 {
1764         setWindowTitle(qt_("LyX"));
1765 }
1766
1767 bool GuiView::focusNextPrevChild(bool /*next*/)
1768 {
1769         setFocus();
1770         return true;
1771 }
1772
1773
1774 bool GuiView::busy() const
1775 {
1776         return busy_ > 0;
1777 }
1778
1779
1780 void GuiView::setBusy(bool busy)
1781 {
1782         bool const busy_before = busy_ > 0;
1783         busy ? ++busy_ : --busy_;
1784         if ((busy_ > 0) == busy_before)
1785                 // busy state didn't change
1786                 return;
1787
1788         if (busy) {
1789                 QApplication::setOverrideCursor(Qt::WaitCursor);
1790                 return;
1791         }
1792         QApplication::restoreOverrideCursor();
1793         updateLayoutList();
1794 }
1795
1796
1797 void GuiView::resetCommandExecute()
1798 {
1799         command_execute_ = false;
1800         updateToolbars();
1801 }
1802
1803
1804 double GuiView::pixelRatio() const
1805 {
1806         return qt_scale_factor * devicePixelRatio();
1807 }
1808
1809
1810 GuiWorkArea * GuiView::workArea(int index)
1811 {
1812         if (TabWorkArea * twa = d.currentTabWorkArea())
1813                 if (index < twa->count())
1814                         return twa->workArea(index);
1815         return nullptr;
1816 }
1817
1818
1819 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1820 {
1821         if (currentWorkArea()
1822                 && &currentWorkArea()->bufferView().buffer() == &buffer)
1823                 return currentWorkArea();
1824         if (TabWorkArea * twa = d.currentTabWorkArea())
1825                 return twa->workArea(buffer);
1826         return nullptr;
1827 }
1828
1829
1830 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1831 {
1832         // Automatically create a TabWorkArea if there are none yet.
1833         TabWorkArea * tab_widget = d.splitter_->count()
1834                 ? d.currentTabWorkArea() : addTabWorkArea();
1835         return tab_widget->addWorkArea(buffer, *this);
1836 }
1837
1838
1839 TabWorkArea * GuiView::addTabWorkArea()
1840 {
1841         TabWorkArea * twa = new TabWorkArea;
1842         QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1843                 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1844         QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1845                          this, SLOT(on_lastWorkAreaRemoved()));
1846
1847         d.splitter_->addWidget(twa);
1848         d.stack_widget_->setCurrentWidget(d.splitter_);
1849         return twa;
1850 }
1851
1852
1853 GuiWorkArea const * GuiView::currentWorkArea() const
1854 {
1855         return d.current_work_area_;
1856 }
1857
1858
1859 GuiWorkArea * GuiView::currentWorkArea()
1860 {
1861         return d.current_work_area_;
1862 }
1863
1864
1865 GuiWorkArea const * GuiView::currentMainWorkArea() const
1866 {
1867         if (!d.currentTabWorkArea())
1868                 return nullptr;
1869         return d.currentTabWorkArea()->currentWorkArea();
1870 }
1871
1872
1873 GuiWorkArea * GuiView::currentMainWorkArea()
1874 {
1875         if (!d.currentTabWorkArea())
1876                 return nullptr;
1877         return d.currentTabWorkArea()->currentWorkArea();
1878 }
1879
1880
1881 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1882 {
1883         LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1884         if (!wa) {
1885                 d.current_work_area_ = nullptr;
1886                 d.setBackground();
1887                 Q_EMIT bufferViewChanged();
1888                 return;
1889         }
1890
1891         // FIXME: I've no clue why this is here and why it accesses
1892         //  theGuiApp()->currentView, which might be 0 (bug 6464).
1893         //  See also 27525 (vfr).
1894         if (theGuiApp()->currentView() == this
1895                   && theGuiApp()->currentView()->currentWorkArea() == wa)
1896                 return;
1897
1898         if (currentBufferView())
1899                 cap::saveSelection(currentBufferView()->cursor());
1900
1901         theGuiApp()->setCurrentView(this);
1902         d.current_work_area_ = wa;
1903
1904         // We need to reset this now, because it will need to be
1905         // right if the tabWorkArea gets reset in the for loop. We
1906         // will change it back if we aren't in that case.
1907         GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1908         d.current_main_work_area_ = wa;
1909
1910         for (int i = 0; i != d.splitter_->count(); ++i) {
1911                 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1912                         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1913                                 << ", Current main wa: " << currentMainWorkArea());
1914                         return;
1915                 }
1916         }
1917
1918         d.current_main_work_area_ = old_cmwa;
1919
1920         LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1921         on_currentWorkAreaChanged(wa);
1922         BufferView & bv = wa->bufferView();
1923         bv.cursor().fixIfBroken();
1924         bv.updateMetrics();
1925         wa->setUpdatesEnabled(true);
1926         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1927 }
1928
1929
1930 void GuiView::removeWorkArea(GuiWorkArea * wa)
1931 {
1932         LASSERT(wa, return);
1933         if (wa == d.current_work_area_) {
1934                 disconnectBuffer();
1935                 disconnectBufferView();
1936                 d.current_work_area_ = nullptr;
1937                 d.current_main_work_area_ = nullptr;
1938         }
1939
1940         bool found_twa = false;
1941         for (int i = 0; i != d.splitter_->count(); ++i) {
1942                 TabWorkArea * twa = d.tabWorkArea(i);
1943                 if (twa->removeWorkArea(wa)) {
1944                         // Found in this tab group, and deleted the GuiWorkArea.
1945                         found_twa = true;
1946                         if (twa->count() != 0) {
1947                                 if (d.current_work_area_ == nullptr)
1948                                         // This means that we are closing the current GuiWorkArea, so
1949                                         // switch to the next GuiWorkArea in the found TabWorkArea.
1950                                         setCurrentWorkArea(twa->currentWorkArea());
1951                         } else {
1952                                 // No more WorkAreas in this tab group, so delete it.
1953                                 delete twa;
1954                         }
1955                         break;
1956                 }
1957         }
1958
1959         // It is not a tabbed work area (i.e., the search work area), so it
1960         // should be deleted by other means.
1961         LASSERT(found_twa, return);
1962
1963         if (d.current_work_area_ == nullptr) {
1964                 if (d.splitter_->count() != 0) {
1965                         TabWorkArea * twa = d.currentTabWorkArea();
1966                         setCurrentWorkArea(twa->currentWorkArea());
1967                 } else {
1968                         // No more work areas, switch to the background widget.
1969                         setCurrentWorkArea(nullptr);
1970                 }
1971         }
1972 }
1973
1974
1975 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1976 {
1977         for (int i = 0; i < d.splitter_->count(); ++i)
1978                 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1979                         return true;
1980
1981         FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1982         return fr->isVisible() && fr->hasWorkArea(wa);
1983 }
1984
1985
1986 LayoutBox * GuiView::getLayoutDialog() const
1987 {
1988         return d.layout_;
1989 }
1990
1991
1992 void GuiView::updateLayoutList()
1993 {
1994         if (d.layout_)
1995                 d.layout_->updateContents(false);
1996 }
1997
1998
1999 void GuiView::updateToolbars()
2000 {
2001         if (d.current_work_area_) {
2002                 int context = 0;
2003                 if (d.current_work_area_->bufferView().cursor().inMathed()
2004                         && !d.current_work_area_->bufferView().cursor().inRegexped())
2005                         context |= Toolbars::MATH;
2006                 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2007                         context |= Toolbars::TABLE;
2008                 if (currentBufferView()->buffer().areChangesPresent()
2009                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2010                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2011                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2012                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2013                         context |= Toolbars::REVIEW;
2014                 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2015                         context |= Toolbars::MATHMACROTEMPLATE;
2016                 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2017                         context |= Toolbars::IPA;
2018                 if (command_execute_)
2019                         context |= Toolbars::MINIBUFFER;
2020                 if (minibuffer_focus_) {
2021                         context |= Toolbars::MINIBUFFER_FOCUS;
2022                         minibuffer_focus_ = false;
2023                 }
2024
2025                 for (auto const & tb_p : d.toolbars_)
2026                         tb_p.second->update(context);
2027         } else
2028                 for (auto const & tb_p : d.toolbars_)
2029                         tb_p.second->update();
2030 }
2031
2032
2033 void GuiView::refillToolbars()
2034 {
2035         DynamicMenuButton::resetIconCache();
2036         for (auto const & tb_p : d.toolbars_)
2037                 tb_p.second->refill();
2038 }
2039
2040
2041 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2042 {
2043         LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2044         LASSERT(newBuffer, return);
2045
2046         GuiWorkArea * wa = workArea(*newBuffer);
2047         if (wa == nullptr) {
2048                 setBusy(true);
2049                 newBuffer->masterBuffer()->updateBuffer();
2050                 setBusy(false);
2051                 wa = addWorkArea(*newBuffer);
2052                 // scroll to the position when the BufferView was last closed
2053                 if (lyxrc.use_lastfilepos) {
2054                         LastFilePosSection::FilePos filepos =
2055                                 theSession().lastFilePos().load(newBuffer->fileName());
2056                         wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2057                 }
2058         } else {
2059                 //Disconnect the old buffer...there's no new one.
2060                 disconnectBuffer();
2061         }
2062         connectBuffer(*newBuffer);
2063         connectBufferView(wa->bufferView());
2064         if (switch_to)
2065                 setCurrentWorkArea(wa);
2066 }
2067
2068
2069 void GuiView::connectBuffer(Buffer & buf)
2070 {
2071         buf.setGuiDelegate(this);
2072 }
2073
2074
2075 void GuiView::disconnectBuffer()
2076 {
2077         if (d.current_work_area_)
2078                 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2079 }
2080
2081
2082 void GuiView::connectBufferView(BufferView & bv)
2083 {
2084         bv.setGuiDelegate(this);
2085 }
2086
2087
2088 void GuiView::disconnectBufferView()
2089 {
2090         if (d.current_work_area_)
2091                 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2092 }
2093
2094
2095 void GuiView::errors(string const & error_type, bool from_master)
2096 {
2097         BufferView const * const bv = currentBufferView();
2098         if (!bv)
2099                 return;
2100
2101         ErrorList const & el = from_master ?
2102                 bv->buffer().masterBuffer()->errorList(error_type) :
2103                 bv->buffer().errorList(error_type);
2104
2105         if (el.empty())
2106                 return;
2107
2108         string err = error_type;
2109         if (from_master)
2110                 err = "from_master|" + error_type;
2111         showDialog("errorlist", err);
2112 }
2113
2114
2115 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2116 {
2117         d.toc_models_.updateItem(toqstr(type), dit);
2118 }
2119
2120
2121 void GuiView::structureChanged()
2122 {
2123         // This is called from the Buffer, which has no way to ensure that cursors
2124         // in BufferView remain valid.
2125         if (documentBufferView())
2126                 documentBufferView()->cursor().sanitize();
2127         // FIXME: This is slightly expensive, though less than the tocBackend update
2128         // (#9880). This also resets the view in the Toc Widget (#6675).
2129         d.toc_models_.reset(documentBufferView());
2130         // Navigator needs more than a simple update in this case. It needs to be
2131         // rebuilt.
2132         updateDialog("toc", "");
2133 }
2134
2135
2136 void GuiView::updateDialog(string const & name, string const & sdata)
2137 {
2138         if (!isDialogVisible(name))
2139                 return;
2140
2141         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2142         if (it == d.dialogs_.end())
2143                 return;
2144
2145         Dialog * const dialog = it->second.get();
2146         if (dialog->isVisibleView())
2147                 dialog->initialiseParams(sdata);
2148 }
2149
2150
2151 BufferView * GuiView::documentBufferView()
2152 {
2153         return currentMainWorkArea()
2154                 ? &currentMainWorkArea()->bufferView()
2155                 : nullptr;
2156 }
2157
2158
2159 BufferView const * GuiView::documentBufferView() const
2160 {
2161         return currentMainWorkArea()
2162                 ? &currentMainWorkArea()->bufferView()
2163                 : nullptr;
2164 }
2165
2166
2167 BufferView * GuiView::currentBufferView()
2168 {
2169         return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2170 }
2171
2172
2173 BufferView const * GuiView::currentBufferView() const
2174 {
2175         return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2176 }
2177
2178
2179 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2180         Buffer const * orig, Buffer * clone)
2181 {
2182         bool const success = clone->autoSave();
2183         delete clone;
2184         busyBuffers.remove(orig);
2185         return success
2186                 ? _("Automatic save done.")
2187                 : _("Automatic save failed!");
2188 }
2189
2190
2191 void GuiView::autoSave()
2192 {
2193         LYXERR(Debug::INFO, "Running autoSave()");
2194
2195         Buffer * buffer = documentBufferView()
2196                 ? &documentBufferView()->buffer() : nullptr;
2197         if (!buffer) {
2198                 resetAutosaveTimers();
2199                 return;
2200         }
2201
2202         GuiViewPrivate::busyBuffers.insert(buffer);
2203         QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2204                 buffer, buffer->cloneBufferOnly());
2205         d.autosave_watcher_.setFuture(f);
2206         resetAutosaveTimers();
2207 }
2208
2209
2210 void GuiView::resetAutosaveTimers()
2211 {
2212         if (lyxrc.autosave)
2213                 d.autosave_timeout_.restart();
2214 }
2215
2216
2217 namespace {
2218
2219 double zoomRatio(FuncRequest const & cmd, double const zr)
2220 {
2221         if (cmd.argument().empty()) {
2222                 if (cmd.action() == LFUN_BUFFER_ZOOM)
2223                         return 1.0;
2224                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2225                         return zr + 0.1;
2226                 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2227                         return zr - 0.1;
2228         } else {
2229                 if (cmd.action() == LFUN_BUFFER_ZOOM)
2230                         return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2231                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2232                         return zr + convert<int>(cmd.argument()) / 100.0;
2233                 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2234                         return zr - convert<int>(cmd.argument()) / 100.0;
2235         }
2236 }
2237
2238 }
2239
2240
2241 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2242 {
2243         bool enable = true;
2244         Buffer * buf = currentBufferView()
2245                 ? &currentBufferView()->buffer() : nullptr;
2246         Buffer * doc_buffer = documentBufferView()
2247                 ? &(documentBufferView()->buffer()) : nullptr;
2248
2249 #ifdef Q_OS_MAC
2250         /* In LyX/Mac, when a dialog is open, the menus of the
2251            application can still be accessed without giving focus to
2252            the main window. In this case, we want to disable the menu
2253            entries that are buffer-related.
2254            This code must not be used on Linux and Windows, since it
2255            would disable buffer-related entries when hovering over the
2256            menu (see bug #9574).
2257          */
2258         if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2259                 buf = 0;
2260                 doc_buffer = 0;
2261         }
2262 #endif
2263
2264         // Check whether we need a buffer
2265         if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2266                 // no, exit directly
2267                 flag.message(from_utf8(N_("Command not allowed with"
2268                                         "out any document open")));
2269                 flag.setEnabled(false);
2270                 return true;
2271         }
2272
2273         if (cmd.origin() == FuncRequest::TOC) {
2274                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2275                 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2276                         flag.setEnabled(false);
2277                 return true;
2278         }
2279
2280         switch(cmd.action()) {
2281         case LFUN_BUFFER_IMPORT:
2282                 break;
2283
2284         case LFUN_MASTER_BUFFER_EXPORT:
2285                 enable = doc_buffer
2286                         && (doc_buffer->parent() != nullptr
2287                             || doc_buffer->hasChildren())
2288                         && !d.processing_thread_watcher_.isRunning()
2289                         // this launches a dialog, which would be in the wrong Buffer
2290                         && !(::lyx::operator==(cmd.argument(), "custom"));
2291                 break;
2292
2293         case LFUN_MASTER_BUFFER_UPDATE:
2294         case LFUN_MASTER_BUFFER_VIEW:
2295                 enable = doc_buffer
2296                         && (doc_buffer->parent() != nullptr
2297                             || doc_buffer->hasChildren())
2298                         && !d.processing_thread_watcher_.isRunning();
2299                 break;
2300
2301         case LFUN_BUFFER_UPDATE:
2302         case LFUN_BUFFER_VIEW: {
2303                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2304                         enable = false;
2305                         break;
2306                 }
2307                 string format = to_utf8(cmd.argument());
2308                 if (cmd.argument().empty())
2309                         format = doc_buffer->params().getDefaultOutputFormat();
2310                 enable = doc_buffer->params().isExportable(format, true);
2311                 break;
2312         }
2313
2314         case LFUN_BUFFER_RELOAD:
2315                 enable = doc_buffer && !doc_buffer->isUnnamed()
2316                         && doc_buffer->fileName().exists()
2317                         && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2318                 break;
2319
2320         case LFUN_BUFFER_RESET_EXPORT:
2321                 enable = doc_buffer != nullptr;
2322                 break;
2323
2324         case LFUN_BUFFER_CHILD_OPEN:
2325                 enable = doc_buffer != nullptr;
2326                 break;
2327
2328         case LFUN_MASTER_BUFFER_FORALL: {
2329                 if (doc_buffer == nullptr) {
2330                         flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2331                         enable = false;
2332                         break;
2333                 }
2334                 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2335                 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2336                         flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2337                         enable = false;
2338                         break;
2339                 }
2340                 enable = false;
2341                 for (Buffer * buf : doc_buffer->allRelatives()) {
2342                         GuiWorkArea * wa = workArea(*buf);
2343                         if (!wa)
2344                                 continue;
2345                         if (wa->bufferView().getStatus(cmdToPass, flag)) {
2346                                 enable = flag.enabled();
2347                                 break;
2348                         }
2349                 }
2350                 break;
2351         }
2352
2353         case LFUN_BUFFER_WRITE:
2354                 enable = doc_buffer && (doc_buffer->isUnnamed()
2355                                         || (!doc_buffer->isClean()
2356                                             || cmd.argument() == "force"));
2357                 break;
2358
2359         //FIXME: This LFUN should be moved to GuiApplication.
2360         case LFUN_BUFFER_WRITE_ALL: {
2361                 // We enable the command only if there are some modified buffers
2362                 Buffer * first = theBufferList().first();
2363                 enable = false;
2364                 if (!first)
2365                         break;
2366                 Buffer * b = first;
2367                 // We cannot use a for loop as the buffer list is a cycle.
2368                 do {
2369                         if (!b->isClean()) {
2370                                 enable = true;
2371                                 break;
2372                         }
2373                         b = theBufferList().next(b);
2374                 } while (b != first);
2375                 break;
2376         }
2377
2378         case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2379                 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2380                 break;
2381
2382         case LFUN_BUFFER_EXPORT: {
2383                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2384                         enable = false;
2385                         break;
2386                 }
2387                 return doc_buffer->getStatus(cmd, flag);
2388         }
2389
2390         case LFUN_BUFFER_EXPORT_AS:
2391                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2392                         enable = false;
2393                         break;
2394                 }
2395                 // fall through
2396         case LFUN_BUFFER_WRITE_AS:
2397         case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2398                 enable = doc_buffer != nullptr;
2399                 break;
2400
2401         case LFUN_EXPORT_CANCEL:
2402                 enable = d.processing_thread_watcher_.isRunning();
2403                 break;
2404
2405         case LFUN_BUFFER_CLOSE:
2406         case LFUN_VIEW_CLOSE:
2407                 enable = doc_buffer != nullptr;
2408                 break;
2409
2410         case LFUN_BUFFER_CLOSE_ALL:
2411                 enable = theBufferList().last() != theBufferList().first();
2412                 break;
2413
2414         case LFUN_BUFFER_CHKTEX: {
2415                 // hide if we have no checktex command
2416                 if (lyxrc.chktex_command.empty()) {
2417                         flag.setUnknown(true);
2418                         enable = false;
2419                         break;
2420                 }
2421                 if (!doc_buffer || !doc_buffer->params().isLatex()
2422                     || d.processing_thread_watcher_.isRunning()) {
2423                         // grey out, don't hide
2424                         enable = false;
2425                         break;
2426                 }
2427                 enable = true;
2428                 break;
2429         }
2430
2431         case LFUN_VIEW_SPLIT:
2432                 if (cmd.getArg(0) == "vertical")
2433                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2434                                          d.splitter_->orientation() == Qt::Vertical);
2435                 else
2436                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2437                                          d.splitter_->orientation() == Qt::Horizontal);
2438                 break;
2439
2440         case LFUN_TAB_GROUP_CLOSE:
2441                 enable = d.tabWorkAreaCount() > 1;
2442                 break;
2443
2444         case LFUN_DEVEL_MODE_TOGGLE:
2445                 flag.setOnOff(devel_mode_);
2446                 break;
2447
2448         case LFUN_TOOLBAR_SET: {
2449                 string const name = cmd.getArg(0);
2450                 string const state = cmd.getArg(1);
2451                 if (name.empty() || state.empty()) {
2452                         enable = false;
2453                         docstring const msg =
2454                                 _("Function toolbar-set requires two arguments!");
2455                         flag.message(msg);
2456                         break;
2457                 }
2458                 if (state != "on" && state != "off" && state != "auto") {
2459                         enable = false;
2460                         docstring const msg =
2461                                 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2462                                         from_utf8(state));
2463                         flag.message(msg);
2464                         break;
2465                 }
2466                 if (GuiToolbar * t = toolbar(name)) {
2467                         bool const autovis = t->visibility() & Toolbars::AUTO;
2468                         if (state == "on")
2469                                 flag.setOnOff(t->isVisible() && !autovis);
2470                         else if (state == "off")
2471                                 flag.setOnOff(!t->isVisible() && !autovis);
2472                         else if (state == "auto")
2473                                 flag.setOnOff(autovis);
2474                 } else {
2475                         enable = false;
2476                         docstring const msg =
2477                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2478                         flag.message(msg);
2479                 }
2480                 break;
2481         }
2482
2483         case LFUN_TOOLBAR_TOGGLE: {
2484                 string const name = cmd.getArg(0);
2485                 if (GuiToolbar * t = toolbar(name))
2486                         flag.setOnOff(t->isVisible());
2487                 else {
2488                         enable = false;
2489                         docstring const msg =
2490                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2491                         flag.message(msg);
2492                 }
2493                 break;
2494         }
2495
2496         case LFUN_TOOLBAR_MOVABLE: {
2497                 string const name = cmd.getArg(0);
2498                 // use negation since locked == !movable
2499                 if (name == "*")
2500                         // toolbar name * locks all toolbars
2501                         flag.setOnOff(!toolbarsMovable_);
2502                 else if (GuiToolbar * t = toolbar(name))
2503                         flag.setOnOff(!(t->isMovable()));
2504                 else {
2505                         enable = false;
2506                         docstring const msg =
2507                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2508                         flag.message(msg);
2509                 }
2510                 break;
2511         }
2512
2513         case LFUN_ICON_SIZE:
2514                 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2515                 break;
2516
2517         case LFUN_DROP_LAYOUTS_CHOICE:
2518                 enable = buf != nullptr;
2519                 break;
2520
2521         case LFUN_UI_TOGGLE:
2522                 if (cmd.argument() == "zoomlevel") {
2523                         flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2524                 } else if (cmd.argument() == "zoomslider") {
2525                         flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2526                 } else if (cmd.argument() == "statistics-w") {
2527                         flag.setOnOff(word_count_enabled_);
2528                 } else if (cmd.argument() == "statistics-cb") {
2529                         flag.setOnOff(char_count_enabled_);
2530                 } else if (cmd.argument() == "statistics-c") {
2531                         flag.setOnOff(char_nb_count_enabled_);
2532                 } else
2533                         flag.setOnOff(isFullScreen());
2534                 break;
2535
2536         case LFUN_DIALOG_DISCONNECT_INSET:
2537                 break;
2538
2539         case LFUN_DIALOG_HIDE:
2540                 // FIXME: should we check if the dialog is shown?
2541                 break;
2542
2543         case LFUN_DIALOG_TOGGLE:
2544                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2545                 // to set "enable"
2546                 // fall through
2547         case LFUN_DIALOG_SHOW: {
2548                 string const name = cmd.getArg(0);
2549                 if (!doc_buffer)
2550                         enable = name == "aboutlyx"
2551                                 || name == "file" //FIXME: should be removed.
2552                                 || name == "lyxfiles"
2553                                 || name == "prefs"
2554                                 || name == "texinfo"
2555                                 || name == "progress"
2556                                 || name == "compare";
2557                 else if (name == "character" || name == "symbols"
2558                         || name == "mathdelimiter" || name == "mathmatrix") {
2559                         if (!buf || buf->isReadonly())
2560                                 enable = false;
2561                         else {
2562                                 Cursor const & cur = currentBufferView()->cursor();
2563                                 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2564                         }
2565                 }
2566                 else if (name == "latexlog")
2567                         enable = FileName(doc_buffer->logName()).isReadableFile();
2568                 else if (name == "spellchecker")
2569                         enable = theSpellChecker()
2570                                 && !doc_buffer->text().empty();
2571                 else if (name == "vclog")
2572                         enable = doc_buffer->lyxvc().inUse();
2573                 break;
2574         }
2575
2576         case LFUN_DIALOG_UPDATE: {
2577                 string const name = cmd.getArg(0);
2578                 if (!buf)
2579                         enable = name == "prefs";
2580                 break;
2581         }
2582
2583         case LFUN_COMMAND_EXECUTE:
2584         case LFUN_MESSAGE:
2585         case LFUN_MENU_OPEN:
2586                 // Nothing to check.
2587                 break;
2588
2589         case LFUN_COMPLETION_INLINE:
2590                 if (!d.current_work_area_
2591                         || !d.current_work_area_->completer().inlinePossible(
2592                         currentBufferView()->cursor()))
2593                         enable = false;
2594                 break;
2595
2596         case LFUN_COMPLETION_POPUP:
2597                 if (!d.current_work_area_
2598                         || !d.current_work_area_->completer().popupPossible(
2599                         currentBufferView()->cursor()))
2600                         enable = false;
2601                 break;
2602
2603         case LFUN_COMPLETE:
2604                 if (!d.current_work_area_
2605                         || !d.current_work_area_->completer().inlinePossible(
2606                         currentBufferView()->cursor()))
2607                         enable = false;
2608                 break;
2609
2610         case LFUN_COMPLETION_ACCEPT:
2611                 if (!d.current_work_area_
2612                         || (!d.current_work_area_->completer().popupVisible()
2613                         && !d.current_work_area_->completer().inlineVisible()
2614                         && !d.current_work_area_->completer().completionAvailable()))
2615                         enable = false;
2616                 break;
2617
2618         case LFUN_COMPLETION_CANCEL:
2619                 if (!d.current_work_area_
2620                         || (!d.current_work_area_->completer().popupVisible()
2621                         && !d.current_work_area_->completer().inlineVisible()))
2622                         enable = false;
2623                 break;
2624
2625         case LFUN_BUFFER_ZOOM_OUT:
2626         case LFUN_BUFFER_ZOOM_IN:
2627         case LFUN_BUFFER_ZOOM: {
2628                 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2629                 if (zoom < zoom_min_) {
2630                         docstring const msg =
2631                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2632                         flag.message(msg);
2633                         enable = false;
2634                 } else if (zoom > zoom_max_) {
2635                         docstring const msg =
2636                                 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2637                         flag.message(msg);
2638                         enable = false;
2639                 } else
2640                         enable = doc_buffer;
2641                 break;
2642         }
2643
2644
2645         case LFUN_BUFFER_MOVE_NEXT:
2646         case LFUN_BUFFER_MOVE_PREVIOUS:
2647                 // we do not cycle when moving
2648         case LFUN_BUFFER_NEXT:
2649         case LFUN_BUFFER_PREVIOUS:
2650                 // because we cycle, it doesn't matter whether on first or last
2651                 enable = (d.currentTabWorkArea()->count() > 1);
2652                 break;
2653         case LFUN_BUFFER_SWITCH:
2654                 // toggle on the current buffer, but do not toggle off
2655                 // the other ones (is that a good idea?)
2656                 if (doc_buffer
2657                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2658                         flag.setOnOff(true);
2659                 break;
2660
2661         case LFUN_VC_REGISTER:
2662                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2663                 break;
2664         case LFUN_VC_RENAME:
2665                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2666                 break;
2667         case LFUN_VC_COPY:
2668                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2669                 break;
2670         case LFUN_VC_CHECK_IN:
2671                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2672                 break;
2673         case LFUN_VC_CHECK_OUT:
2674                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2675                 break;
2676         case LFUN_VC_LOCKING_TOGGLE:
2677                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2678                         && doc_buffer->lyxvc().lockingToggleEnabled();
2679                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2680                 break;
2681         case LFUN_VC_REVERT:
2682                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2683                         && !doc_buffer->hasReadonlyFlag();
2684                 break;
2685         case LFUN_VC_UNDO_LAST:
2686                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2687                 break;
2688         case LFUN_VC_REPO_UPDATE:
2689                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2690                 break;
2691         case LFUN_VC_COMMAND: {
2692                 if (cmd.argument().empty())
2693                         enable = false;
2694                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2695                         enable = false;
2696                 break;
2697         }
2698         case LFUN_VC_COMPARE:
2699                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2700                 break;
2701
2702         case LFUN_SERVER_GOTO_FILE_ROW:
2703         case LFUN_LYX_ACTIVATE:
2704         case LFUN_WINDOW_RAISE:
2705                 break;
2706         case LFUN_FORWARD_SEARCH:
2707                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2708                         doc_buffer && doc_buffer->isSyncTeXenabled();
2709                 break;
2710
2711         case LFUN_FILE_INSERT_PLAINTEXT:
2712         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2713                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2714                 break;
2715
2716         case LFUN_SPELLING_CONTINUOUSLY:
2717                 flag.setOnOff(lyxrc.spellcheck_continuously);
2718                 break;
2719
2720         case LFUN_CITATION_OPEN:
2721                 enable = true;
2722                 break;
2723
2724         default:
2725                 return false;
2726         }
2727
2728         if (!enable)
2729                 flag.setEnabled(false);
2730
2731         return true;
2732 }
2733
2734
2735 static FileName selectTemplateFile()
2736 {
2737         FileDialog dlg(qt_("Select template file"));
2738         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2739         dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2740
2741         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2742                                  QStringList(qt_("LyX Documents (*.lyx)")));
2743
2744         if (result.first == FileDialog::Later)
2745                 return FileName();
2746         if (result.second.isEmpty())
2747                 return FileName();
2748         return FileName(fromqstr(result.second));
2749 }
2750
2751
2752 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2753 {
2754         setBusy(true);
2755
2756         Buffer * newBuffer = nullptr;
2757         try {
2758                 newBuffer = checkAndLoadLyXFile(filename);
2759         } catch (ExceptionMessage const &) {
2760                 setBusy(false);
2761                 throw;
2762         }
2763         setBusy(false);
2764
2765         if (!newBuffer) {
2766                 message(_("Document not loaded."));
2767                 return nullptr;
2768         }
2769
2770         setBuffer(newBuffer);
2771         newBuffer->errors("Parse");
2772
2773         if (tolastfiles) {
2774                 theSession().lastFiles().add(filename);
2775                 theSession().writeFile();
2776   }
2777
2778         return newBuffer;
2779 }
2780
2781
2782 void GuiView::openDocument(string const & fname)
2783 {
2784         string initpath = lyxrc.document_path;
2785
2786         if (documentBufferView()) {
2787                 string const trypath = documentBufferView()->buffer().filePath();
2788                 // If directory is writeable, use this as default.
2789                 if (FileName(trypath).isDirWritable())
2790                         initpath = trypath;
2791         }
2792
2793         string filename;
2794
2795         if (fname.empty()) {
2796                 FileDialog dlg(qt_("Select document to open"));
2797                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2798                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2799
2800                 QStringList const filter({
2801                                 qt_("LyX Documents (*.lyx)"),
2802                                 qt_("LyX Document Backups (*.lyx~)"),
2803                                 qt_("All Files (*.*)")
2804                 });
2805                 FileDialog::Result result =
2806                         dlg.open(toqstr(initpath), filter);
2807
2808                 if (result.first == FileDialog::Later)
2809                         return;
2810
2811                 filename = fromqstr(result.second);
2812
2813                 // check selected filename
2814                 if (filename.empty()) {
2815                         message(_("Canceled."));
2816                         return;
2817                 }
2818         } else
2819                 filename = fname;
2820
2821         // get absolute path of file and add ".lyx" to the filename if
2822         // necessary.
2823         FileName const fullname =
2824                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2825         if (!fullname.empty())
2826                 filename = fullname.absFileName();
2827
2828         if (!fullname.onlyPath().isDirectory()) {
2829                 Alert::warning(_("Invalid filename"),
2830                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2831                                 from_utf8(fullname.absFileName())));
2832                 return;
2833         }
2834
2835         // if the file doesn't exist and isn't already open (bug 6645),
2836         // let the user create one
2837         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2838             !LyXVC::file_not_found_hook(fullname)) {
2839                 // the user specifically chose this name. Believe him.
2840                 Buffer * const b = newFile(filename, string(), true);
2841                 if (b)
2842                         setBuffer(b);
2843                 return;
2844         }
2845
2846         docstring const disp_fn = makeDisplayPath(filename);
2847         message(bformat(_("Opening document %1$s..."), disp_fn));
2848
2849         docstring str2;
2850         Buffer * buf = loadDocument(fullname);
2851         if (buf) {
2852                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2853                 if (buf->lyxvc().inUse())
2854                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2855                                 " " + _("Version control detected.");
2856         } else {
2857                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2858         }
2859         message(str2);
2860 }
2861
2862 // FIXME: clean that
2863 static bool import(GuiView * lv, FileName const & filename,
2864         string const & format, ErrorList & errorList)
2865 {
2866         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2867
2868         string loader_format;
2869         vector<string> loaders = theConverters().loaders();
2870         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2871                 for (string const & loader : loaders) {
2872                         if (!theConverters().isReachable(format, loader))
2873                                 continue;
2874
2875                         string const tofile =
2876                                 support::changeExtension(filename.absFileName(),
2877                                 theFormats().extension(loader));
2878                         if (theConverters().convert(nullptr, filename, FileName(tofile),
2879                                 filename, format, loader, errorList) != Converters::SUCCESS)
2880                                 return false;
2881                         loader_format = loader;
2882                         break;
2883                 }
2884                 if (loader_format.empty()) {
2885                         frontend::Alert::error(_("Couldn't import file"),
2886                                          bformat(_("No information for importing the format %1$s."),
2887                                          translateIfPossible(theFormats().prettyName(format))));
2888                         return false;
2889                 }
2890         } else
2891                 loader_format = format;
2892
2893         if (loader_format == "lyx") {
2894                 Buffer * buf = lv->loadDocument(lyxfile);
2895                 if (!buf)
2896                         return false;
2897         } else {
2898                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2899                 if (!b)
2900                         return false;
2901                 lv->setBuffer(b);
2902                 bool as_paragraphs = loader_format == "textparagraph";
2903                 string filename2 = (loader_format == format) ? filename.absFileName()
2904                         : support::changeExtension(filename.absFileName(),
2905                                           theFormats().extension(loader_format));
2906                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2907                         as_paragraphs);
2908                 guiApp->setCurrentView(lv);
2909                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2910         }
2911
2912         return true;
2913 }
2914
2915
2916 void GuiView::importDocument(string const & argument)
2917 {
2918         string format;
2919         string filename = split(argument, format, ' ');
2920
2921         LYXERR(Debug::INFO, format << " file: " << filename);
2922
2923         // need user interaction
2924         if (filename.empty()) {
2925                 string initpath = lyxrc.document_path;
2926                 if (documentBufferView()) {
2927                         string const trypath = documentBufferView()->buffer().filePath();
2928                         // If directory is writeable, use this as default.
2929                         if (FileName(trypath).isDirWritable())
2930                                 initpath = trypath;
2931                 }
2932
2933                 docstring const text = bformat(_("Select %1$s file to import"),
2934                         translateIfPossible(theFormats().prettyName(format)));
2935
2936                 FileDialog dlg(toqstr(text));
2937                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2938                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2939
2940                 docstring filter = translateIfPossible(theFormats().prettyName(format));
2941                 filter += " (*.{";
2942                 // FIXME UNICODE
2943                 filter += from_utf8(theFormats().extensions(format));
2944                 filter += "})";
2945
2946                 FileDialog::Result result =
2947                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2948
2949                 if (result.first == FileDialog::Later)
2950                         return;
2951
2952                 filename = fromqstr(result.second);
2953
2954                 // check selected filename
2955                 if (filename.empty())
2956                         message(_("Canceled."));
2957         }
2958
2959         if (filename.empty())
2960                 return;
2961
2962         // get absolute path of file
2963         FileName const fullname(support::makeAbsPath(filename));
2964
2965         // Can happen if the user entered a path into the dialog
2966         // (see bug #7437)
2967         if (fullname.onlyFileName().empty()) {
2968                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2969                                           "Aborting import."),
2970                                         from_utf8(fullname.absFileName()));
2971                 frontend::Alert::error(_("File name error"), msg);
2972                 message(_("Canceled."));
2973                 return;
2974         }
2975
2976
2977         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2978
2979         // Check if the document already is open
2980         Buffer * buf = theBufferList().getBuffer(lyxfile);
2981         if (buf) {
2982                 setBuffer(buf);
2983                 if (!closeBuffer()) {
2984                         message(_("Canceled."));
2985                         return;
2986                 }
2987         }
2988
2989         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2990
2991         // if the file exists already, and we didn't do
2992         // -i lyx thefile.lyx, warn
2993         if (lyxfile.exists() && fullname != lyxfile) {
2994
2995                 docstring text = bformat(_("The document %1$s already exists.\n\n"
2996                         "Do you want to overwrite that document?"), displaypath);
2997                 int const ret = Alert::prompt(_("Overwrite document?"),
2998                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
2999
3000                 if (ret == 1) {
3001                         message(_("Canceled."));
3002                         return;
3003                 }
3004         }
3005
3006         message(bformat(_("Importing %1$s..."), displaypath));
3007         ErrorList errorList;
3008         if (import(this, fullname, format, errorList))
3009                 message(_("imported."));
3010         else
3011                 message(_("file not imported!"));
3012
3013         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3014 }
3015
3016
3017 void GuiView::newDocument(string const & filename, string templatefile,
3018                           bool from_template)
3019 {
3020         FileName initpath(lyxrc.document_path);
3021         if (documentBufferView()) {
3022                 FileName const trypath(documentBufferView()->buffer().filePath());
3023                 // If directory is writeable, use this as default.
3024                 if (trypath.isDirWritable())
3025                         initpath = trypath;
3026         }
3027
3028         if (from_template) {
3029                 if (templatefile.empty())
3030                         templatefile =  selectTemplateFile().absFileName();
3031                 if (templatefile.empty())
3032                         return;
3033         }
3034
3035         Buffer * b;
3036         if (filename.empty())
3037                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3038         else
3039                 b = newFile(filename, templatefile, true);
3040
3041         if (b)
3042                 setBuffer(b);
3043
3044         // If no new document could be created, it is unsure
3045         // whether there is a valid BufferView.
3046         if (currentBufferView())
3047                 // Ensure the cursor is correctly positioned on screen.
3048                 currentBufferView()->showCursor();
3049 }
3050
3051
3052 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3053 {
3054         BufferView * bv = documentBufferView();
3055         if (!bv)
3056                 return false;
3057
3058         // FIXME UNICODE
3059         FileName filename(to_utf8(fname));
3060         if (filename.empty()) {
3061                 // Launch a file browser
3062                 // FIXME UNICODE
3063                 string initpath = lyxrc.document_path;
3064                 string const trypath = bv->buffer().filePath();
3065                 // If directory is writeable, use this as default.
3066                 if (FileName(trypath).isDirWritable())
3067                         initpath = trypath;
3068
3069                 // FIXME UNICODE
3070                 FileDialog dlg(qt_("Select LyX document to insert"));
3071                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3072                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3073
3074                 FileDialog::Result result = dlg.open(toqstr(initpath),
3075                                          QStringList(qt_("LyX Documents (*.lyx)")));
3076
3077                 if (result.first == FileDialog::Later)
3078                         return false;
3079
3080                 // FIXME UNICODE
3081                 filename.set(fromqstr(result.second));
3082
3083                 // check selected filename
3084                 if (filename.empty()) {
3085                         // emit message signal.
3086                         message(_("Canceled."));
3087                         return false;
3088                 }
3089         }
3090
3091         bv->insertLyXFile(filename, ignorelang);
3092         bv->buffer().errors("Parse");
3093         return true;
3094 }
3095
3096
3097 string const GuiView::getTemplatesPath(Buffer & b)
3098 {
3099         // We start off with the user's templates path
3100         string result = addPath(package().user_support().absFileName(), "templates");
3101         // Check for the document language
3102         string const langcode = b.params().language->code();
3103         string const shortcode = langcode.substr(0, 2);
3104         if (!langcode.empty() && shortcode != "en") {
3105                 string subpath = addPath(result, shortcode);
3106                 string subpath_long = addPath(result, langcode);
3107                 // If we have a subdirectory for the language already,
3108                 // navigate there
3109                 FileName sp = FileName(subpath);
3110                 if (sp.isDirectory())
3111                         result = subpath;
3112                 else if (FileName(subpath_long).isDirectory())
3113                         result = subpath_long;
3114                 else {
3115                         // Ask whether we should create such a subdirectory
3116                         docstring const text =
3117                                 bformat(_("It is suggested to save the template in a subdirectory\n"
3118                                           "appropriate to the document language (%1$s).\n"
3119                                           "This subdirectory does not exists yet.\n"
3120                                           "Do you want to create it?"),
3121                                         _(b.params().language->display()));
3122                         if (Alert::prompt(_("Create Language Directory?"),
3123                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3124                                 // If the user agreed, we try to create it and report if this failed.
3125                                 if (!sp.createDirectory(0777))
3126                                         Alert::error(_("Subdirectory creation failed!"),
3127                                                      _("Could not create subdirectory.\n"
3128                                                        "The template will be saved in the parent directory."));
3129                                 else
3130                                         result = subpath;
3131                         }
3132                 }
3133         }
3134         // Do we have a layout category?
3135         string const cat = b.params().baseClass() ?
3136                                 b.params().baseClass()->category()
3137                               : string();
3138         if (!cat.empty()) {
3139                 string subpath = addPath(result, cat);
3140                 // If we have a subdirectory for the category already,
3141                 // navigate there
3142                 FileName sp = FileName(subpath);
3143                 if (sp.isDirectory())
3144                         result = subpath;
3145                 else {
3146                         // Ask whether we should create such a subdirectory
3147                         docstring const text =
3148                                 bformat(_("It is suggested to save the template in a subdirectory\n"
3149                                           "appropriate to the layout category (%1$s).\n"
3150                                           "This subdirectory does not exists yet.\n"
3151                                           "Do you want to create it?"),
3152                                         _(cat));
3153                         if (Alert::prompt(_("Create Category Directory?"),
3154                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3155                                 // If the user agreed, we try to create it and report if this failed.
3156                                 if (!sp.createDirectory(0777))
3157                                         Alert::error(_("Subdirectory creation failed!"),
3158                                                      _("Could not create subdirectory.\n"
3159                                                        "The template will be saved in the parent directory."));
3160                                 else
3161                                         result = subpath;
3162                         }
3163                 }
3164         }
3165         return result;
3166 }
3167
3168
3169 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3170 {
3171         FileName fname = b.fileName();
3172         FileName const oldname = fname;
3173         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3174
3175         if (!newname.empty()) {
3176                 // FIXME UNICODE
3177                 if (as_template)
3178                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3179                 else
3180                         fname = support::makeAbsPath(to_utf8(newname),
3181                                                      oldname.onlyPath().absFileName());
3182         } else {
3183                 // Switch to this Buffer.
3184                 setBuffer(&b);
3185
3186                 // No argument? Ask user through dialog.
3187                 // FIXME UNICODE
3188                 QString const title = as_template ? qt_("Choose a filename to save template as")
3189                                                   : qt_("Choose a filename to save document as");
3190                 FileDialog dlg(title);
3191                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3192                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3193
3194                 fname.ensureExtension(".lyx");
3195
3196                 string const path = as_template ?
3197                                         getTemplatesPath(b)
3198                                       : fname.onlyPath().absFileName();
3199                 FileDialog::Result result =
3200                         dlg.save(toqstr(path),
3201                                    QStringList(qt_("LyX Documents (*.lyx)")),
3202                                          toqstr(fname.onlyFileName()));
3203
3204                 if (result.first == FileDialog::Later)
3205                         return false;
3206
3207                 fname.set(fromqstr(result.second));
3208
3209                 if (fname.empty())
3210                         return false;
3211
3212                 fname.ensureExtension(".lyx");
3213         }
3214
3215         // fname is now the new Buffer location.
3216
3217         // if there is already a Buffer open with this name, we do not want
3218         // to have another one. (the second test makes sure we're not just
3219         // trying to overwrite ourselves, which is fine.)
3220         if (theBufferList().exists(fname) && fname != oldname
3221                   && theBufferList().getBuffer(fname) != &b) {
3222                 docstring const text =
3223                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
3224                             "Please close it before attempting to overwrite it.\n"
3225                             "Do you want to choose a new filename?"),
3226                                 from_utf8(fname.absFileName()));
3227                 int const ret = Alert::prompt(_("Chosen File Already Open"),
3228                         text, 0, 1, _("&Rename"), _("&Cancel"));
3229                 switch (ret) {
3230                 case 0: return renameBuffer(b, docstring(), kind);
3231                 case 1: return false;
3232                 }
3233                 //return false;
3234         }
3235
3236         bool const existsLocal = fname.exists();
3237         bool const existsInVC = LyXVC::fileInVC(fname);
3238         if (existsLocal || existsInVC) {
3239                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3240                 if (kind != LV_WRITE_AS && existsInVC) {
3241                         // renaming to a name that is already in VC
3242                         // would not work
3243                         docstring text = bformat(_("The document %1$s "
3244                                         "is already registered.\n\n"
3245                                         "Do you want to choose a new name?"),
3246                                 file);
3247                         docstring const title = (kind == LV_VC_RENAME) ?
3248                                 _("Rename document?") : _("Copy document?");
3249                         docstring const button = (kind == LV_VC_RENAME) ?
3250                                 _("&Rename") : _("&Copy");
3251                         int const ret = Alert::prompt(title, text, 0, 1,
3252                                 button, _("&Cancel"));
3253                         switch (ret) {
3254                         case 0: return renameBuffer(b, docstring(), kind);
3255                         case 1: return false;
3256                         }
3257                 }
3258
3259                 if (existsLocal) {
3260                         docstring text = bformat(_("The document %1$s "
3261                                         "already exists.\n\n"
3262                                         "Do you want to overwrite that document?"),
3263                                 file);
3264                         int const ret = Alert::prompt(_("Overwrite document?"),
3265                                         text, 0, 2, _("&Overwrite"),
3266                                         _("&Rename"), _("&Cancel"));
3267                         switch (ret) {
3268                         case 0: break;
3269                         case 1: return renameBuffer(b, docstring(), kind);
3270                         case 2: return false;
3271                         }
3272                 }
3273         }
3274
3275         switch (kind) {
3276         case LV_VC_RENAME: {
3277                 string msg = b.lyxvc().rename(fname);
3278                 if (msg.empty())
3279                         return false;
3280                 message(from_utf8(msg));
3281                 break;
3282         }
3283         case LV_VC_COPY: {
3284                 string msg = b.lyxvc().copy(fname);
3285                 if (msg.empty())
3286                         return false;
3287                 message(from_utf8(msg));
3288                 break;
3289         }
3290         case LV_WRITE_AS:
3291         case LV_WRITE_AS_TEMPLATE:
3292                 break;
3293         }
3294         // LyXVC created the file already in case of LV_VC_RENAME or
3295         // LV_VC_COPY, but call saveBuffer() nevertheless to get
3296         // relative paths of included stuff right if we moved e.g. from
3297         // /a/b.lyx to /a/c/b.lyx.
3298
3299         bool const saved = saveBuffer(b, fname);
3300         if (saved)
3301                 b.reload();
3302         return saved;
3303 }
3304
3305
3306 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3307 {
3308         FileName fname = b.fileName();
3309
3310         FileDialog dlg(qt_("Choose a filename to export the document as"));
3311         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3312
3313         QStringList types;
3314         QString const anyformat = qt_("Guess from extension (*.*)");
3315         types << anyformat;
3316
3317         vector<Format const *> export_formats;
3318         for (Format const & f : theFormats())
3319                 if (f.documentFormat())
3320                         export_formats.push_back(&f);
3321         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3322         map<QString, string> fmap;
3323         QString filter;
3324         string ext;
3325         for (Format const * f : export_formats) {
3326                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3327                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3328                                                      loc_prettyname,
3329                                                      from_ascii(f->extension())));
3330                 types << loc_filter;
3331                 fmap[loc_filter] = f->name();
3332                 if (from_ascii(f->name()) == iformat) {
3333                         filter = loc_filter;
3334                         ext = f->extension();
3335                 }
3336         }
3337         string ofname = fname.onlyFileName();
3338         if (!ext.empty())
3339                 ofname = support::changeExtension(ofname, ext);
3340         FileDialog::Result result =
3341                 dlg.save(toqstr(fname.onlyPath().absFileName()),
3342                          types,
3343                          toqstr(ofname),
3344                          &filter);
3345         if (result.first != FileDialog::Chosen)
3346                 return false;
3347
3348         string fmt_name;
3349         fname.set(fromqstr(result.second));
3350         if (filter == anyformat)
3351                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3352         else
3353                 fmt_name = fmap[filter];
3354         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3355                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3356
3357         if (fmt_name.empty() || fname.empty())
3358                 return false;
3359
3360         fname.ensureExtension(theFormats().extension(fmt_name));
3361
3362         // fname is now the new Buffer location.
3363         if (fname.exists()) {
3364                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3365                 docstring text = bformat(_("The document %1$s already "
3366                                            "exists.\n\nDo you want to "
3367                                            "overwrite that document?"),
3368                                          file);
3369                 int const ret = Alert::prompt(_("Overwrite document?"),
3370                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3371                 switch (ret) {
3372                 case 0: break;
3373                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3374                 case 2: return false;
3375                 }
3376         }
3377
3378         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3379         DispatchResult dr;
3380         dispatch(cmd, dr);
3381         return dr.dispatched();
3382 }
3383
3384
3385 bool GuiView::saveBuffer(Buffer & b)
3386 {
3387         return saveBuffer(b, FileName());
3388 }
3389
3390
3391 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3392 {
3393         if (workArea(b) && workArea(b)->inDialogMode())
3394                 return true;
3395
3396         if (fn.empty() && b.isUnnamed())
3397                 return renameBuffer(b, docstring());
3398
3399         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3400         if (success) {
3401                 theSession().lastFiles().add(b.fileName());
3402                 theSession().writeFile();
3403                 return true;
3404         }
3405
3406         // Switch to this Buffer.
3407         setBuffer(&b);
3408
3409         // FIXME: we don't tell the user *WHY* the save failed !!
3410         docstring const file = makeDisplayPath(b.absFileName(), 30);
3411         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3412                                    "Do you want to rename the document and "
3413                                    "try again?"), file);
3414         int const ret = Alert::prompt(_("Rename and save?"),
3415                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3416         switch (ret) {
3417         case 0:
3418                 if (!renameBuffer(b, docstring()))
3419                         return false;
3420                 break;
3421         case 1:
3422                 break;
3423         case 2:
3424                 return false;
3425         }
3426
3427         return saveBuffer(b, fn);
3428 }
3429
3430
3431 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3432 {
3433         return closeWorkArea(wa, false);
3434 }
3435
3436
3437 // We only want to close the buffer if it is not visible in other workareas
3438 // of the same view, nor in other views, and if this is not a child
3439 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3440 {
3441         Buffer & buf = wa->bufferView().buffer();
3442
3443         bool last_wa = d.countWorkAreasOf(buf) == 1
3444                 && !inOtherView(buf) && !buf.parent();
3445
3446         bool close_buffer = last_wa;
3447
3448         if (last_wa) {
3449                 if (lyxrc.close_buffer_with_last_view == "yes")
3450                         ; // Nothing to do
3451                 else if (lyxrc.close_buffer_with_last_view == "no")
3452                         close_buffer = false;
3453                 else {
3454                         docstring file;
3455                         if (buf.isUnnamed())
3456                                 file = from_utf8(buf.fileName().onlyFileName());
3457                         else
3458                                 file = buf.fileName().displayName(30);
3459                         docstring const text = bformat(
3460                                 _("Last view on document %1$s is being closed.\n"
3461                                   "Would you like to close or hide the document?\n"
3462                                   "\n"
3463                                   "Hidden documents can be displayed back through\n"
3464                                   "the menu: View->Hidden->...\n"
3465                                   "\n"
3466                                   "To remove this question, set your preference in:\n"
3467                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3468                                 ), file);
3469                         int ret = Alert::prompt(_("Close or hide document?"),
3470                                 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3471                         if (ret == 2)
3472                                 return false;
3473                         close_buffer = (ret == 0);
3474                 }
3475         }
3476
3477         return closeWorkArea(wa, close_buffer);
3478 }
3479
3480
3481 bool GuiView::closeBuffer()
3482 {
3483         GuiWorkArea * wa = currentMainWorkArea();
3484         // coverity complained about this
3485         // it seems unnecessary, but perhaps is worth the check
3486         LASSERT(wa, return false);
3487
3488         setCurrentWorkArea(wa);
3489         Buffer & buf = wa->bufferView().buffer();
3490         return closeWorkArea(wa, !buf.parent());
3491 }
3492
3493
3494 void GuiView::writeSession() const {
3495         GuiWorkArea const * active_wa = currentMainWorkArea();
3496         for (int i = 0; i < d.splitter_->count(); ++i) {
3497                 TabWorkArea * twa = d.tabWorkArea(i);
3498                 for (int j = 0; j < twa->count(); ++j) {
3499                         GuiWorkArea * wa = twa->workArea(j);
3500                         Buffer & buf = wa->bufferView().buffer();
3501                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3502                 }
3503         }
3504 }
3505
3506
3507 bool GuiView::closeBufferAll()
3508 {
3509
3510         for (auto & buf : theBufferList()) {
3511                 if (!saveBufferIfNeeded(*buf, false)) {
3512                         // Closing has been cancelled, so abort.
3513                         return false;
3514                 }
3515         }
3516
3517         // Close the workareas in all other views
3518         QList<int> const ids = guiApp->viewIds();
3519         for (int i = 0; i != ids.size(); ++i) {
3520                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3521                         return false;
3522         }
3523
3524         // Close our own workareas
3525         if (!closeWorkAreaAll())
3526                 return false;
3527
3528         return true;
3529 }
3530
3531
3532 bool GuiView::closeWorkAreaAll()
3533 {
3534         setCurrentWorkArea(currentMainWorkArea());
3535
3536         // We might be in a situation that there is still a tabWorkArea, but
3537         // there are no tabs anymore. This can happen when we get here after a
3538         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3539         // many TabWorkArea's have no documents anymore.
3540         int empty_twa = 0;
3541
3542         // We have to call count() each time, because it can happen that
3543         // more than one splitter will disappear in one iteration (bug 5998).
3544         while (d.splitter_->count() > empty_twa) {
3545                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3546
3547                 if (twa->count() == 0)
3548                         ++empty_twa;
3549                 else {
3550                         setCurrentWorkArea(twa->currentWorkArea());
3551                         if (!closeTabWorkArea(twa))
3552                                 return false;
3553                 }
3554         }
3555         return true;
3556 }
3557
3558
3559 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3560 {
3561         if (!wa)
3562                 return false;
3563
3564         Buffer & buf = wa->bufferView().buffer();
3565
3566         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3567                 Alert::warning(_("Close document"),
3568                         _("Document could not be closed because it is being processed by LyX."));
3569                 return false;
3570         }
3571
3572         if (close_buffer)
3573                 return closeBuffer(buf);
3574         else {
3575                 if (!inMultiTabs(wa))
3576                         if (!saveBufferIfNeeded(buf, true))
3577                                 return false;
3578                 removeWorkArea(wa);
3579                 return true;
3580         }
3581 }
3582
3583
3584 bool GuiView::closeBuffer(Buffer & buf)
3585 {
3586         bool success = true;
3587         for (Buffer * child_buf : buf.getChildren()) {
3588                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3589                         child_buf->setParent(nullptr);
3590                         continue;
3591                 }
3592
3593                 // FIXME: should we look in other tabworkareas?
3594                 // ANSWER: I don't think so. I've tested, and if the child is
3595                 // open in some other window, it closes without a problem.
3596                 GuiWorkArea * child_wa = workArea(*child_buf);
3597                 if (child_wa) {
3598                         if (closing_)
3599                                 // If we are in a close_event all children will be closed in some time,
3600                                 // so no need to do it here. This will ensure that the children end up
3601                                 // in the session file in the correct order. If we close the master
3602                                 // buffer, we can close or release the child buffers here too.
3603                                 continue;
3604                         success = closeWorkArea(child_wa, true);
3605                         if (!success)
3606                                 break;
3607                 } else {
3608                         // In this case the child buffer is open but hidden.
3609                         // Even in this case, children can be dirty (e.g.,
3610                         // after a label change in the master, see #11405).
3611                         // Therefore, check this
3612                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3613                                 // If we are in a close_event all children will be closed in some time,
3614                                 // so no need to do it here. This will ensure that the children end up
3615                                 // in the session file in the correct order. If we close the master
3616                                 // buffer, we can close or release the child buffers here too.
3617                                 continue;
3618                         }
3619                         // Save dirty buffers also if closing_!
3620                         if (saveBufferIfNeeded(*child_buf, false)) {
3621                                 child_buf->removeAutosaveFile();
3622                                 theBufferList().release(child_buf);
3623                         } else {
3624                                 // Saving of dirty children has been cancelled.
3625                                 // Cancel the whole process.
3626                                 success = false;
3627                                 break;
3628                         }
3629                 }
3630         }
3631         if (success) {
3632                 // goto bookmark to update bookmark pit.
3633                 // FIXME: we should update only the bookmarks related to this buffer!
3634                 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3635                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3636                 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3637                         guiApp->gotoBookmark(i, false, false);
3638
3639                 if (saveBufferIfNeeded(buf, false)) {
3640                         buf.removeAutosaveFile();
3641                         theBufferList().release(&buf);
3642                         return true;
3643                 }
3644         }
3645         // open all children again to avoid a crash because of dangling
3646         // pointers (bug 6603)
3647         buf.updateBuffer();
3648         return false;
3649 }
3650
3651
3652 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3653 {
3654         while (twa == d.currentTabWorkArea()) {
3655                 twa->setCurrentIndex(twa->count() - 1);
3656
3657                 GuiWorkArea * wa = twa->currentWorkArea();
3658                 Buffer & b = wa->bufferView().buffer();
3659
3660                 // We only want to close the buffer if the same buffer is not visible
3661                 // in another view, and if this is not a child and if we are closing
3662                 // a view (not a tabgroup).
3663                 bool const close_buffer =
3664                         !inOtherView(b) && !b.parent() && closing_;
3665
3666                 if (!closeWorkArea(wa, close_buffer))
3667                         return false;
3668         }
3669         return true;
3670 }
3671
3672
3673 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3674 {
3675         if (buf.isClean() || buf.paragraphs().empty())
3676                 return true;
3677
3678         // Switch to this Buffer.
3679         setBuffer(&buf);
3680
3681         docstring file;
3682         bool exists;
3683         // FIXME: Unicode?
3684         if (buf.isUnnamed()) {
3685                 file = from_utf8(buf.fileName().onlyFileName());
3686                 exists = false;
3687         } else {
3688                 FileName filename = buf.fileName();
3689                 filename.refresh();
3690                 file = filename.displayName(30);
3691                 exists = filename.exists();
3692         }
3693
3694         // Bring this window to top before asking questions.
3695         raise();
3696         activateWindow();
3697
3698         int ret;
3699         if (hiding && buf.isUnnamed()) {
3700                 docstring const text = bformat(_("The document %1$s has not been "
3701                                                  "saved yet.\n\nDo you want to save "
3702                                                  "the document?"), file);
3703                 ret = Alert::prompt(_("Save new document?"),
3704                         text, 0, 1, _("&Save"), _("&Cancel"));
3705                 if (ret == 1)
3706                         ++ret;
3707         } else {
3708                 docstring const text = exists ?
3709                         bformat(_("The document %1$s has unsaved changes."
3710                                   "\n\nDo you want to save the document or "
3711                                   "discard the changes?"), file) :
3712                         bformat(_("The document %1$s has not been saved yet."
3713                                   "\n\nDo you want to save the document or "
3714                                   "discard it entirely?"), file);
3715                 docstring const title = exists ?
3716                         _("Save changed document?") : _("Save document?");
3717                 ret = Alert::prompt(title, text, 0, 2,
3718                                     _("&Save"), _("&Discard"), _("&Cancel"));
3719         }
3720
3721         switch (ret) {
3722         case 0:
3723                 if (!saveBuffer(buf))
3724                         return false;
3725                 break;
3726         case 1:
3727                 // If we crash after this we could have no autosave file
3728                 // but I guess this is really improbable (Jug).
3729                 // Sometimes improbable things happen:
3730                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3731                 // buf.removeAutosaveFile();
3732                 if (hiding)
3733                         // revert all changes
3734                         reloadBuffer(buf);
3735                 buf.markClean();
3736                 break;
3737         case 2:
3738                 return false;
3739         }
3740         return true;
3741 }
3742
3743
3744 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3745 {
3746         Buffer & buf = wa->bufferView().buffer();
3747
3748         for (int i = 0; i != d.splitter_->count(); ++i) {
3749                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3750                 if (wa_ && wa_ != wa)
3751                         return true;
3752         }
3753         return inOtherView(buf);
3754 }
3755
3756
3757 bool GuiView::inOtherView(Buffer & buf)
3758 {
3759         QList<int> const ids = guiApp->viewIds();
3760
3761         for (int i = 0; i != ids.size(); ++i) {
3762                 if (id_ == ids[i])
3763                         continue;
3764
3765                 if (guiApp->view(ids[i]).workArea(buf))
3766                         return true;
3767         }
3768         return false;
3769 }
3770
3771
3772 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3773 {
3774         if (!documentBufferView())
3775                 return;
3776
3777         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3778                 Buffer * const curbuf = &documentBufferView()->buffer();
3779                 int nwa = twa->count();
3780                 for (int i = 0; i < nwa; ++i) {
3781                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3782                                 int next_index;
3783                                 if (np == NEXTBUFFER)
3784                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3785                                 else
3786                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3787                                 if (move)
3788                                         twa->moveTab(i, next_index);
3789                                 else
3790                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3791                                 break;
3792                         }
3793                 }
3794         }
3795 }
3796
3797
3798 /// make sure the document is saved
3799 static bool ensureBufferClean(Buffer * buffer)
3800 {
3801         LASSERT(buffer, return false);
3802         if (buffer->isClean() && !buffer->isUnnamed())
3803                 return true;
3804
3805         docstring const file = buffer->fileName().displayName(30);
3806         docstring title;
3807         docstring text;
3808         if (!buffer->isUnnamed()) {
3809                 text = bformat(_("The document %1$s has unsaved "
3810                                                  "changes.\n\nDo you want to save "
3811                                                  "the document?"), file);
3812                 title = _("Save changed document?");
3813
3814         } else {
3815                 text = bformat(_("The document %1$s has not been "
3816                                                  "saved yet.\n\nDo you want to save "
3817                                                  "the document?"), file);
3818                 title = _("Save new document?");
3819         }
3820         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3821
3822         if (ret == 0)
3823                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3824
3825         return buffer->isClean() && !buffer->isUnnamed();
3826 }
3827
3828
3829 bool GuiView::reloadBuffer(Buffer & buf)
3830 {
3831         currentBufferView()->cursor().reset();
3832         Buffer::ReadStatus status = buf.reload();
3833         return status == Buffer::ReadSuccess;
3834 }
3835
3836
3837 void GuiView::checkExternallyModifiedBuffers()
3838 {
3839         for (Buffer * buf : theBufferList()) {
3840                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3841                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3842                                         " Reload now? Any local changes will be lost."),
3843                                         from_utf8(buf->absFileName()));
3844                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3845                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3846                         if (!ret)
3847                                 reloadBuffer(*buf);
3848                 }
3849         }
3850 }
3851
3852
3853 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3854 {
3855         Buffer * buffer = documentBufferView()
3856                 ? &(documentBufferView()->buffer()) : nullptr;
3857
3858         switch (cmd.action()) {
3859         case LFUN_VC_REGISTER:
3860                 if (!buffer || !ensureBufferClean(buffer))
3861                         break;
3862                 if (!buffer->lyxvc().inUse()) {
3863                         if (buffer->lyxvc().registrer()) {
3864                                 reloadBuffer(*buffer);
3865                                 dr.clearMessageUpdate();
3866                         }
3867                 }
3868                 break;
3869
3870         case LFUN_VC_RENAME:
3871         case LFUN_VC_COPY: {
3872                 if (!buffer || !ensureBufferClean(buffer))
3873                         break;
3874                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3875                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3876                                 // Some changes are not yet committed.
3877                                 // We test here and not in getStatus(), since
3878                                 // this test is expensive.
3879                                 string log;
3880                                 LyXVC::CommandResult ret =
3881                                         buffer->lyxvc().checkIn(log);
3882                                 dr.setMessage(log);
3883                                 if (ret == LyXVC::ErrorCommand ||
3884                                     ret == LyXVC::VCSuccess)
3885                                         reloadBuffer(*buffer);
3886                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3887                                         frontend::Alert::error(
3888                                                 _("Revision control error."),
3889                                                 _("Document could not be checked in."));
3890                                         break;
3891                                 }
3892                         }
3893                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3894                                 LV_VC_RENAME : LV_VC_COPY;
3895                         renameBuffer(*buffer, cmd.argument(), kind);
3896                 }
3897                 break;
3898         }
3899
3900         case LFUN_VC_CHECK_IN:
3901                 if (!buffer || !ensureBufferClean(buffer))
3902                         break;
3903                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3904                         string log;
3905                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3906                         dr.setMessage(log);
3907                         // Only skip reloading if the checkin was cancelled or
3908                         // an error occurred before the real checkin VCS command
3909                         // was executed, since the VCS might have changed the
3910                         // file even if it could not checkin successfully.
3911                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3912                                 reloadBuffer(*buffer);
3913                 }
3914                 break;
3915
3916         case LFUN_VC_CHECK_OUT:
3917                 if (!buffer || !ensureBufferClean(buffer))
3918                         break;
3919                 if (buffer->lyxvc().inUse()) {
3920                         dr.setMessage(buffer->lyxvc().checkOut());
3921                         reloadBuffer(*buffer);
3922                 }
3923                 break;
3924
3925         case LFUN_VC_LOCKING_TOGGLE:
3926                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3927                         break;
3928                 if (buffer->lyxvc().inUse()) {
3929                         string res = buffer->lyxvc().lockingToggle();
3930                         if (res.empty()) {
3931                                 frontend::Alert::error(_("Revision control error."),
3932                                 _("Error when setting the locking property."));
3933                         } else {
3934                                 dr.setMessage(res);
3935                                 reloadBuffer(*buffer);
3936                         }
3937                 }
3938                 break;
3939
3940         case LFUN_VC_REVERT:
3941                 if (!buffer)
3942                         break;
3943                 if (buffer->lyxvc().revert()) {
3944                         reloadBuffer(*buffer);
3945                         dr.clearMessageUpdate();
3946                 }
3947                 break;
3948
3949         case LFUN_VC_UNDO_LAST:
3950                 if (!buffer)
3951                         break;
3952                 buffer->lyxvc().undoLast();
3953                 reloadBuffer(*buffer);
3954                 dr.clearMessageUpdate();
3955                 break;
3956
3957         case LFUN_VC_REPO_UPDATE:
3958                 if (!buffer)
3959                         break;
3960                 if (ensureBufferClean(buffer)) {
3961                         dr.setMessage(buffer->lyxvc().repoUpdate());
3962                         checkExternallyModifiedBuffers();
3963                 }
3964                 break;
3965
3966         case LFUN_VC_COMMAND: {
3967                 string flag = cmd.getArg(0);
3968                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3969                         break;
3970                 docstring message;
3971                 if (contains(flag, 'M')) {
3972                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3973                                 break;
3974                 }
3975                 string path = cmd.getArg(1);
3976                 if (contains(path, "$$p") && buffer)
3977                         path = subst(path, "$$p", buffer->filePath());
3978                 LYXERR(Debug::LYXVC, "Directory: " << path);
3979                 FileName pp(path);
3980                 if (!pp.isReadableDirectory()) {
3981                         lyxerr << _("Directory is not accessible.") << endl;
3982                         break;
3983                 }
3984                 support::PathChanger p(pp);
3985
3986                 string command = cmd.getArg(2);
3987                 if (command.empty())
3988                         break;
3989                 if (buffer) {
3990                         command = subst(command, "$$i", buffer->absFileName());
3991                         command = subst(command, "$$p", buffer->filePath());
3992                 }
3993                 command = subst(command, "$$m", to_utf8(message));
3994                 LYXERR(Debug::LYXVC, "Command: " << command);
3995                 Systemcall one;
3996                 one.startscript(Systemcall::Wait, command);
3997
3998                 if (!buffer)
3999                         break;
4000                 if (contains(flag, 'I'))
4001                         buffer->markDirty();
4002                 if (contains(flag, 'R'))
4003                         reloadBuffer(*buffer);
4004
4005                 break;
4006                 }
4007
4008         case LFUN_VC_COMPARE: {
4009                 if (cmd.argument().empty()) {
4010                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4011                         break;
4012                 }
4013                 if (!buffer)
4014                         break;
4015
4016                 string rev1 = cmd.getArg(0);
4017                 string f1, f2;
4018
4019                 // f1
4020                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4021                         break;
4022
4023                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4024                         f2 = buffer->absFileName();
4025                 } else {
4026                         string rev2 = cmd.getArg(1);
4027                         if (rev2.empty())
4028                                 break;
4029                         // f2
4030                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4031                                 break;
4032                 }
4033
4034                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4035                                         f1 << "\n"  << f2 << "\n" );
4036                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4037                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4038                 break;
4039         }
4040
4041         default:
4042                 break;
4043         }
4044 }
4045
4046
4047 void GuiView::openChildDocument(string const & fname)
4048 {
4049         LASSERT(documentBufferView(), return);
4050         Buffer & buffer = documentBufferView()->buffer();
4051         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4052         documentBufferView()->saveBookmark(false);
4053         Buffer * child = nullptr;
4054         if (theBufferList().exists(filename)) {
4055                 child = theBufferList().getBuffer(filename);
4056                 setBuffer(child);
4057         } else {
4058                 message(bformat(_("Opening child document %1$s..."),
4059                         makeDisplayPath(filename.absFileName())));
4060                 child = loadDocument(filename, false);
4061         }
4062         // Set the parent name of the child document.
4063         // This makes insertion of citations and references in the child work,
4064         // when the target is in the parent or another child document.
4065         if (child)
4066                 child->setParent(&buffer);
4067 }
4068
4069
4070 bool GuiView::goToFileRow(string const & argument)
4071 {
4072         string file_name;
4073         int row = -1;
4074         size_t i = argument.find_last_of(' ');
4075         if (i != string::npos) {
4076                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4077                 istringstream is(argument.substr(i + 1));
4078                 is >> row;
4079                 if (is.fail())
4080                         i = string::npos;
4081         }
4082         if (i == string::npos) {
4083                 LYXERR0("Wrong argument: " << argument);
4084                 return false;
4085         }
4086         Buffer * buf = nullptr;
4087         string const realtmp = package().temp_dir().realPath();
4088         // We have to use os::path_prefix_is() here, instead of
4089         // simply prefixIs(), because the file name comes from
4090         // an external application and may need case adjustment.
4091         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4092                 buf = theBufferList().getBufferFromTmp(file_name, true);
4093                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4094                            << (buf ? " success" : " failed"));
4095         } else {
4096                 // Must replace extension of the file to be .lyx
4097                 // and get full path
4098                 FileName const s = fileSearch(string(),
4099                                                   support::changeExtension(file_name, ".lyx"), "lyx");
4100                 // Either change buffer or load the file
4101                 if (theBufferList().exists(s))
4102                         buf = theBufferList().getBuffer(s);
4103                 else if (s.exists()) {
4104                         buf = loadDocument(s);
4105                         if (!buf)
4106                                 return false;
4107                 } else {
4108                         message(bformat(
4109                                         _("File does not exist: %1$s"),
4110                                         makeDisplayPath(file_name)));
4111                         return false;
4112                 }
4113         }
4114         if (!buf) {
4115                 message(bformat(
4116                         _("No buffer for file: %1$s."),
4117                         makeDisplayPath(file_name))
4118                 );
4119                 return false;
4120         }
4121         setBuffer(buf);
4122         bool success = documentBufferView()->setCursorFromRow(row);
4123         if (!success) {
4124                 LYXERR(Debug::OUTFILE,
4125                        "setCursorFromRow: invalid position for row " << row);
4126                 frontend::Alert::error(_("Inverse Search Failed"),
4127                                        _("Invalid position requested by inverse search.\n"
4128                                          "You may need to update the viewed document."));
4129         }
4130         return success;
4131 }
4132
4133
4134 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4135 {
4136         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4137         menu->exec(QCursor::pos());
4138 }
4139
4140
4141 template<class T>
4142 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4143                 Buffer const * orig, Buffer * clone, string const & format)
4144 {
4145         Buffer::ExportStatus const status = func(format);
4146
4147         // the cloning operation will have produced a clone of the entire set of
4148         // documents, starting from the master. so we must delete those.
4149         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4150         delete mbuf;
4151         busyBuffers.remove(orig);
4152         return status;
4153 }
4154
4155
4156 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4157                 Buffer const * orig, Buffer * clone, string const & format)
4158 {
4159         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4160                         &Buffer::doExport;
4161         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4162 }
4163
4164
4165 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4166                 Buffer const * orig, Buffer * clone, string const & format)
4167 {
4168         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4169                         &Buffer::doExport;
4170         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4171 }
4172
4173
4174 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4175                 Buffer const * orig, Buffer * clone, string const & format)
4176 {
4177         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4178                         &Buffer::preview;
4179         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4180 }
4181
4182
4183 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4184                            Buffer const * used_buffer,
4185                            docstring const & msg,
4186                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4187                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4188                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4189                            bool allow_async, bool use_tmpdir)
4190 {
4191         if (!used_buffer)
4192                 return false;
4193
4194         string format = argument;
4195         if (format.empty())
4196                 format = used_buffer->params().getDefaultOutputFormat();
4197         processing_format = format;
4198         if (!msg.empty()) {
4199                 progress_->clearMessages();
4200                 gv_->message(msg);
4201         }
4202 #if EXPORT_in_THREAD
4203         if (allow_async) {
4204                 GuiViewPrivate::busyBuffers.insert(used_buffer);
4205                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4206                 if (!cloned_buffer) {
4207                         Alert::error(_("Export Error"),
4208                                      _("Error cloning the Buffer."));
4209                         return false;
4210                 }
4211                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4212                                         asyncFunc,
4213                                         used_buffer,
4214                                         cloned_buffer,
4215                                         format);
4216                 setPreviewFuture(f);
4217                 last_export_format = used_buffer->params().bufferFormat();
4218                 (void) syncFunc;
4219                 (void) previewFunc;
4220                 // We are asynchronous, so we don't know here anything about the success
4221                 return true;
4222         } else {
4223                 Buffer::ExportStatus status;
4224                 if (syncFunc) {
4225                         status = (used_buffer->*syncFunc)(format, use_tmpdir);
4226                 } else if (previewFunc) {
4227                         status = (used_buffer->*previewFunc)(format);
4228                 } else
4229                         return false;
4230                 handleExportStatus(gv_, status, format);
4231                 (void) asyncFunc;
4232                 return (status == Buffer::ExportSuccess
4233                                 || status == Buffer::PreviewSuccess);
4234         }
4235 #else
4236         (void) allow_async;
4237         Buffer::ExportStatus status;
4238         if (syncFunc) {
4239                 status = (used_buffer->*syncFunc)(format, true);
4240         } else if (previewFunc) {
4241                 status = (used_buffer->*previewFunc)(format);
4242         } else
4243                 return false;
4244         handleExportStatus(gv_, status, format);
4245         (void) asyncFunc;
4246         return (status == Buffer::ExportSuccess
4247                         || status == Buffer::PreviewSuccess);
4248 #endif
4249 }
4250
4251 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4252 {
4253         BufferView * bv = currentBufferView();
4254         LASSERT(bv, return);
4255
4256         // Let the current BufferView dispatch its own actions.
4257         bv->dispatch(cmd, dr);
4258         if (dr.dispatched()) {
4259                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4260                         updateDialog("document", "");
4261                 return;
4262         }
4263
4264         // Try with the document BufferView dispatch if any.
4265         BufferView * doc_bv = documentBufferView();
4266         if (doc_bv && doc_bv != bv) {
4267                 doc_bv->dispatch(cmd, dr);
4268                 if (dr.dispatched()) {
4269                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4270                                 updateDialog("document", "");
4271                         return;
4272                 }
4273         }
4274
4275         // Then let the current Cursor dispatch its own actions.
4276         bv->cursor().dispatch(cmd);
4277
4278         // update completion. We do it here and not in
4279         // processKeySym to avoid another redraw just for a
4280         // changed inline completion
4281         if (cmd.origin() == FuncRequest::KEYBOARD) {
4282                 if (cmd.action() == LFUN_SELF_INSERT
4283                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4284                         updateCompletion(bv->cursor(), true, true);
4285                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4286                         updateCompletion(bv->cursor(), false, true);
4287                 else
4288                         updateCompletion(bv->cursor(), false, false);
4289         }
4290
4291         dr = bv->cursor().result();
4292 }
4293
4294
4295 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4296 {
4297         BufferView * bv = currentBufferView();
4298         // By default we won't need any update.
4299         dr.screenUpdate(Update::None);
4300         // assume cmd will be dispatched
4301         dr.dispatched(true);
4302
4303         Buffer * doc_buffer = documentBufferView()
4304                 ? &(documentBufferView()->buffer()) : nullptr;
4305
4306         if (cmd.origin() == FuncRequest::TOC) {
4307                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4308                 toc->doDispatch(bv->cursor(), cmd, dr);
4309                 return;
4310         }
4311
4312         string const argument = to_utf8(cmd.argument());
4313
4314         switch(cmd.action()) {
4315                 case LFUN_BUFFER_CHILD_OPEN:
4316                         openChildDocument(to_utf8(cmd.argument()));
4317                         break;
4318
4319                 case LFUN_BUFFER_IMPORT:
4320                         importDocument(to_utf8(cmd.argument()));
4321                         break;
4322
4323                 case LFUN_MASTER_BUFFER_EXPORT:
4324                         if (doc_buffer)
4325                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4326                         // fall through
4327                 case LFUN_BUFFER_EXPORT: {
4328                         if (!doc_buffer)
4329                                 break;
4330                         // GCC only sees strfwd.h when building merged
4331                         if (::lyx::operator==(cmd.argument(), "custom")) {
4332                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4333                                 // so the following test should not be needed.
4334                                 // In principle, we could try to switch to such a view...
4335                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4336                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4337                                 break;
4338                         }
4339
4340                         string const dest = cmd.getArg(1);
4341                         FileName target_dir;
4342                         if (!dest.empty() && FileName::isAbsolute(dest))
4343                                 target_dir = FileName(support::onlyPath(dest));
4344                         else
4345                                 target_dir = doc_buffer->fileName().onlyPath();
4346
4347                         string const format = (argument.empty() || argument == "default") ?
4348                                 doc_buffer->params().getDefaultOutputFormat() : argument;
4349
4350                         if ((dest.empty() && doc_buffer->isUnnamed())
4351                             || !target_dir.isDirWritable()) {
4352                                 exportBufferAs(*doc_buffer, from_utf8(format));
4353                                 break;
4354                         }
4355                         /* TODO/Review: Is it a problem to also export the children?
4356                                         See the update_unincluded flag */
4357                         d.asyncBufferProcessing(format,
4358                                                 doc_buffer,
4359                                                 _("Exporting ..."),
4360                                                 &GuiViewPrivate::exportAndDestroy,
4361                                                 &Buffer::doExport,
4362                                                 nullptr, cmd.allowAsync());
4363                         // TODO Inform user about success
4364                         break;
4365                 }
4366
4367                 case LFUN_BUFFER_EXPORT_AS: {
4368                         LASSERT(doc_buffer, break);
4369                         docstring f = cmd.argument();
4370                         if (f.empty())
4371                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4372                         exportBufferAs(*doc_buffer, f);
4373                         break;
4374                 }
4375
4376                 case LFUN_BUFFER_UPDATE: {
4377                         d.asyncBufferProcessing(argument,
4378                                                 doc_buffer,
4379                                                 _("Exporting ..."),
4380                                                 &GuiViewPrivate::compileAndDestroy,
4381                                                 &Buffer::doExport,
4382                                                 nullptr, cmd.allowAsync(), true);
4383                         break;
4384                 }
4385                 case LFUN_BUFFER_VIEW: {
4386                         d.asyncBufferProcessing(argument,
4387                                                 doc_buffer,
4388                                                 _("Previewing ..."),
4389                                                 &GuiViewPrivate::previewAndDestroy,
4390                                                 nullptr,
4391                                                 &Buffer::preview, cmd.allowAsync());
4392                         break;
4393                 }
4394                 case LFUN_MASTER_BUFFER_UPDATE: {
4395                         d.asyncBufferProcessing(argument,
4396                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4397                                                 docstring(),
4398                                                 &GuiViewPrivate::compileAndDestroy,
4399                                                 &Buffer::doExport,
4400                                                 nullptr, cmd.allowAsync(), true);
4401                         break;
4402                 }
4403                 case LFUN_MASTER_BUFFER_VIEW: {
4404                         d.asyncBufferProcessing(argument,
4405                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4406                                                 docstring(),
4407                                                 &GuiViewPrivate::previewAndDestroy,
4408                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4409                         break;
4410                 }
4411                 case LFUN_EXPORT_CANCEL: {
4412                         Systemcall::killscript();
4413                         break;
4414                 }
4415                 case LFUN_BUFFER_SWITCH: {
4416                         string const file_name = to_utf8(cmd.argument());
4417                         if (!FileName::isAbsolute(file_name)) {
4418                                 dr.setError(true);
4419                                 dr.setMessage(_("Absolute filename expected."));
4420                                 break;
4421                         }
4422
4423                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4424                         if (!buffer) {
4425                                 dr.setError(true);
4426                                 dr.setMessage(_("Document not loaded"));
4427                                 break;
4428                         }
4429
4430                         // Do we open or switch to the buffer in this view ?
4431                         if (workArea(*buffer)
4432                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4433                                 setBuffer(buffer);
4434                                 break;
4435                         }
4436
4437                         // Look for the buffer in other views
4438                         QList<int> const ids = guiApp->viewIds();
4439                         int i = 0;
4440                         for (; i != ids.size(); ++i) {
4441                                 GuiView & gv = guiApp->view(ids[i]);
4442                                 if (gv.workArea(*buffer)) {
4443                                         gv.raise();
4444                                         gv.activateWindow();
4445                                         gv.setFocus();
4446                                         gv.setBuffer(buffer);
4447                                         break;
4448                                 }
4449                         }
4450
4451                         // If necessary, open a new window as a last resort
4452                         if (i == ids.size()) {
4453                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4454                                 lyx::dispatch(cmd);
4455                         }
4456                         break;
4457                 }
4458
4459                 case LFUN_BUFFER_NEXT:
4460                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4461                         break;
4462
4463                 case LFUN_BUFFER_MOVE_NEXT:
4464                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4465                         break;
4466
4467                 case LFUN_BUFFER_PREVIOUS:
4468                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
4469                         break;
4470
4471                 case LFUN_BUFFER_MOVE_PREVIOUS:
4472                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
4473                         break;
4474
4475                 case LFUN_BUFFER_CHKTEX:
4476                         LASSERT(doc_buffer, break);
4477                         doc_buffer->runChktex();
4478                         break;
4479
4480                 case LFUN_COMMAND_EXECUTE: {
4481                         command_execute_ = true;
4482                         minibuffer_focus_ = true;
4483                         break;
4484                 }
4485                 case LFUN_DROP_LAYOUTS_CHOICE:
4486                         d.layout_->showPopup();
4487                         break;
4488
4489                 case LFUN_MENU_OPEN:
4490                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4491                                 menu->exec(QCursor::pos());
4492                         break;
4493
4494                 case LFUN_FILE_INSERT: {
4495                         bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4496                         if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4497                                 dr.forceBufferUpdate();
4498                                 dr.screenUpdate(Update::Force);
4499                         }
4500                         break;
4501                 }
4502
4503                 case LFUN_FILE_INSERT_PLAINTEXT:
4504                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4505                         string const fname = to_utf8(cmd.argument());
4506                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4507                                 dr.setMessage(_("Absolute filename expected."));
4508                                 break;
4509                         }
4510
4511                         FileName filename(fname);
4512                         if (fname.empty()) {
4513                                 FileDialog dlg(qt_("Select file to insert"));
4514
4515                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4516                                         QStringList(qt_("All Files (*)")));
4517
4518                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4519                                         dr.setMessage(_("Canceled."));
4520                                         break;
4521                                 }
4522
4523                                 filename.set(fromqstr(result.second));
4524                         }
4525
4526                         if (bv) {
4527                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4528                                 bv->dispatch(new_cmd, dr);
4529                         }
4530                         break;
4531                 }
4532
4533                 case LFUN_BUFFER_RELOAD: {
4534                         LASSERT(doc_buffer, break);
4535
4536                         // drop changes?
4537                         bool drop = (cmd.argument() == "dump");
4538
4539                         int ret = 0;
4540                         if (!drop && !doc_buffer->isClean()) {
4541                                 docstring const file =
4542                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4543                                 if (doc_buffer->notifiesExternalModification()) {
4544                                         docstring text = _("The current version will be lost. "
4545                                             "Are you sure you want to load the version on disk "
4546                                             "of the document %1$s?");
4547                                         ret = Alert::prompt(_("Reload saved document?"),
4548                                                             bformat(text, file), 1, 1,
4549                                                             _("&Reload"), _("&Cancel"));
4550                                 } else {
4551                                         docstring text = _("Any changes will be lost. "
4552                                             "Are you sure you want to revert to the saved version "
4553                                             "of the document %1$s?");
4554                                         ret = Alert::prompt(_("Revert to saved document?"),
4555                                                             bformat(text, file), 1, 1,
4556                                                             _("&Revert"), _("&Cancel"));
4557                                 }
4558                         }
4559
4560                         if (ret == 0) {
4561                                 doc_buffer->markClean();
4562                                 reloadBuffer(*doc_buffer);
4563                                 dr.forceBufferUpdate();
4564                         }
4565                         break;
4566                 }
4567
4568                 case LFUN_BUFFER_RESET_EXPORT:
4569                         LASSERT(doc_buffer, break);
4570                         doc_buffer->requireFreshStart(true);
4571                         dr.setMessage(_("Buffer export reset."));
4572                         break;
4573
4574                 case LFUN_BUFFER_WRITE:
4575                         LASSERT(doc_buffer, break);
4576                         saveBuffer(*doc_buffer);
4577                         break;
4578
4579                 case LFUN_BUFFER_WRITE_AS:
4580                         LASSERT(doc_buffer, break);
4581                         renameBuffer(*doc_buffer, cmd.argument());
4582                         break;
4583
4584                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4585                         LASSERT(doc_buffer, break);
4586                         renameBuffer(*doc_buffer, cmd.argument(),
4587                                      LV_WRITE_AS_TEMPLATE);
4588                         break;
4589
4590                 case LFUN_BUFFER_WRITE_ALL: {
4591                         Buffer * first = theBufferList().first();
4592                         if (!first)
4593                                 break;
4594                         message(_("Saving all documents..."));
4595                         // We cannot use a for loop as the buffer list cycles.
4596                         Buffer * b = first;
4597                         do {
4598                                 if (!b->isClean()) {
4599                                         saveBuffer(*b);
4600                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4601                                 }
4602                                 b = theBufferList().next(b);
4603                         } while (b != first);
4604                         dr.setMessage(_("All documents saved."));
4605                         break;
4606                 }
4607
4608                 case LFUN_MASTER_BUFFER_FORALL: {
4609                         if (!doc_buffer)
4610                                 break;
4611
4612                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4613                         funcToRun.allowAsync(false);
4614
4615                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4616                                 // Switch to other buffer view and resend cmd
4617                                 lyx::dispatch(FuncRequest(
4618                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4619                                 lyx::dispatch(funcToRun);
4620                         }
4621                         // switch back
4622                         lyx::dispatch(FuncRequest(
4623                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4624                         break;
4625                 }
4626
4627                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4628                         LASSERT(doc_buffer, break);
4629                         doc_buffer->clearExternalModification();
4630                         break;
4631
4632                 case LFUN_BUFFER_CLOSE:
4633                         closeBuffer();
4634                         break;
4635
4636                 case LFUN_BUFFER_CLOSE_ALL:
4637                         closeBufferAll();
4638                         break;
4639
4640                 case LFUN_DEVEL_MODE_TOGGLE:
4641                         devel_mode_ = !devel_mode_;
4642                         if (devel_mode_)
4643                                 dr.setMessage(_("Developer mode is now enabled."));
4644                         else
4645                                 dr.setMessage(_("Developer mode is now disabled."));
4646                         break;
4647
4648                 case LFUN_TOOLBAR_SET: {
4649                         string const name = cmd.getArg(0);
4650                         string const state = cmd.getArg(1);
4651                         if (GuiToolbar * t = toolbar(name))
4652                                 t->setState(state);
4653                         break;
4654                 }
4655
4656                 case LFUN_TOOLBAR_TOGGLE: {
4657                         string const name = cmd.getArg(0);
4658                         if (GuiToolbar * t = toolbar(name))
4659                                 t->toggle();
4660                         break;
4661                 }
4662
4663                 case LFUN_TOOLBAR_MOVABLE: {
4664                         string const name = cmd.getArg(0);
4665                         if (name == "*") {
4666                                 // toggle (all) toolbars movablility
4667                                 toolbarsMovable_ = !toolbarsMovable_;
4668                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4669                                         GuiToolbar * tb = toolbar(ti.name);
4670                                         if (tb && tb->isMovable() != toolbarsMovable_)
4671                                                 // toggle toolbar movablity if it does not fit lock
4672                                                 // (all) toolbars positions state silent = true, since
4673                                                 // status bar notifications are slow
4674                                                 tb->movable(true);
4675                                 }
4676                                 if (toolbarsMovable_)
4677                                         dr.setMessage(_("Toolbars unlocked."));
4678                                 else
4679                                         dr.setMessage(_("Toolbars locked."));
4680                         } else if (GuiToolbar * tb = toolbar(name))
4681                                 // toggle current toolbar movablity
4682                                 tb->movable();
4683                         // update lock (all) toolbars positions
4684                         updateLockToolbars();
4685                         break;
4686                 }
4687
4688                 case LFUN_ICON_SIZE: {
4689                         QSize size = d.iconSize(cmd.argument());
4690                         setIconSize(size);
4691                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4692                                                 size.width(), size.height()));
4693                         break;
4694                 }
4695
4696                 case LFUN_DIALOG_UPDATE: {
4697                         string const name = to_utf8(cmd.argument());
4698                         if (name == "prefs" || name == "document")
4699                                 updateDialog(name, string());
4700                         else if (name == "paragraph")
4701                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4702                         else if (currentBufferView()) {
4703                                 Inset * inset = currentBufferView()->editedInset(name);
4704                                 // Can only update a dialog connected to an existing inset
4705                                 if (inset) {
4706                                         // FIXME: get rid of this indirection; GuiView ask the inset
4707                                         // if he is kind enough to update itself...
4708                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4709                                         //FIXME: pass DispatchResult here?
4710                                         inset->dispatch(currentBufferView()->cursor(), fr);
4711                                 }
4712                         }
4713                         break;
4714                 }
4715
4716                 case LFUN_DIALOG_TOGGLE: {
4717                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4718                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4719                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4720                         break;
4721                 }
4722
4723                 case LFUN_DIALOG_DISCONNECT_INSET:
4724                         disconnectDialog(to_utf8(cmd.argument()));
4725                         break;
4726
4727                 case LFUN_DIALOG_HIDE: {
4728                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4729                         break;
4730                 }
4731
4732                 case LFUN_DIALOG_SHOW: {
4733                         string const name = cmd.getArg(0);
4734                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4735
4736                         if (name == "latexlog") {
4737                                 // getStatus checks that
4738                                 LASSERT(doc_buffer, break);
4739                                 Buffer::LogType type;
4740                                 string const logfile = doc_buffer->logName(&type);
4741                                 switch (type) {
4742                                 case Buffer::latexlog:
4743                                         sdata = "latex ";
4744                                         break;
4745                                 case Buffer::buildlog:
4746                                         sdata = "literate ";
4747                                         break;
4748                                 }
4749                                 sdata += Lexer::quoteString(logfile);
4750                                 showDialog("log", sdata);
4751                         } else if (name == "vclog") {
4752                                 // getStatus checks that
4753                                 LASSERT(doc_buffer, break);
4754                                 string const sdata2 = "vc " +
4755                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4756                                 showDialog("log", sdata2);
4757                         } else if (name == "symbols") {
4758                                 sdata = bv->cursor().getEncoding()->name();
4759                                 if (!sdata.empty())
4760                                         showDialog("symbols", sdata);
4761                         } else if (name == "findreplace") {
4762                                 sdata = to_utf8(bv->cursor().selectionAsString(false));
4763                                 showDialog(name, sdata);
4764                         // bug 5274
4765                         } else if (name == "prefs" && isFullScreen()) {
4766                                 lfunUiToggle("fullscreen");
4767                                 showDialog("prefs", sdata);
4768                         } else
4769                                 showDialog(name, sdata);
4770                         break;
4771                 }
4772
4773                 case LFUN_MESSAGE:
4774                         dr.setMessage(cmd.argument());
4775                         break;
4776
4777                 case LFUN_UI_TOGGLE: {
4778                         string arg = cmd.getArg(0);
4779                         if (!lfunUiToggle(arg)) {
4780                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4781                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4782                         }
4783                         // Make sure the keyboard focus stays in the work area.
4784                         setFocus();
4785                         break;
4786                 }
4787
4788                 case LFUN_VIEW_SPLIT: {
4789                         LASSERT(doc_buffer, break);
4790                         string const orientation = cmd.getArg(0);
4791                         d.splitter_->setOrientation(orientation == "vertical"
4792                                 ? Qt::Vertical : Qt::Horizontal);
4793                         TabWorkArea * twa = addTabWorkArea();
4794                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4795                         setCurrentWorkArea(wa);
4796                         break;
4797                 }
4798                 case LFUN_TAB_GROUP_CLOSE:
4799                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4800                                 closeTabWorkArea(twa);
4801                                 d.current_work_area_ = nullptr;
4802                                 twa = d.currentTabWorkArea();
4803                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4804                                 if (twa) {
4805                                         // Make sure the work area is up to date.
4806                                         setCurrentWorkArea(twa->currentWorkArea());
4807                                 } else {
4808                                         setCurrentWorkArea(nullptr);
4809                                 }
4810                         }
4811                         break;
4812
4813                 case LFUN_VIEW_CLOSE:
4814                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4815                                 closeWorkArea(twa->currentWorkArea());
4816                                 d.current_work_area_ = nullptr;
4817                                 twa = d.currentTabWorkArea();
4818                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4819                                 if (twa) {
4820                                         // Make sure the work area is up to date.
4821                                         setCurrentWorkArea(twa->currentWorkArea());
4822                                 } else {
4823                                         setCurrentWorkArea(nullptr);
4824                                 }
4825                         }
4826                         break;
4827
4828                 case LFUN_COMPLETION_INLINE:
4829                         if (d.current_work_area_)
4830                                 d.current_work_area_->completer().showInline();
4831                         break;
4832
4833                 case LFUN_COMPLETION_POPUP:
4834                         if (d.current_work_area_)
4835                                 d.current_work_area_->completer().showPopup();
4836                         break;
4837
4838
4839                 case LFUN_COMPLETE:
4840                         if (d.current_work_area_)
4841                                 d.current_work_area_->completer().tab();
4842                         break;
4843
4844                 case LFUN_COMPLETION_CANCEL:
4845                         if (d.current_work_area_) {
4846                                 if (d.current_work_area_->completer().popupVisible())
4847                                         d.current_work_area_->completer().hidePopup();
4848                                 else
4849                                         d.current_work_area_->completer().hideInline();
4850                         }
4851                         break;
4852
4853                 case LFUN_COMPLETION_ACCEPT:
4854                         if (d.current_work_area_)
4855                                 d.current_work_area_->completer().activate();
4856                         break;
4857
4858                 case LFUN_BUFFER_ZOOM_IN:
4859                 case LFUN_BUFFER_ZOOM_OUT:
4860                 case LFUN_BUFFER_ZOOM: {
4861                         zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4862
4863                         // Actual zoom value: default zoom + fractional extra value
4864                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4865                         zoom = min(max(zoom, zoom_min_), zoom_max_);
4866
4867                         setCurrentZoom(zoom);
4868
4869                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4870                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4871
4872                         guiApp->fontLoader().update();
4873                         dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4874                         break;
4875                 }
4876
4877                 case LFUN_VC_REGISTER:
4878                 case LFUN_VC_RENAME:
4879                 case LFUN_VC_COPY:
4880                 case LFUN_VC_CHECK_IN:
4881                 case LFUN_VC_CHECK_OUT:
4882                 case LFUN_VC_REPO_UPDATE:
4883                 case LFUN_VC_LOCKING_TOGGLE:
4884                 case LFUN_VC_REVERT:
4885                 case LFUN_VC_UNDO_LAST:
4886                 case LFUN_VC_COMMAND:
4887                 case LFUN_VC_COMPARE:
4888                         dispatchVC(cmd, dr);
4889                         break;
4890
4891                 case LFUN_SERVER_GOTO_FILE_ROW:
4892                         if(goToFileRow(to_utf8(cmd.argument())))
4893                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4894                         break;
4895
4896                 case LFUN_LYX_ACTIVATE:
4897                         activateWindow();
4898                         break;
4899
4900                 case LFUN_WINDOW_RAISE:
4901                         raise();
4902                         activateWindow();
4903                         showNormal();
4904                         break;
4905
4906                 case LFUN_FORWARD_SEARCH: {
4907                         // it seems safe to assume we have a document buffer, since
4908                         // getStatus wants one.
4909                         LASSERT(doc_buffer, break);
4910                         Buffer const * doc_master = doc_buffer->masterBuffer();
4911                         FileName const path(doc_master->temppath());
4912                         string const texname = doc_master->isChild(doc_buffer)
4913                                 ? DocFileName(changeExtension(
4914                                         doc_buffer->absFileName(),
4915                                                 "tex")).mangledFileName()
4916                                 : doc_buffer->latexName();
4917                         string const fulltexname =
4918                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4919                         string const mastername =
4920                                 removeExtension(doc_master->latexName());
4921                         FileName const dviname(addName(path.absFileName(),
4922                                         addExtension(mastername, "dvi")));
4923                         FileName const pdfname(addName(path.absFileName(),
4924                                         addExtension(mastername, "pdf")));
4925                         bool const have_dvi = dviname.exists();
4926                         bool const have_pdf = pdfname.exists();
4927                         if (!have_dvi && !have_pdf) {
4928                                 dr.setMessage(_("Please, preview the document first."));
4929                                 break;
4930                         }
4931                         bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4932                         bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4933                         string outname = dviname.onlyFileName();
4934                         string command = lyxrc.forward_search_dvi;
4935                         if ((!goto_dvi || goto_pdf) &&
4936                             pdfname.lastModified() > dviname.lastModified()) {
4937                                 outname = pdfname.onlyFileName();
4938                                 command = lyxrc.forward_search_pdf;
4939                         }
4940
4941                         DocIterator cur = bv->cursor();
4942                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4943                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4944                                    << " cur:" << cur);
4945                         if (row == -1 || command.empty()) {
4946                                 dr.setMessage(_("Couldn't proceed."));
4947                                 break;
4948                         }
4949                         string texrow = convert<string>(row);
4950
4951                         command = subst(command, "$$n", texrow);
4952                         command = subst(command, "$$f", fulltexname);
4953                         command = subst(command, "$$t", texname);
4954                         command = subst(command, "$$o", outname);
4955
4956                         volatile PathChanger p(path);
4957                         Systemcall one;
4958                         one.startscript(Systemcall::DontWait, command);
4959                         break;
4960                 }
4961
4962                 case LFUN_SPELLING_CONTINUOUSLY:
4963                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4964                         dr.screenUpdate(Update::Force);
4965                         break;
4966
4967                 case LFUN_CITATION_OPEN: {
4968                         string pdfv, psv;
4969                         if (theFormats().getFormat("pdf"))
4970                                 pdfv = theFormats().getFormat("pdf")->viewer();
4971                         if (theFormats().getFormat("ps"))
4972                                 psv = theFormats().getFormat("ps")->viewer();
4973                         frontend::showTarget(argument, pdfv, psv);
4974                         break;
4975                 }
4976
4977                 default:
4978                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4979                         // let's try that:
4980                         dispatchToBufferView(cmd, dr);
4981                         break;
4982         }
4983
4984         // Need to update bv because many LFUNs here might have destroyed it
4985         bv = currentBufferView();
4986
4987         // Clear non-empty selections
4988         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4989         if (bv) {
4990                 Cursor & cur = bv->cursor();
4991                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4992                         cur.clearSelection();
4993                 }
4994         }
4995 }
4996
4997
4998 bool GuiView::lfunUiToggle(string const & ui_component)
4999 {
5000         if (ui_component == "scrollbar") {
5001                 // hide() is of no help
5002                 if (d.current_work_area_->verticalScrollBarPolicy() ==
5003                         Qt::ScrollBarAlwaysOff)
5004
5005                         d.current_work_area_->setVerticalScrollBarPolicy(
5006                                 Qt::ScrollBarAsNeeded);
5007                 else
5008                         d.current_work_area_->setVerticalScrollBarPolicy(
5009                                 Qt::ScrollBarAlwaysOff);
5010         } else if (ui_component == "statusbar") {
5011                 statusBar()->setVisible(!statusBar()->isVisible());
5012         } else if (ui_component == "menubar") {
5013                 menuBar()->setVisible(!menuBar()->isVisible());
5014         } else if (ui_component == "zoomlevel") {
5015                 zoom_value_->setVisible(!zoom_value_->isVisible());
5016         } else if (ui_component == "zoomslider") {
5017                 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5018                 zoom_in_->setVisible(zoom_slider_->isVisible());
5019                 zoom_out_->setVisible(zoom_slider_->isVisible());
5020         } else if (ui_component == "statistics-w") {
5021                 word_count_enabled_ = !word_count_enabled_;
5022                 if (statsEnabled())
5023                         showStats();
5024         } else if (ui_component == "statistics-cb") {
5025                 char_count_enabled_ = !char_count_enabled_;
5026                 if (statsEnabled())
5027                         showStats();
5028         } else if (ui_component == "statistics-c") {
5029                 char_nb_count_enabled_ = !char_nb_count_enabled_;
5030                 if (statsEnabled())
5031                         showStats();
5032         } else if (ui_component == "frame") {
5033                 int const l = contentsMargins().left();
5034
5035                 //are the frames in default state?
5036                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5037                 if (l == 0) {
5038 #if QT_VERSION >  0x050903
5039                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5040 #endif
5041                         setContentsMargins(-2, -2, -2, -2);
5042                 } else {
5043 #if QT_VERSION >  0x050903
5044                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5045 #endif
5046                         setContentsMargins(0, 0, 0, 0);
5047                 }
5048         } else
5049         if (ui_component == "fullscreen") {
5050                 toggleFullScreen();
5051         } else
5052                 return false;
5053         stat_counts_->setVisible(statsEnabled());
5054         return true;
5055 }
5056
5057
5058 void GuiView::toggleFullScreen()
5059 {
5060         setWindowState(windowState() ^ Qt::WindowFullScreen);
5061 }
5062
5063
5064 Buffer const * GuiView::updateInset(Inset const * inset)
5065 {
5066         if (!inset)
5067                 return nullptr;
5068
5069         Buffer const * inset_buffer = &(inset->buffer());
5070
5071         for (int i = 0; i != d.splitter_->count(); ++i) {
5072                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5073                 if (!wa)
5074                         continue;
5075                 Buffer const * buffer = &(wa->bufferView().buffer());
5076                 if (inset_buffer == buffer)
5077                         wa->scheduleRedraw(true);
5078         }
5079         return inset_buffer;
5080 }
5081
5082
5083 void GuiView::restartCaret()
5084 {
5085         /* When we move around, or type, it's nice to be able to see
5086          * the caret immediately after the keypress.
5087          */
5088         if (d.current_work_area_)
5089                 d.current_work_area_->startBlinkingCaret();
5090
5091         // Take this occasion to update the other GUI elements.
5092         updateDialogs();
5093         updateStatusBar();
5094 }
5095
5096
5097 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5098 {
5099         if (d.current_work_area_)
5100                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5101 }
5102
5103 namespace {
5104
5105 // This list should be kept in sync with the list of insets in
5106 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
5107 // dialog should have the same name as the inset.
5108 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5109 // docs in LyXAction.cpp.
5110
5111 char const * const dialognames[] = {
5112
5113 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5114 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5115 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5116 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5117 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5118 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5119 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5120 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5121
5122 char const * const * const end_dialognames =
5123         dialognames + (sizeof(dialognames) / sizeof(char *));
5124
5125 class cmpCStr {
5126 public:
5127         cmpCStr(char const * name) : name_(name) {}
5128         bool operator()(char const * other) {
5129                 return strcmp(other, name_) == 0;
5130         }
5131 private:
5132         char const * name_;
5133 };
5134
5135
5136 bool isValidName(string const & name)
5137 {
5138         return find_if(dialognames, end_dialognames,
5139                 cmpCStr(name.c_str())) != end_dialognames;
5140 }
5141
5142 } // namespace
5143
5144
5145 void GuiView::resetDialogs()
5146 {
5147         // Make sure that no LFUN uses any GuiView.
5148         guiApp->setCurrentView(nullptr);
5149         saveLayout();
5150         saveUISettings();
5151         menuBar()->clear();
5152         constructToolbars();
5153         guiApp->menus().fillMenuBar(menuBar(), this, false);
5154         d.layout_->updateContents(true);
5155         // Now update controls with current buffer.
5156         guiApp->setCurrentView(this);
5157         restoreLayout();
5158         restartCaret();
5159 }
5160
5161
5162 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5163 {
5164         for (QObject * child: widget->children()) {
5165                 if (child->inherits("QGroupBox")) {
5166                         QGroupBox * box = (QGroupBox*) child;
5167                         box->setFlat(flag);
5168                 } else {
5169                         flatGroupBoxes(child, flag);
5170                 }
5171         }
5172 }
5173
5174
5175 Dialog * GuiView::find(string const & name, bool hide_it) const
5176 {
5177         if (!isValidName(name))
5178                 return nullptr;
5179
5180         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5181
5182         if (it != d.dialogs_.end()) {
5183                 if (hide_it)
5184                         it->second->hideView();
5185                 return it->second.get();
5186         }
5187         return nullptr;
5188 }
5189
5190
5191 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5192 {
5193         Dialog * dialog = find(name, hide_it);
5194         if (dialog != nullptr)
5195                 return dialog;
5196
5197         dialog = build(name);
5198         d.dialogs_[name].reset(dialog);
5199         // Force a uniform style for group boxes
5200         // On Mac non-flat works better, on Linux flat is standard
5201         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5202         if (lyxrc.allow_geometry_session)
5203                 dialog->restoreSession();
5204         if (hide_it)
5205                 dialog->hideView();
5206         return dialog;
5207 }
5208
5209
5210 void GuiView::showDialog(string const & name, string const & sdata,
5211         Inset * inset)
5212 {
5213         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5214 }
5215
5216
5217 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5218         Inset * inset)
5219 {
5220         if (d.in_show_)
5221                 return;
5222
5223         const string name = fromqstr(qname);
5224         const string sdata = fromqstr(qdata);
5225
5226         d.in_show_ = true;
5227         try {
5228                 Dialog * dialog = findOrBuild(name, false);
5229                 if (dialog) {
5230                         bool const visible = dialog->isVisibleView();
5231                         dialog->showData(sdata);
5232                         if (currentBufferView())
5233                                 currentBufferView()->editInset(name, inset);
5234                         // We only set the focus to the new dialog if it was not yet
5235                         // visible in order not to change the existing previous behaviour
5236                         if (visible) {
5237                                 // activateWindow is needed for floating dockviews
5238                                 dialog->asQWidget()->raise();
5239                                 dialog->asQWidget()->activateWindow();
5240                                 if (dialog->wantInitialFocus())
5241                                         dialog->asQWidget()->setFocus();
5242                         }
5243                 }
5244         }
5245         catch (ExceptionMessage const &) {
5246                 d.in_show_ = false;
5247                 throw;
5248         }
5249         d.in_show_ = false;
5250 }
5251
5252
5253 bool GuiView::isDialogVisible(string const & name) const
5254 {
5255         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5256         if (it == d.dialogs_.end())
5257                 return false;
5258         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5259 }
5260
5261
5262 void GuiView::hideDialog(string const & name, Inset * inset)
5263 {
5264         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5265         if (it == d.dialogs_.end())
5266                 return;
5267
5268         if (inset) {
5269                 if (!currentBufferView())
5270                         return;
5271                 if (inset != currentBufferView()->editedInset(name))
5272                         return;
5273         }
5274
5275         Dialog * const dialog = it->second.get();
5276         if (dialog->isVisibleView())
5277                 dialog->hideView();
5278         if (currentBufferView())
5279                 currentBufferView()->editInset(name, nullptr);
5280 }
5281
5282
5283 void GuiView::disconnectDialog(string const & name)
5284 {
5285         if (!isValidName(name))
5286                 return;
5287         if (currentBufferView())
5288                 currentBufferView()->editInset(name, nullptr);
5289 }
5290
5291
5292 void GuiView::hideAll() const
5293 {
5294         for(auto const & dlg_p : d.dialogs_)
5295                 dlg_p.second->hideView();
5296 }
5297
5298
5299 void GuiView::updateDialogs()
5300 {
5301         for(auto const & dlg_p : d.dialogs_) {
5302                 Dialog * dialog = dlg_p.second.get();
5303                 if (dialog) {
5304                         if (dialog->needBufferOpen() && !documentBufferView())
5305                                 hideDialog(fromqstr(dialog->name()), nullptr);
5306                         else if (dialog->isVisibleView())
5307                                 dialog->checkStatus();
5308                 }
5309         }
5310         updateToolbars();
5311         updateLayoutList();
5312 }
5313
5314
5315 Dialog * GuiView::build(string const & name)
5316 {
5317         return createDialog(*this, name);
5318 }
5319
5320
5321 SEMenu::SEMenu(QWidget * parent)
5322 {
5323         QAction * action = addAction(qt_("Disable Shell Escape"));
5324         connect(action, SIGNAL(triggered()),
5325                 parent, SLOT(disableShellEscape()));
5326 }
5327
5328
5329 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5330 {
5331         if (event->button() == Qt::LeftButton) {
5332         Q_EMIT pressed();
5333     }
5334 }
5335
5336 } // namespace frontend
5337 } // namespace lyx
5338
5339 #include "moc_GuiView.cpp"