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