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