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