]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiView.cpp
Hack to display section symbol
[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                         setCurrentWorkArea(wa);
4884                         break;
4885                 }
4886
4887                 case LFUN_TAB_GROUP_NEXT:
4888                         gotoNextTabWorkArea(NEXT);
4889                         break;
4890
4891                 case LFUN_TAB_GROUP_PREVIOUS:
4892                         gotoNextTabWorkArea(PREV);
4893                         break;
4894
4895                 case LFUN_TAB_GROUP_CLOSE:
4896                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4897                                 closeTabWorkArea(twa);
4898                                 d.current_work_area_ = nullptr;
4899                                 twa = d.currentTabWorkArea();
4900                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4901                                 if (twa) {
4902                                         // Make sure the work area is up to date.
4903                                         setCurrentWorkArea(twa->currentWorkArea());
4904                                 } else {
4905                                         setCurrentWorkArea(nullptr);
4906                                 }
4907                         }
4908                         break;
4909
4910                 case LFUN_VIEW_CLOSE:
4911                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4912                                 closeWorkArea(twa->currentWorkArea());
4913                                 d.current_work_area_ = nullptr;
4914                                 twa = d.currentTabWorkArea();
4915                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4916                                 if (twa) {
4917                                         // Make sure the work area is up to date.
4918                                         setCurrentWorkArea(twa->currentWorkArea());
4919                                 } else {
4920                                         setCurrentWorkArea(nullptr);
4921                                 }
4922                         }
4923                         break;
4924
4925                 case LFUN_COMPLETION_INLINE:
4926                         if (d.current_work_area_)
4927                                 d.current_work_area_->completer().showInline();
4928                         break;
4929
4930                 case LFUN_COMPLETION_POPUP:
4931                         if (d.current_work_area_)
4932                                 d.current_work_area_->completer().showPopup();
4933                         break;
4934
4935
4936                 case LFUN_COMPLETE:
4937                         if (d.current_work_area_)
4938                                 d.current_work_area_->completer().tab();
4939                         break;
4940
4941                 case LFUN_COMPLETION_CANCEL:
4942                         if (d.current_work_area_) {
4943                                 if (d.current_work_area_->completer().popupVisible())
4944                                         d.current_work_area_->completer().hidePopup();
4945                                 else
4946                                         d.current_work_area_->completer().hideInline();
4947                         }
4948                         break;
4949
4950                 case LFUN_COMPLETION_ACCEPT:
4951                         if (d.current_work_area_)
4952                                 d.current_work_area_->completer().activate();
4953                         break;
4954
4955                 case LFUN_BUFFER_ZOOM_IN:
4956                 case LFUN_BUFFER_ZOOM_OUT:
4957                 case LFUN_BUFFER_ZOOM: {
4958                         zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4959
4960                         // Actual zoom value: default zoom + fractional extra value
4961                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4962                         zoom = min(max(zoom, zoom_min_), zoom_max_);
4963
4964                         setCurrentZoom(zoom);
4965
4966                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4967                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4968
4969                         guiApp->fontLoader().update();
4970                         // Regenerate instant previews
4971                         if (lyxrc.preview != LyXRC::PREVIEW_OFF
4972                             && doc_buffer && doc_buffer->loader())
4973                                 doc_buffer->loader()->refreshPreviews();
4974                         dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4975                         break;
4976                 }
4977
4978                 case LFUN_VC_REGISTER:
4979                 case LFUN_VC_RENAME:
4980                 case LFUN_VC_COPY:
4981                 case LFUN_VC_CHECK_IN:
4982                 case LFUN_VC_CHECK_OUT:
4983                 case LFUN_VC_REPO_UPDATE:
4984                 case LFUN_VC_LOCKING_TOGGLE:
4985                 case LFUN_VC_REVERT:
4986                 case LFUN_VC_UNDO_LAST:
4987                 case LFUN_VC_COMMAND:
4988                 case LFUN_VC_COMPARE:
4989                         dispatchVC(cmd, dr);
4990                         break;
4991
4992                 case LFUN_SERVER_GOTO_FILE_ROW:
4993                         if(goToFileRow(to_utf8(cmd.argument())))
4994                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4995                         break;
4996
4997                 case LFUN_LYX_ACTIVATE:
4998                         activateWindow();
4999                         break;
5000
5001                 case LFUN_WINDOW_RAISE:
5002                         raise();
5003                         activateWindow();
5004                         showNormal();
5005                         break;
5006
5007                 case LFUN_FORWARD_SEARCH: {
5008                         // it seems safe to assume we have a document buffer, since
5009                         // getStatus wants one.
5010                         LASSERT(doc_buffer, break);
5011                         Buffer const * doc_master = doc_buffer->masterBuffer();
5012                         FileName const path(doc_master->temppath());
5013                         string const texname = doc_master->isChild(doc_buffer)
5014                                 ? DocFileName(changeExtension(
5015                                         doc_buffer->absFileName(),
5016                                                 "tex")).mangledFileName()
5017                                 : doc_buffer->latexName();
5018                         string const fulltexname =
5019                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
5020                         string const mastername =
5021                                 removeExtension(doc_master->latexName());
5022                         FileName const dviname(addName(path.absFileName(),
5023                                         addExtension(mastername, "dvi")));
5024                         FileName const pdfname(addName(path.absFileName(),
5025                                         addExtension(mastername, "pdf")));
5026                         bool const have_dvi = dviname.exists();
5027                         bool const have_pdf = pdfname.exists();
5028                         if (!have_dvi && !have_pdf) {
5029                                 dr.setMessage(_("Please, preview the document first."));
5030                                 break;
5031                         }
5032                         bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
5033                         bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
5034                         string outname = dviname.onlyFileName();
5035                         string command = lyxrc.forward_search_dvi;
5036                         if ((!goto_dvi || goto_pdf) &&
5037                             pdfname.lastModified() > dviname.lastModified()) {
5038                                 outname = pdfname.onlyFileName();
5039                                 command = lyxrc.forward_search_pdf;
5040                         }
5041
5042                         DocIterator cur = bv->cursor();
5043                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
5044                         LYXERR(Debug::ACTION, "Forward search: row:" << row
5045                                    << " cur:" << cur);
5046                         if (row == -1 || command.empty()) {
5047                                 dr.setMessage(_("Couldn't proceed."));
5048                                 break;
5049                         }
5050                         string texrow = convert<string>(row);
5051
5052                         command = subst(command, "$$n", texrow);
5053                         command = subst(command, "$$f", fulltexname);
5054                         command = subst(command, "$$t", texname);
5055                         command = subst(command, "$$o", outname);
5056
5057                         volatile PathChanger p(path);
5058                         Systemcall one;
5059                         one.startscript(Systemcall::DontWait, command);
5060                         break;
5061                 }
5062
5063                 case LFUN_SPELLING_CONTINUOUSLY:
5064                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5065                         dr.screenUpdate(Update::Force);
5066                         break;
5067
5068                 case LFUN_CITATION_OPEN: {
5069                         string pdfv, psv;
5070                         if (theFormats().getFormat("pdf"))
5071                                 pdfv = theFormats().getFormat("pdf")->viewer();
5072                         if (theFormats().getFormat("ps"))
5073                                 psv = theFormats().getFormat("ps")->viewer();
5074                         frontend::showTarget(argument, pdfv, psv);
5075                         break;
5076                 }
5077
5078                 default:
5079                         // The LFUN must be for one of BufferView, Buffer or Cursor;
5080                         // let's try that:
5081                         dispatchToBufferView(cmd, dr);
5082                         break;
5083         }
5084
5085         // Need to update bv because many LFUNs here might have destroyed it
5086         bv = currentBufferView();
5087
5088         // Clear non-empty selections
5089         // (e.g. from a "char-forward-select" followed by "char-backward-select")
5090         if (bv) {
5091                 Cursor & cur = bv->cursor();
5092                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5093                         cur.clearSelection();
5094                 }
5095         }
5096 }
5097
5098
5099 bool GuiView::lfunUiToggle(string const & ui_component)
5100 {
5101         if (ui_component == "scrollbar") {
5102                 // hide() is of no help
5103                 if (d.current_work_area_->verticalScrollBarPolicy() ==
5104                         Qt::ScrollBarAlwaysOff)
5105
5106                         d.current_work_area_->setVerticalScrollBarPolicy(
5107                                 Qt::ScrollBarAsNeeded);
5108                 else
5109                         d.current_work_area_->setVerticalScrollBarPolicy(
5110                                 Qt::ScrollBarAlwaysOff);
5111         } else if (ui_component == "statusbar") {
5112                 statusBar()->setVisible(!statusBar()->isVisible());
5113         } else if (ui_component == "menubar") {
5114                 menuBar()->setVisible(!menuBar()->isVisible());
5115         } else if (ui_component == "zoomlevel") {
5116                 zoom_value_->setVisible(!zoom_value_->isVisible());
5117         } else if (ui_component == "zoomslider") {
5118                 zoom_widget_->setVisible(!zoom_widget_->isVisible());
5119         } else if (ui_component == "statistics-w") {
5120                 word_count_enabled_ = !word_count_enabled_;
5121                 if (statsEnabled())
5122                         showStats();
5123         } else if (ui_component == "statistics-cb") {
5124                 char_count_enabled_ = !char_count_enabled_;
5125                 if (statsEnabled())
5126                         showStats();
5127         } else if (ui_component == "statistics-c") {
5128                 char_nb_count_enabled_ = !char_nb_count_enabled_;
5129                 if (statsEnabled())
5130                         showStats();
5131         } else if (ui_component == "frame") {
5132                 int const l = contentsMargins().left();
5133
5134                 //are the frames in default state?
5135                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5136                 if (l == 0) {
5137 #if QT_VERSION >  0x050903
5138                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5139 #endif
5140                         setContentsMargins(-2, -2, -2, -2);
5141                 } else {
5142 #if QT_VERSION >  0x050903
5143                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5144 #endif
5145                         setContentsMargins(0, 0, 0, 0);
5146                 }
5147         } else
5148         if (ui_component == "fullscreen") {
5149                 toggleFullScreen();
5150         } else
5151                 return false;
5152         stat_counts_->setVisible(statsEnabled());
5153         return true;
5154 }
5155
5156
5157 void GuiView::cancelExport()
5158 {
5159         Systemcall::killscript();
5160         // stop busy signal immediately so that in the subsequent
5161         // "Export canceled" prompt the status bar icons are accurate.
5162         Q_EMIT scriptKilled();
5163 }
5164
5165
5166 void GuiView::toggleFullScreen()
5167 {
5168         setWindowState(windowState() ^ Qt::WindowFullScreen);
5169 }
5170
5171
5172 Buffer const * GuiView::updateInset(Inset const * inset)
5173 {
5174         if (!inset)
5175                 return nullptr;
5176
5177         Buffer const * inset_buffer = &(inset->buffer());
5178
5179         for (int i = 0; i != d.splitter_->count(); ++i) {
5180                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5181                 if (!wa)
5182                         continue;
5183                 Buffer const * buffer = &(wa->bufferView().buffer());
5184                 if (inset_buffer == buffer)
5185                         wa->scheduleRedraw(true);
5186         }
5187         return inset_buffer;
5188 }
5189
5190
5191 void GuiView::restartCaret()
5192 {
5193         /* When we move around, or type, it's nice to be able to see
5194          * the caret immediately after the keypress.
5195          */
5196         if (d.current_work_area_)
5197                 d.current_work_area_->startBlinkingCaret();
5198
5199         // Take this occasion to update the other GUI elements.
5200         updateDialogs();
5201         updateStatusBar();
5202 }
5203
5204
5205 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5206 {
5207         if (d.current_work_area_)
5208                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5209 }
5210
5211 namespace {
5212
5213 // This list should be kept in sync with the list of insets in
5214 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
5215 // dialog should have the same name as the inset.
5216 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5217 // docs in LyXAction.cpp.
5218
5219 char const * const dialognames[] = {
5220
5221 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5222 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5223 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5224 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5225 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5226 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5227 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5228 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5229
5230 char const * const * const end_dialognames =
5231         dialognames + (sizeof(dialognames) / sizeof(char *));
5232
5233 class cmpCStr {
5234 public:
5235         cmpCStr(char const * name) : name_(name) {}
5236         bool operator()(char const * other) {
5237                 return strcmp(other, name_) == 0;
5238         }
5239 private:
5240         char const * name_;
5241 };
5242
5243
5244 bool isValidName(string const & name)
5245 {
5246         return find_if(dialognames, end_dialognames,
5247                 cmpCStr(name.c_str())) != end_dialognames;
5248 }
5249
5250 } // namespace
5251
5252
5253 void GuiView::resetDialogs()
5254 {
5255         // Make sure that no LFUN uses any GuiView.
5256         guiApp->setCurrentView(nullptr);
5257         saveLayout();
5258         saveUISettings();
5259         menuBar()->clear();
5260         constructToolbars();
5261         guiApp->menus().fillMenuBar(menuBar(), this, false);
5262         d.layout_->updateContents(true);
5263         // Now update controls with current buffer.
5264         guiApp->setCurrentView(this);
5265         restoreLayout();
5266         restartCaret();
5267 }
5268
5269
5270 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5271 {
5272         for (QObject * child: widget->children()) {
5273                 if (child->inherits("QGroupBox")) {
5274                         QGroupBox * box = (QGroupBox*) child;
5275                         box->setFlat(flag);
5276                 } else {
5277                         flatGroupBoxes(child, flag);
5278                 }
5279         }
5280 }
5281
5282
5283 Dialog * GuiView::find(string const & name, bool hide_it) const
5284 {
5285         if (!isValidName(name))
5286                 return nullptr;
5287
5288         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5289
5290         if (it != d.dialogs_.end()) {
5291                 if (hide_it)
5292                         it->second->hideView();
5293                 return it->second.get();
5294         }
5295         return nullptr;
5296 }
5297
5298
5299 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5300 {
5301         Dialog * dialog = find(name, hide_it);
5302         if (dialog != nullptr)
5303                 return dialog;
5304
5305         dialog = build(name);
5306         d.dialogs_[name].reset(dialog);
5307         // Force a uniform style for group boxes
5308         // On Mac non-flat works better, on Linux flat is standard
5309         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5310         if (lyxrc.allow_geometry_session)
5311                 dialog->restoreSession();
5312         if (hide_it)
5313                 dialog->hideView();
5314         return dialog;
5315 }
5316
5317
5318 void GuiView::showDialog(string const & name, string const & sdata,
5319         Inset * inset)
5320 {
5321         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5322 }
5323
5324
5325 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5326         Inset * inset)
5327 {
5328         if (d.in_show_)
5329                 return;
5330
5331         const string name = fromqstr(qname);
5332         const string sdata = fromqstr(qdata);
5333
5334         d.in_show_ = true;
5335         try {
5336                 Dialog * dialog = findOrBuild(name, false);
5337                 if (dialog) {
5338                         bool const visible = dialog->isVisibleView();
5339                         dialog->showData(sdata);
5340                         if (currentBufferView())
5341                                 currentBufferView()->editInset(name, inset);
5342                         // We only set the focus to the new dialog if it was not yet
5343                         // visible in order not to change the existing previous behaviour
5344                         if (visible) {
5345                                 // activateWindow is needed for floating dockviews
5346                                 dialog->asQWidget()->raise();
5347                                 dialog->asQWidget()->activateWindow();
5348                                 if (dialog->wantInitialFocus())
5349                                         dialog->asQWidget()->setFocus();
5350                         }
5351                 }
5352         }
5353         catch (ExceptionMessage const &) {
5354                 d.in_show_ = false;
5355                 throw;
5356         }
5357         d.in_show_ = false;
5358 }
5359
5360
5361 bool GuiView::isDialogVisible(string const & name) const
5362 {
5363         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5364         if (it == d.dialogs_.end())
5365                 return false;
5366         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5367 }
5368
5369
5370 void GuiView::hideDialog(string const & name, Inset * inset)
5371 {
5372         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5373         if (it == d.dialogs_.end())
5374                 return;
5375
5376         if (inset) {
5377                 if (!currentBufferView())
5378                         return;
5379                 if (inset != currentBufferView()->editedInset(name))
5380                         return;
5381         }
5382
5383         Dialog * const dialog = it->second.get();
5384         if (dialog->isVisibleView())
5385                 dialog->hideView();
5386         if (currentBufferView())
5387                 currentBufferView()->editInset(name, nullptr);
5388 }
5389
5390
5391 void GuiView::disconnectDialog(string const & name)
5392 {
5393         if (!isValidName(name))
5394                 return;
5395         if (currentBufferView())
5396                 currentBufferView()->editInset(name, nullptr);
5397 }
5398
5399
5400 void GuiView::hideAll() const
5401 {
5402         for(auto const & dlg_p : d.dialogs_)
5403                 dlg_p.second->hideView();
5404 }
5405
5406
5407 void GuiView::updateDialogs()
5408 {
5409         for(auto const & dlg_p : d.dialogs_) {
5410                 Dialog * dialog = dlg_p.second.get();
5411                 if (dialog) {
5412                         if (dialog->needBufferOpen() && !documentBufferView())
5413                                 hideDialog(fromqstr(dialog->name()), nullptr);
5414                         else if (dialog->isVisibleView())
5415                                 dialog->checkStatus();
5416                 }
5417         }
5418         updateToolbars();
5419         updateLayoutList();
5420 }
5421
5422
5423 Dialog * GuiView::build(string const & name)
5424 {
5425         return createDialog(*this, name);
5426 }
5427
5428
5429 SEMenu::SEMenu(QWidget * parent)
5430 {
5431         QAction * action = addAction(qt_("Disable Shell Escape"));
5432         connect(action, SIGNAL(triggered()),
5433                 parent, SLOT(disableShellEscape()));
5434 }
5435
5436
5437 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5438 {
5439         if (event->button() == Qt::LeftButton) {
5440         Q_EMIT pressed();
5441     }
5442 }
5443
5444 } // namespace frontend
5445 } // namespace lyx
5446
5447 #include "moc_GuiView.cpp"