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