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