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