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