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