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