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