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