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