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