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