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