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