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