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