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