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