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