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