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