]> git.lyx.org Git - features.git/blob - src/frontends/qt/GuiView.cpp
Fix use of BookmarkSection::size()
[features.git] / src / frontends / qt / GuiView.cpp
1 /**
2  * \file GuiView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
27 #include "GuiToc.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
32 #include "Menus.h"
33 #include "TocModel.h"
34
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
37
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
40
41 #include "buffer_funcs.h"
42 #include "Buffer.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
46 #include "Compare.h"
47 #include "Converter.h"
48 #include "Cursor.h"
49 #include "CutAndPaste.h"
50 #include "Encoding.h"
51 #include "ErrorList.h"
52 #include "Format.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
56 #include "Language.h"
57 #include "LayoutFile.h"
58 #include "Lexer.h"
59 #include "LyXAction.h"
60 #include "LyX.h"
61 #include "LyXRC.h"
62 #include "LyXVC.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
65 #include "Session.h"
66 #include "TexRow.h"
67 #include "Text.h"
68 #include "Toolbars.h"
69 #include "version.h"
70
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
85
86 #include <QAction>
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
90 #include <QDropEvent>
91 #include <QFuture>
92 #include <QFutureWatcher>
93 #include <QGroupBox>
94 #include <QLabel>
95 #include <QList>
96 #include <QMenu>
97 #include <QMenuBar>
98 #include <QMimeData>
99 #include <QMovie>
100 #include <QPainter>
101 #include <QPixmap>
102 #include <QPoint>
103 #include <QSettings>
104 #include <QShowEvent>
105 #include <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 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2720 {
2721         BufferView * bv = documentBufferView();
2722         if (!bv)
2723                 return;
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;
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;
2755                 }
2756         }
2757
2758         bv->insertLyXFile(filename, ignorelang);
2759         bv->buffer().errors("Parse");
2760 }
2761
2762
2763 string const GuiView::getTemplatesPath(Buffer & b)
2764 {
2765         // We start off with the user's templates path
2766         string result = addPath(package().user_support().absFileName(), "templates");
2767         // Check for the document language
2768         string const langcode = b.params().language->code();
2769         string const shortcode = langcode.substr(0, 2);
2770         if (!langcode.empty() && shortcode != "en") {
2771                 string subpath = addPath(result, shortcode);
2772                 string subpath_long = addPath(result, langcode);
2773                 // If we have a subdirectory for the language already,
2774                 // navigate there
2775                 FileName sp = FileName(subpath);
2776                 if (sp.isDirectory())
2777                         result = subpath;
2778                 else if (FileName(subpath_long).isDirectory())
2779                         result = subpath_long;
2780                 else {
2781                         // Ask whether we should create such a subdirectory
2782                         docstring const text =
2783                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2784                                           "appropriate to the document language (%1$s).\n"
2785                                           "This subdirectory does not exists yet.\n"
2786                                           "Do you want to create it?"),
2787                                         _(b.params().language->display()));
2788                         if (Alert::prompt(_("Create Language Directory?"),
2789                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2790                                 // If the user agreed, we try to create it and report if this failed.
2791                                 if (!sp.createDirectory(0777))
2792                                         Alert::error(_("Subdirectory creation failed!"),
2793                                                      _("Could not create subdirectory.\n"
2794                                                        "The template will be saved in the parent directory."));
2795                                 else
2796                                         result = subpath;
2797                         }
2798                 }
2799         }
2800         // Do we have a layout category?
2801         string const cat = b.params().baseClass() ?
2802                                 b.params().baseClass()->category()
2803                               : string();
2804         if (!cat.empty()) {
2805                 string subpath = addPath(result, cat);
2806                 // If we have a subdirectory for the category already,
2807                 // navigate there
2808                 FileName sp = FileName(subpath);
2809                 if (sp.isDirectory())
2810                         result = subpath;
2811                 else {
2812                         // Ask whether we should create such a subdirectory
2813                         docstring const text =
2814                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2815                                           "appropriate to the layout category (%1$s).\n"
2816                                           "This subdirectory does not exists yet.\n"
2817                                           "Do you want to create it?"),
2818                                         _(cat));
2819                         if (Alert::prompt(_("Create Category Directory?"),
2820                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2821                                 // If the user agreed, we try to create it and report if this failed.
2822                                 if (!sp.createDirectory(0777))
2823                                         Alert::error(_("Subdirectory creation failed!"),
2824                                                      _("Could not create subdirectory.\n"
2825                                                        "The template will be saved in the parent directory."));
2826                                 else
2827                                         result = subpath;
2828                         }
2829                 }
2830         }
2831         return result;
2832 }
2833
2834
2835 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2836 {
2837         FileName fname = b.fileName();
2838         FileName const oldname = fname;
2839         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2840
2841         if (!newname.empty()) {
2842                 // FIXME UNICODE
2843                 if (as_template)
2844                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2845                 else
2846                         fname = support::makeAbsPath(to_utf8(newname),
2847                                                      oldname.onlyPath().absFileName());
2848         } else {
2849                 // Switch to this Buffer.
2850                 setBuffer(&b);
2851
2852                 // No argument? Ask user through dialog.
2853                 // FIXME UNICODE
2854                 QString const title = as_template ? qt_("Choose a filename to save template as")
2855                                                   : qt_("Choose a filename to save document as");
2856                 FileDialog dlg(title);
2857                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2858                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2859
2860                 if (!isLyXFileName(fname.absFileName()))
2861                         fname.changeExtension(".lyx");
2862
2863                 string const path = as_template ?
2864                                         getTemplatesPath(b)
2865                                       : fname.onlyPath().absFileName();
2866                 FileDialog::Result result =
2867                         dlg.save(toqstr(path),
2868                                    QStringList(qt_("LyX Documents (*.lyx)")),
2869                                          toqstr(fname.onlyFileName()));
2870
2871                 if (result.first == FileDialog::Later)
2872                         return false;
2873
2874                 fname.set(fromqstr(result.second));
2875
2876                 if (fname.empty())
2877                         return false;
2878
2879                 if (!isLyXFileName(fname.absFileName()))
2880                         fname.changeExtension(".lyx");
2881         }
2882
2883         // fname is now the new Buffer location.
2884
2885         // if there is already a Buffer open with this name, we do not want
2886         // to have another one. (the second test makes sure we're not just
2887         // trying to overwrite ourselves, which is fine.)
2888         if (theBufferList().exists(fname) && fname != oldname
2889                   && theBufferList().getBuffer(fname) != &b) {
2890                 docstring const text =
2891                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
2892                             "Please close it before attempting to overwrite it.\n"
2893                             "Do you want to choose a new filename?"),
2894                                 from_utf8(fname.absFileName()));
2895                 int const ret = Alert::prompt(_("Chosen File Already Open"),
2896                         text, 0, 1, _("&Rename"), _("&Cancel"));
2897                 switch (ret) {
2898                 case 0: return renameBuffer(b, docstring(), kind);
2899                 case 1: return false;
2900                 }
2901                 //return false;
2902         }
2903
2904         bool const existsLocal = fname.exists();
2905         bool const existsInVC = LyXVC::fileInVC(fname);
2906         if (existsLocal || existsInVC) {
2907                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2908                 if (kind != LV_WRITE_AS && existsInVC) {
2909                         // renaming to a name that is already in VC
2910                         // would not work
2911                         docstring text = bformat(_("The document %1$s "
2912                                         "is already registered.\n\n"
2913                                         "Do you want to choose a new name?"),
2914                                 file);
2915                         docstring const title = (kind == LV_VC_RENAME) ?
2916                                 _("Rename document?") : _("Copy document?");
2917                         docstring const button = (kind == LV_VC_RENAME) ?
2918                                 _("&Rename") : _("&Copy");
2919                         int const ret = Alert::prompt(title, text, 0, 1,
2920                                 button, _("&Cancel"));
2921                         switch (ret) {
2922                         case 0: return renameBuffer(b, docstring(), kind);
2923                         case 1: return false;
2924                         }
2925                 }
2926
2927                 if (existsLocal) {
2928                         docstring text = bformat(_("The document %1$s "
2929                                         "already exists.\n\n"
2930                                         "Do you want to overwrite that document?"),
2931                                 file);
2932                         int const ret = Alert::prompt(_("Overwrite document?"),
2933                                         text, 0, 2, _("&Overwrite"),
2934                                         _("&Rename"), _("&Cancel"));
2935                         switch (ret) {
2936                         case 0: break;
2937                         case 1: return renameBuffer(b, docstring(), kind);
2938                         case 2: return false;
2939                         }
2940                 }
2941         }
2942
2943         switch (kind) {
2944         case LV_VC_RENAME: {
2945                 string msg = b.lyxvc().rename(fname);
2946                 if (msg.empty())
2947                         return false;
2948                 message(from_utf8(msg));
2949                 break;
2950         }
2951         case LV_VC_COPY: {
2952                 string msg = b.lyxvc().copy(fname);
2953                 if (msg.empty())
2954                         return false;
2955                 message(from_utf8(msg));
2956                 break;
2957         }
2958         case LV_WRITE_AS:
2959         case LV_WRITE_AS_TEMPLATE:
2960                 break;
2961         }
2962         // LyXVC created the file already in case of LV_VC_RENAME or
2963         // LV_VC_COPY, but call saveBuffer() nevertheless to get
2964         // relative paths of included stuff right if we moved e.g. from
2965         // /a/b.lyx to /a/c/b.lyx.
2966
2967         bool const saved = saveBuffer(b, fname);
2968         if (saved)
2969                 b.reload();
2970         return saved;
2971 }
2972
2973
2974 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2975 {
2976         FileName fname = b.fileName();
2977
2978         FileDialog dlg(qt_("Choose a filename to export the document as"));
2979         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2980
2981         QStringList types;
2982         QString const anyformat = qt_("Guess from extension (*.*)");
2983         types << anyformat;
2984
2985         vector<Format const *> export_formats;
2986         for (Format const & f : theFormats())
2987                 if (f.documentFormat())
2988                         export_formats.push_back(&f);
2989         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2990         map<QString, string> fmap;
2991         QString filter;
2992         string ext;
2993         for (Format const * f : export_formats) {
2994                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2995                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2996                                                      loc_prettyname,
2997                                                      from_ascii(f->extension())));
2998                 types << loc_filter;
2999                 fmap[loc_filter] = f->name();
3000                 if (from_ascii(f->name()) == iformat) {
3001                         filter = loc_filter;
3002                         ext = f->extension();
3003                 }
3004         }
3005         string ofname = fname.onlyFileName();
3006         if (!ext.empty())
3007                 ofname = support::changeExtension(ofname, ext);
3008         FileDialog::Result result =
3009                 dlg.save(toqstr(fname.onlyPath().absFileName()),
3010                          types,
3011                          toqstr(ofname),
3012                          &filter);
3013         if (result.first != FileDialog::Chosen)
3014                 return false;
3015
3016         string fmt_name;
3017         fname.set(fromqstr(result.second));
3018         if (filter == anyformat)
3019                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3020         else
3021                 fmt_name = fmap[filter];
3022         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3023                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3024
3025         if (fmt_name.empty() || fname.empty())
3026                 return false;
3027
3028         // fname is now the new Buffer location.
3029         if (FileName(fname).exists()) {
3030                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3031                 docstring text = bformat(_("The document %1$s already "
3032                                            "exists.\n\nDo you want to "
3033                                            "overwrite that document?"),
3034                                          file);
3035                 int const ret = Alert::prompt(_("Overwrite document?"),
3036                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3037                 switch (ret) {
3038                 case 0: break;
3039                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3040                 case 2: return false;
3041                 }
3042         }
3043
3044         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3045         DispatchResult dr;
3046         dispatch(cmd, dr);
3047         return dr.dispatched();
3048 }
3049
3050
3051 bool GuiView::saveBuffer(Buffer & b)
3052 {
3053         return saveBuffer(b, FileName());
3054 }
3055
3056
3057 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3058 {
3059         if (workArea(b) && workArea(b)->inDialogMode())
3060                 return true;
3061
3062         if (fn.empty() && b.isUnnamed())
3063                 return renameBuffer(b, docstring());
3064
3065         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3066         if (success) {
3067                 theSession().lastFiles().add(b.fileName());
3068                 theSession().writeFile();
3069                 return true;
3070         }
3071
3072         // Switch to this Buffer.
3073         setBuffer(&b);
3074
3075         // FIXME: we don't tell the user *WHY* the save failed !!
3076         docstring const file = makeDisplayPath(b.absFileName(), 30);
3077         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3078                                    "Do you want to rename the document and "
3079                                    "try again?"), file);
3080         int const ret = Alert::prompt(_("Rename and save?"),
3081                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3082         switch (ret) {
3083         case 0:
3084                 if (!renameBuffer(b, docstring()))
3085                         return false;
3086                 break;
3087         case 1:
3088                 break;
3089         case 2:
3090                 return false;
3091         }
3092
3093         return saveBuffer(b, fn);
3094 }
3095
3096
3097 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3098 {
3099         return closeWorkArea(wa, false);
3100 }
3101
3102
3103 // We only want to close the buffer if it is not visible in other workareas
3104 // of the same view, nor in other views, and if this is not a child
3105 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3106 {
3107         Buffer & buf = wa->bufferView().buffer();
3108
3109         bool last_wa = d.countWorkAreasOf(buf) == 1
3110                 && !inOtherView(buf) && !buf.parent();
3111
3112         bool close_buffer = last_wa;
3113
3114         if (last_wa) {
3115                 if (lyxrc.close_buffer_with_last_view == "yes")
3116                         ; // Nothing to do
3117                 else if (lyxrc.close_buffer_with_last_view == "no")
3118                         close_buffer = false;
3119                 else {
3120                         docstring file;
3121                         if (buf.isUnnamed())
3122                                 file = from_utf8(buf.fileName().onlyFileName());
3123                         else
3124                                 file = buf.fileName().displayName(30);
3125                         docstring const text = bformat(
3126                                 _("Last view on document %1$s is being closed.\n"
3127                                   "Would you like to close or hide the document?\n"
3128                                   "\n"
3129                                   "Hidden documents can be displayed back through\n"
3130                                   "the menu: View->Hidden->...\n"
3131                                   "\n"
3132                                   "To remove this question, set your preference in:\n"
3133                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3134                                 ), file);
3135                         int ret = Alert::prompt(_("Close or hide document?"),
3136                                 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3137                         if (ret == 2)
3138                                 return false;
3139                         close_buffer = (ret == 0);
3140                 }
3141         }
3142
3143         return closeWorkArea(wa, close_buffer);
3144 }
3145
3146
3147 bool GuiView::closeBuffer()
3148 {
3149         GuiWorkArea * wa = currentMainWorkArea();
3150         // coverity complained about this
3151         // it seems unnecessary, but perhaps is worth the check
3152         LASSERT(wa, return false);
3153
3154         setCurrentWorkArea(wa);
3155         Buffer & buf = wa->bufferView().buffer();
3156         return closeWorkArea(wa, !buf.parent());
3157 }
3158
3159
3160 void GuiView::writeSession() const {
3161         GuiWorkArea const * active_wa = currentMainWorkArea();
3162         for (int i = 0; i < d.splitter_->count(); ++i) {
3163                 TabWorkArea * twa = d.tabWorkArea(i);
3164                 for (int j = 0; j < twa->count(); ++j) {
3165                         GuiWorkArea * wa = twa->workArea(j);
3166                         Buffer & buf = wa->bufferView().buffer();
3167                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3168                 }
3169         }
3170 }
3171
3172
3173 bool GuiView::closeBufferAll()
3174 {
3175
3176         for (auto & buf : theBufferList()) {
3177                 if (!saveBufferIfNeeded(*buf, false)) {
3178                         // Closing has been cancelled, so abort.
3179                         return false;
3180                 }
3181         }
3182
3183         // Close the workareas in all other views
3184         QList<int> const ids = guiApp->viewIds();
3185         for (int i = 0; i != ids.size(); ++i) {
3186                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3187                         return false;
3188         }
3189
3190         // Close our own workareas
3191         if (!closeWorkAreaAll())
3192                 return false;
3193
3194         return true;
3195 }
3196
3197
3198 bool GuiView::closeWorkAreaAll()
3199 {
3200         setCurrentWorkArea(currentMainWorkArea());
3201
3202         // We might be in a situation that there is still a tabWorkArea, but
3203         // there are no tabs anymore. This can happen when we get here after a
3204         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3205         // many TabWorkArea's have no documents anymore.
3206         int empty_twa = 0;
3207
3208         // We have to call count() each time, because it can happen that
3209         // more than one splitter will disappear in one iteration (bug 5998).
3210         while (d.splitter_->count() > empty_twa) {
3211                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3212
3213                 if (twa->count() == 0)
3214                         ++empty_twa;
3215                 else {
3216                         setCurrentWorkArea(twa->currentWorkArea());
3217                         if (!closeTabWorkArea(twa))
3218                                 return false;
3219                 }
3220         }
3221         return true;
3222 }
3223
3224
3225 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3226 {
3227         if (!wa)
3228                 return false;
3229
3230         Buffer & buf = wa->bufferView().buffer();
3231
3232         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3233                 Alert::warning(_("Close document"),
3234                         _("Document could not be closed because it is being processed by LyX."));
3235                 return false;
3236         }
3237
3238         if (close_buffer)
3239                 return closeBuffer(buf);
3240         else {
3241                 if (!inMultiTabs(wa))
3242                         if (!saveBufferIfNeeded(buf, true))
3243                                 return false;
3244                 removeWorkArea(wa);
3245                 return true;
3246         }
3247 }
3248
3249
3250 bool GuiView::closeBuffer(Buffer & buf)
3251 {
3252         bool success = true;
3253         for (Buffer * child_buf : buf.getChildren()) {
3254                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3255                         child_buf->setParent(nullptr);
3256                         continue;
3257                 }
3258
3259                 // FIXME: should we look in other tabworkareas?
3260                 // ANSWER: I don't think so. I've tested, and if the child is
3261                 // open in some other window, it closes without a problem.
3262                 GuiWorkArea * child_wa = workArea(*child_buf);
3263                 if (child_wa) {
3264                         if (closing_)
3265                                 // If we are in a close_event all children will be closed in some time,
3266                                 // so no need to do it here. This will ensure that the children end up
3267                                 // in the session file in the correct order. If we close the master
3268                                 // buffer, we can close or release the child buffers here too.
3269                                 continue;
3270                         success = closeWorkArea(child_wa, true);
3271                         if (!success)
3272                                 break;
3273                 } else {
3274                         // In this case the child buffer is open but hidden.
3275                         // Even in this case, children can be dirty (e.g.,
3276                         // after a label change in the master, see #11405).
3277                         // Therefore, check this
3278                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3279                                 // If we are in a close_event all children will be closed in some time,
3280                                 // so no need to do it here. This will ensure that the children end up
3281                                 // in the session file in the correct order. If we close the master
3282                                 // buffer, we can close or release the child buffers here too.
3283                                 continue;
3284                         }
3285                         // Save dirty buffers also if closing_!
3286                         if (saveBufferIfNeeded(*child_buf, false)) {
3287                                 child_buf->removeAutosaveFile();
3288                                 theBufferList().release(child_buf);
3289                         } else {
3290                                 // Saving of dirty children has been cancelled.
3291                                 // Cancel the whole process.
3292                                 success = false;
3293                                 break;
3294                         }
3295                 }
3296         }
3297         if (success) {
3298                 // goto bookmark to update bookmark pit.
3299                 // FIXME: we should update only the bookmarks related to this buffer!
3300                 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3301                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3302                 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3303                         guiApp->gotoBookmark(i, false, false);
3304
3305                 if (saveBufferIfNeeded(buf, false)) {
3306                         buf.removeAutosaveFile();
3307                         theBufferList().release(&buf);
3308                         return true;
3309                 }
3310         }
3311         // open all children again to avoid a crash because of dangling
3312         // pointers (bug 6603)
3313         buf.updateBuffer();
3314         return false;
3315 }
3316
3317
3318 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3319 {
3320         while (twa == d.currentTabWorkArea()) {
3321                 twa->setCurrentIndex(twa->count() - 1);
3322
3323                 GuiWorkArea * wa = twa->currentWorkArea();
3324                 Buffer & b = wa->bufferView().buffer();
3325
3326                 // We only want to close the buffer if the same buffer is not visible
3327                 // in another view, and if this is not a child and if we are closing
3328                 // a view (not a tabgroup).
3329                 bool const close_buffer =
3330                         !inOtherView(b) && !b.parent() && closing_;
3331
3332                 if (!closeWorkArea(wa, close_buffer))
3333                         return false;
3334         }
3335         return true;
3336 }
3337
3338
3339 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3340 {
3341         if (buf.isClean() || buf.paragraphs().empty())
3342                 return true;
3343
3344         // Switch to this Buffer.
3345         setBuffer(&buf);
3346
3347         docstring file;
3348         bool exists;
3349         // FIXME: Unicode?
3350         if (buf.isUnnamed()) {
3351                 file = from_utf8(buf.fileName().onlyFileName());
3352                 exists = false;
3353         } else {
3354                 FileName filename = buf.fileName();
3355                 filename.refresh();
3356                 file = filename.displayName(30);
3357                 exists = filename.exists();
3358         }
3359
3360         // Bring this window to top before asking questions.
3361         raise();
3362         activateWindow();
3363
3364         int ret;
3365         if (hiding && buf.isUnnamed()) {
3366                 docstring const text = bformat(_("The document %1$s has not been "
3367                                                  "saved yet.\n\nDo you want to save "
3368                                                  "the document?"), file);
3369                 ret = Alert::prompt(_("Save new document?"),
3370                         text, 0, 1, _("&Save"), _("&Cancel"));
3371                 if (ret == 1)
3372                         ++ret;
3373         } else {
3374                 docstring const text = exists ?
3375                         bformat(_("The document %1$s has unsaved changes."
3376                                   "\n\nDo you want to save the document or "
3377                                   "discard the changes?"), file) :
3378                         bformat(_("The document %1$s has not been saved yet."
3379                                   "\n\nDo you want to save the document or "
3380                                   "discard it entirely?"), file);
3381                 docstring const title = exists ?
3382                         _("Save changed document?") : _("Save document?");
3383                 ret = Alert::prompt(title, text, 0, 2,
3384                                     _("&Save"), _("&Discard"), _("&Cancel"));
3385         }
3386
3387         switch (ret) {
3388         case 0:
3389                 if (!saveBuffer(buf))
3390                         return false;
3391                 break;
3392         case 1:
3393                 // If we crash after this we could have no autosave file
3394                 // but I guess this is really improbable (Jug).
3395                 // Sometimes improbable things happen:
3396                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3397                 // buf.removeAutosaveFile();
3398                 if (hiding)
3399                         // revert all changes
3400                         reloadBuffer(buf);
3401                 buf.markClean();
3402                 break;
3403         case 2:
3404                 return false;
3405         }
3406         return true;
3407 }
3408
3409
3410 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3411 {
3412         Buffer & buf = wa->bufferView().buffer();
3413
3414         for (int i = 0; i != d.splitter_->count(); ++i) {
3415                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3416                 if (wa_ && wa_ != wa)
3417                         return true;
3418         }
3419         return inOtherView(buf);
3420 }
3421
3422
3423 bool GuiView::inOtherView(Buffer & buf)
3424 {
3425         QList<int> const ids = guiApp->viewIds();
3426
3427         for (int i = 0; i != ids.size(); ++i) {
3428                 if (id_ == ids[i])
3429                         continue;
3430
3431                 if (guiApp->view(ids[i]).workArea(buf))
3432                         return true;
3433         }
3434         return false;
3435 }
3436
3437
3438 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3439 {
3440         if (!documentBufferView())
3441                 return;
3442
3443         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3444                 Buffer * const curbuf = &documentBufferView()->buffer();
3445                 int nwa = twa->count();
3446                 for (int i = 0; i < nwa; ++i) {
3447                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3448                                 int next_index;
3449                                 if (np == NEXTBUFFER)
3450                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3451                                 else
3452                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3453                                 if (move)
3454                                         twa->moveTab(i, next_index);
3455                                 else
3456                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3457                                 break;
3458                         }
3459                 }
3460         }
3461 }
3462
3463
3464 /// make sure the document is saved
3465 static bool ensureBufferClean(Buffer * buffer)
3466 {
3467         LASSERT(buffer, return false);
3468         if (buffer->isClean() && !buffer->isUnnamed())
3469                 return true;
3470
3471         docstring const file = buffer->fileName().displayName(30);
3472         docstring title;
3473         docstring text;
3474         if (!buffer->isUnnamed()) {
3475                 text = bformat(_("The document %1$s has unsaved "
3476                                                  "changes.\n\nDo you want to save "
3477                                                  "the document?"), file);
3478                 title = _("Save changed document?");
3479
3480         } else {
3481                 text = bformat(_("The document %1$s has not been "
3482                                                  "saved yet.\n\nDo you want to save "
3483                                                  "the document?"), file);
3484                 title = _("Save new document?");
3485         }
3486         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3487
3488         if (ret == 0)
3489                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3490
3491         return buffer->isClean() && !buffer->isUnnamed();
3492 }
3493
3494
3495 bool GuiView::reloadBuffer(Buffer & buf)
3496 {
3497         currentBufferView()->cursor().reset();
3498         Buffer::ReadStatus status = buf.reload();
3499         return status == Buffer::ReadSuccess;
3500 }
3501
3502
3503 void GuiView::checkExternallyModifiedBuffers()
3504 {
3505         for (Buffer * buf : theBufferList()) {
3506                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3507                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3508                                         " Reload now? Any local changes will be lost."),
3509                                         from_utf8(buf->absFileName()));
3510                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3511                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3512                         if (!ret)
3513                                 reloadBuffer(*buf);
3514                 }
3515         }
3516 }
3517
3518
3519 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3520 {
3521         Buffer * buffer = documentBufferView()
3522                 ? &(documentBufferView()->buffer()) : nullptr;
3523
3524         switch (cmd.action()) {
3525         case LFUN_VC_REGISTER:
3526                 if (!buffer || !ensureBufferClean(buffer))
3527                         break;
3528                 if (!buffer->lyxvc().inUse()) {
3529                         if (buffer->lyxvc().registrer()) {
3530                                 reloadBuffer(*buffer);
3531                                 dr.clearMessageUpdate();
3532                         }
3533                 }
3534                 break;
3535
3536         case LFUN_VC_RENAME:
3537         case LFUN_VC_COPY: {
3538                 if (!buffer || !ensureBufferClean(buffer))
3539                         break;
3540                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3541                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3542                                 // Some changes are not yet committed.
3543                                 // We test here and not in getStatus(), since
3544                                 // this test is expensive.
3545                                 string log;
3546                                 LyXVC::CommandResult ret =
3547                                         buffer->lyxvc().checkIn(log);
3548                                 dr.setMessage(log);
3549                                 if (ret == LyXVC::ErrorCommand ||
3550                                     ret == LyXVC::VCSuccess)
3551                                         reloadBuffer(*buffer);
3552                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3553                                         frontend::Alert::error(
3554                                                 _("Revision control error."),
3555                                                 _("Document could not be checked in."));
3556                                         break;
3557                                 }
3558                         }
3559                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3560                                 LV_VC_RENAME : LV_VC_COPY;
3561                         renameBuffer(*buffer, cmd.argument(), kind);
3562                 }
3563                 break;
3564         }
3565
3566         case LFUN_VC_CHECK_IN:
3567                 if (!buffer || !ensureBufferClean(buffer))
3568                         break;
3569                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3570                         string log;
3571                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3572                         dr.setMessage(log);
3573                         // Only skip reloading if the checkin was cancelled or
3574                         // an error occurred before the real checkin VCS command
3575                         // was executed, since the VCS might have changed the
3576                         // file even if it could not checkin successfully.
3577                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3578                                 reloadBuffer(*buffer);
3579                 }
3580                 break;
3581
3582         case LFUN_VC_CHECK_OUT:
3583                 if (!buffer || !ensureBufferClean(buffer))
3584                         break;
3585                 if (buffer->lyxvc().inUse()) {
3586                         dr.setMessage(buffer->lyxvc().checkOut());
3587                         reloadBuffer(*buffer);
3588                 }
3589                 break;
3590
3591         case LFUN_VC_LOCKING_TOGGLE:
3592                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3593                         break;
3594                 if (buffer->lyxvc().inUse()) {
3595                         string res = buffer->lyxvc().lockingToggle();
3596                         if (res.empty()) {
3597                                 frontend::Alert::error(_("Revision control error."),
3598                                 _("Error when setting the locking property."));
3599                         } else {
3600                                 dr.setMessage(res);
3601                                 reloadBuffer(*buffer);
3602                         }
3603                 }
3604                 break;
3605
3606         case LFUN_VC_REVERT:
3607                 if (!buffer)
3608                         break;
3609                 if (buffer->lyxvc().revert()) {
3610                         reloadBuffer(*buffer);
3611                         dr.clearMessageUpdate();
3612                 }
3613                 break;
3614
3615         case LFUN_VC_UNDO_LAST:
3616                 if (!buffer)
3617                         break;
3618                 buffer->lyxvc().undoLast();
3619                 reloadBuffer(*buffer);
3620                 dr.clearMessageUpdate();
3621                 break;
3622
3623         case LFUN_VC_REPO_UPDATE:
3624                 if (!buffer)
3625                         break;
3626                 if (ensureBufferClean(buffer)) {
3627                         dr.setMessage(buffer->lyxvc().repoUpdate());
3628                         checkExternallyModifiedBuffers();
3629                 }
3630                 break;
3631
3632         case LFUN_VC_COMMAND: {
3633                 string flag = cmd.getArg(0);
3634                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3635                         break;
3636                 docstring message;
3637                 if (contains(flag, 'M')) {
3638                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3639                                 break;
3640                 }
3641                 string path = cmd.getArg(1);
3642                 if (contains(path, "$$p") && buffer)
3643                         path = subst(path, "$$p", buffer->filePath());
3644                 LYXERR(Debug::LYXVC, "Directory: " << path);
3645                 FileName pp(path);
3646                 if (!pp.isReadableDirectory()) {
3647                         lyxerr << _("Directory is not accessible.") << endl;
3648                         break;
3649                 }
3650                 support::PathChanger p(pp);
3651
3652                 string command = cmd.getArg(2);
3653                 if (command.empty())
3654                         break;
3655                 if (buffer) {
3656                         command = subst(command, "$$i", buffer->absFileName());
3657                         command = subst(command, "$$p", buffer->filePath());
3658                 }
3659                 command = subst(command, "$$m", to_utf8(message));
3660                 LYXERR(Debug::LYXVC, "Command: " << command);
3661                 Systemcall one;
3662                 one.startscript(Systemcall::Wait, command);
3663
3664                 if (!buffer)
3665                         break;
3666                 if (contains(flag, 'I'))
3667                         buffer->markDirty();
3668                 if (contains(flag, 'R'))
3669                         reloadBuffer(*buffer);
3670
3671                 break;
3672                 }
3673
3674         case LFUN_VC_COMPARE: {
3675                 if (cmd.argument().empty()) {
3676                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3677                         break;
3678                 }
3679                 if (!buffer)
3680                         break;
3681
3682                 string rev1 = cmd.getArg(0);
3683                 string f1, f2;
3684
3685                 // f1
3686                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3687                         break;
3688
3689                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3690                         f2 = buffer->absFileName();
3691                 } else {
3692                         string rev2 = cmd.getArg(1);
3693                         if (rev2.empty())
3694                                 break;
3695                         // f2
3696                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3697                                 break;
3698                 }
3699
3700                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3701                                         f1 << "\n"  << f2 << "\n" );
3702                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3703                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3704                 break;
3705         }
3706
3707         default:
3708                 break;
3709         }
3710 }
3711
3712
3713 void GuiView::openChildDocument(string const & fname)
3714 {
3715         LASSERT(documentBufferView(), return);
3716         Buffer & buffer = documentBufferView()->buffer();
3717         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3718         documentBufferView()->saveBookmark(false);
3719         Buffer * child = nullptr;
3720         if (theBufferList().exists(filename)) {
3721                 child = theBufferList().getBuffer(filename);
3722                 setBuffer(child);
3723         } else {
3724                 message(bformat(_("Opening child document %1$s..."),
3725                         makeDisplayPath(filename.absFileName())));
3726                 child = loadDocument(filename, false);
3727         }
3728         // Set the parent name of the child document.
3729         // This makes insertion of citations and references in the child work,
3730         // when the target is in the parent or another child document.
3731         if (child)
3732                 child->setParent(&buffer);
3733 }
3734
3735
3736 bool GuiView::goToFileRow(string const & argument)
3737 {
3738         string file_name;
3739         int row = -1;
3740         size_t i = argument.find_last_of(' ');
3741         if (i != string::npos) {
3742                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3743                 istringstream is(argument.substr(i + 1));
3744                 is >> row;
3745                 if (is.fail())
3746                         i = string::npos;
3747         }
3748         if (i == string::npos) {
3749                 LYXERR0("Wrong argument: " << argument);
3750                 return false;
3751         }
3752         Buffer * buf = nullptr;
3753         string const realtmp = package().temp_dir().realPath();
3754         // We have to use os::path_prefix_is() here, instead of
3755         // simply prefixIs(), because the file name comes from
3756         // an external application and may need case adjustment.
3757         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3758                 buf = theBufferList().getBufferFromTmp(file_name, true);
3759                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3760                            << (buf ? " success" : " failed"));
3761         } else {
3762                 // Must replace extension of the file to be .lyx
3763                 // and get full path
3764                 FileName const s = fileSearch(string(),
3765                                                   support::changeExtension(file_name, ".lyx"), "lyx");
3766                 // Either change buffer or load the file
3767                 if (theBufferList().exists(s))
3768                         buf = theBufferList().getBuffer(s);
3769                 else if (s.exists()) {
3770                         buf = loadDocument(s);
3771                         if (!buf)
3772                                 return false;
3773                 } else {
3774                         message(bformat(
3775                                         _("File does not exist: %1$s"),
3776                                         makeDisplayPath(file_name)));
3777                         return false;
3778                 }
3779         }
3780         if (!buf) {
3781                 message(bformat(
3782                         _("No buffer for file: %1$s."),
3783                         makeDisplayPath(file_name))
3784                 );
3785                 return false;
3786         }
3787         setBuffer(buf);
3788         bool success = documentBufferView()->setCursorFromRow(row);
3789         if (!success) {
3790                 LYXERR(Debug::LATEX,
3791                        "setCursorFromRow: invalid position for row " << row);
3792                 frontend::Alert::error(_("Inverse Search Failed"),
3793                                        _("Invalid position requested by inverse search.\n"
3794                                          "You may need to update the viewed document."));
3795         }
3796         return success;
3797 }
3798
3799
3800 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3801 {
3802         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3803         menu->exec(QCursor::pos());
3804 }
3805
3806
3807 template<class T>
3808 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3809                 Buffer const * orig, Buffer * clone, string const & format)
3810 {
3811         Buffer::ExportStatus const status = func(format);
3812
3813         // the cloning operation will have produced a clone of the entire set of
3814         // documents, starting from the master. so we must delete those.
3815         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3816         delete mbuf;
3817         busyBuffers.remove(orig);
3818         return status;
3819 }
3820
3821
3822 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3823                 Buffer const * orig, Buffer * clone, string const & format)
3824 {
3825         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3826                         &Buffer::doExport;
3827         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3828 }
3829
3830
3831 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3832                 Buffer const * orig, Buffer * clone, string const & format)
3833 {
3834         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3835                         &Buffer::doExport;
3836         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3837 }
3838
3839
3840 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3841                 Buffer const * orig, Buffer * clone, string const & format)
3842 {
3843         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3844                         &Buffer::preview;
3845         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3846 }
3847
3848
3849 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3850                            Buffer const * used_buffer,
3851                            docstring const & msg,
3852                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3853                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3854                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3855                            bool allow_async, bool use_tmpdir)
3856 {
3857         if (!used_buffer)
3858                 return false;
3859
3860         string format = argument;
3861         if (format.empty())
3862                 format = used_buffer->params().getDefaultOutputFormat();
3863         processing_format = format;
3864         if (!msg.empty()) {
3865                 progress_->clearMessages();
3866                 gv_->message(msg);
3867         }
3868 #if EXPORT_in_THREAD
3869         if (allow_async) {
3870                 GuiViewPrivate::busyBuffers.insert(used_buffer);
3871                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3872                 if (!cloned_buffer) {
3873                         Alert::error(_("Export Error"),
3874                                      _("Error cloning the Buffer."));
3875                         return false;
3876                 }
3877                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3878                                         asyncFunc,
3879                                         used_buffer,
3880                                         cloned_buffer,
3881                                         format);
3882                 setPreviewFuture(f);
3883                 last_export_format = used_buffer->params().bufferFormat();
3884                 (void) syncFunc;
3885                 (void) previewFunc;
3886                 // We are asynchronous, so we don't know here anything about the success
3887                 return true;
3888         } else {
3889                 Buffer::ExportStatus status;
3890                 if (syncFunc) {
3891                         status = (used_buffer->*syncFunc)(format, use_tmpdir);
3892                 } else if (previewFunc) {
3893                         status = (used_buffer->*previewFunc)(format);
3894                 } else
3895                         return false;
3896                 handleExportStatus(gv_, status, format);
3897                 (void) asyncFunc;
3898                 return (status == Buffer::ExportSuccess
3899                                 || status == Buffer::PreviewSuccess);
3900         }
3901 #else
3902         (void) allow_async;
3903         Buffer::ExportStatus status;
3904         if (syncFunc) {
3905                 status = (used_buffer->*syncFunc)(format, true);
3906         } else if (previewFunc) {
3907                 status = (used_buffer->*previewFunc)(format);
3908         } else
3909                 return false;
3910         handleExportStatus(gv_, status, format);
3911         (void) asyncFunc;
3912         return (status == Buffer::ExportSuccess
3913                         || status == Buffer::PreviewSuccess);
3914 #endif
3915 }
3916
3917 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3918 {
3919         BufferView * bv = currentBufferView();
3920         LASSERT(bv, return);
3921
3922         // Let the current BufferView dispatch its own actions.
3923         bv->dispatch(cmd, dr);
3924         if (dr.dispatched()) {
3925                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3926                         updateDialog("document", "");
3927                 return;
3928         }
3929
3930         // Try with the document BufferView dispatch if any.
3931         BufferView * doc_bv = documentBufferView();
3932         if (doc_bv && doc_bv != bv) {
3933                 doc_bv->dispatch(cmd, dr);
3934                 if (dr.dispatched()) {
3935                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3936                                 updateDialog("document", "");
3937                         return;
3938                 }
3939         }
3940
3941         // Then let the current Cursor dispatch its own actions.
3942         bv->cursor().dispatch(cmd);
3943
3944         // update completion. We do it here and not in
3945         // processKeySym to avoid another redraw just for a
3946         // changed inline completion
3947         if (cmd.origin() == FuncRequest::KEYBOARD) {
3948                 if (cmd.action() == LFUN_SELF_INSERT
3949                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3950                         updateCompletion(bv->cursor(), true, true);
3951                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3952                         updateCompletion(bv->cursor(), false, true);
3953                 else
3954                         updateCompletion(bv->cursor(), false, false);
3955         }
3956
3957         dr = bv->cursor().result();
3958 }
3959
3960
3961 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3962 {
3963         BufferView * bv = currentBufferView();
3964         // By default we won't need any update.
3965         dr.screenUpdate(Update::None);
3966         // assume cmd will be dispatched
3967         dr.dispatched(true);
3968
3969         Buffer * doc_buffer = documentBufferView()
3970                 ? &(documentBufferView()->buffer()) : nullptr;
3971
3972         if (cmd.origin() == FuncRequest::TOC) {
3973                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3974                 toc->doDispatch(bv->cursor(), cmd, dr);
3975                 return;
3976         }
3977
3978         string const argument = to_utf8(cmd.argument());
3979
3980         switch(cmd.action()) {
3981                 case LFUN_BUFFER_CHILD_OPEN:
3982                         openChildDocument(to_utf8(cmd.argument()));
3983                         break;
3984
3985                 case LFUN_BUFFER_IMPORT:
3986                         importDocument(to_utf8(cmd.argument()));
3987                         break;
3988
3989                 case LFUN_MASTER_BUFFER_EXPORT:
3990                         if (doc_buffer)
3991                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3992                         // fall through
3993                 case LFUN_BUFFER_EXPORT: {
3994                         if (!doc_buffer)
3995                                 break;
3996                         // GCC only sees strfwd.h when building merged
3997                         if (::lyx::operator==(cmd.argument(), "custom")) {
3998                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3999                                 // so the following test should not be needed.
4000                                 // In principle, we could try to switch to such a view...
4001                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4002                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4003                                 break;
4004                         }
4005
4006                         string const dest = cmd.getArg(1);
4007                         FileName target_dir;
4008                         if (!dest.empty() && FileName::isAbsolute(dest))
4009                                 target_dir = FileName(support::onlyPath(dest));
4010                         else
4011                                 target_dir = doc_buffer->fileName().onlyPath();
4012
4013                         string const format = (argument.empty() || argument == "default") ?
4014                                 doc_buffer->params().getDefaultOutputFormat() : argument;
4015
4016                         if ((dest.empty() && doc_buffer->isUnnamed())
4017                             || !target_dir.isDirWritable()) {
4018                                 exportBufferAs(*doc_buffer, from_utf8(format));
4019                                 break;
4020                         }
4021                         /* TODO/Review: Is it a problem to also export the children?
4022                                         See the update_unincluded flag */
4023                         d.asyncBufferProcessing(format,
4024                                                 doc_buffer,
4025                                                 _("Exporting ..."),
4026                                                 &GuiViewPrivate::exportAndDestroy,
4027                                                 &Buffer::doExport,
4028                                                 nullptr, cmd.allowAsync());
4029                         // TODO Inform user about success
4030                         break;
4031                 }
4032
4033                 case LFUN_BUFFER_EXPORT_AS: {
4034                         LASSERT(doc_buffer, break);
4035                         docstring f = cmd.argument();
4036                         if (f.empty())
4037                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4038                         exportBufferAs(*doc_buffer, f);
4039                         break;
4040                 }
4041
4042                 case LFUN_BUFFER_UPDATE: {
4043                         d.asyncBufferProcessing(argument,
4044                                                 doc_buffer,
4045                                                 _("Exporting ..."),
4046                                                 &GuiViewPrivate::compileAndDestroy,
4047                                                 &Buffer::doExport,
4048                                                 nullptr, cmd.allowAsync(), true);
4049                         break;
4050                 }
4051                 case LFUN_BUFFER_VIEW: {
4052                         d.asyncBufferProcessing(argument,
4053                                                 doc_buffer,
4054                                                 _("Previewing ..."),
4055                                                 &GuiViewPrivate::previewAndDestroy,
4056                                                 nullptr,
4057                                                 &Buffer::preview, cmd.allowAsync());
4058                         break;
4059                 }
4060                 case LFUN_MASTER_BUFFER_UPDATE: {
4061                         d.asyncBufferProcessing(argument,
4062                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4063                                                 docstring(),
4064                                                 &GuiViewPrivate::compileAndDestroy,
4065                                                 &Buffer::doExport,
4066                                                 nullptr, cmd.allowAsync(), true);
4067                         break;
4068                 }
4069                 case LFUN_MASTER_BUFFER_VIEW: {
4070                         d.asyncBufferProcessing(argument,
4071                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4072                                                 docstring(),
4073                                                 &GuiViewPrivate::previewAndDestroy,
4074                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4075                         break;
4076                 }
4077                 case LFUN_EXPORT_CANCEL: {
4078                         Systemcall::killscript();
4079                         break;
4080                 }
4081                 case LFUN_BUFFER_SWITCH: {
4082                         string const file_name = to_utf8(cmd.argument());
4083                         if (!FileName::isAbsolute(file_name)) {
4084                                 dr.setError(true);
4085                                 dr.setMessage(_("Absolute filename expected."));
4086                                 break;
4087                         }
4088
4089                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4090                         if (!buffer) {
4091                                 dr.setError(true);
4092                                 dr.setMessage(_("Document not loaded"));
4093                                 break;
4094                         }
4095
4096                         // Do we open or switch to the buffer in this view ?
4097                         if (workArea(*buffer)
4098                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4099                                 setBuffer(buffer);
4100                                 break;
4101                         }
4102
4103                         // Look for the buffer in other views
4104                         QList<int> const ids = guiApp->viewIds();
4105                         int i = 0;
4106                         for (; i != ids.size(); ++i) {
4107                                 GuiView & gv = guiApp->view(ids[i]);
4108                                 if (gv.workArea(*buffer)) {
4109                                         gv.raise();
4110                                         gv.activateWindow();
4111                                         gv.setFocus();
4112                                         gv.setBuffer(buffer);
4113                                         break;
4114                                 }
4115                         }
4116
4117                         // If necessary, open a new window as a last resort
4118                         if (i == ids.size()) {
4119                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4120                                 lyx::dispatch(cmd);
4121                         }
4122                         break;
4123                 }
4124
4125                 case LFUN_BUFFER_NEXT:
4126                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4127                         break;
4128
4129                 case LFUN_BUFFER_MOVE_NEXT:
4130                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4131                         break;
4132
4133                 case LFUN_BUFFER_PREVIOUS:
4134                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
4135                         break;
4136
4137                 case LFUN_BUFFER_MOVE_PREVIOUS:
4138                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
4139                         break;
4140
4141                 case LFUN_BUFFER_CHKTEX:
4142                         LASSERT(doc_buffer, break);
4143                         doc_buffer->runChktex();
4144                         break;
4145
4146                 case LFUN_COMMAND_EXECUTE: {
4147                         command_execute_ = true;
4148                         minibuffer_focus_ = true;
4149                         break;
4150                 }
4151                 case LFUN_DROP_LAYOUTS_CHOICE:
4152                         d.layout_->showPopup();
4153                         break;
4154
4155                 case LFUN_MENU_OPEN:
4156                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4157                                 menu->exec(QCursor::pos());
4158                         break;
4159
4160                 case LFUN_FILE_INSERT: {
4161                         if (cmd.getArg(1) == "ignorelang")
4162                                 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4163                         else
4164                                 insertLyXFile(cmd.argument());
4165                         break;
4166                 }
4167
4168                 case LFUN_FILE_INSERT_PLAINTEXT:
4169                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4170                         string const fname = to_utf8(cmd.argument());
4171                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4172                                 dr.setMessage(_("Absolute filename expected."));
4173                                 break;
4174                         }
4175
4176                         FileName filename(fname);
4177                         if (fname.empty()) {
4178                                 FileDialog dlg(qt_("Select file to insert"));
4179
4180                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4181                                         QStringList(qt_("All Files (*)")));
4182
4183                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4184                                         dr.setMessage(_("Canceled."));
4185                                         break;
4186                                 }
4187
4188                                 filename.set(fromqstr(result.second));
4189                         }
4190
4191                         if (bv) {
4192                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4193                                 bv->dispatch(new_cmd, dr);
4194                         }
4195                         break;
4196                 }
4197
4198                 case LFUN_BUFFER_RELOAD: {
4199                         LASSERT(doc_buffer, break);
4200
4201                         // drop changes?
4202                         bool drop = (cmd.argument() == "dump");
4203
4204                         int ret = 0;
4205                         if (!drop && !doc_buffer->isClean()) {
4206                                 docstring const file =
4207                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4208                                 if (doc_buffer->notifiesExternalModification()) {
4209                                         docstring text = _("The current version will be lost. "
4210                                             "Are you sure you want to load the version on disk "
4211                                             "of the document %1$s?");
4212                                         ret = Alert::prompt(_("Reload saved document?"),
4213                                                             bformat(text, file), 1, 1,
4214                                                             _("&Reload"), _("&Cancel"));
4215                                 } else {
4216                                         docstring text = _("Any changes will be lost. "
4217                                             "Are you sure you want to revert to the saved version "
4218                                             "of the document %1$s?");
4219                                         ret = Alert::prompt(_("Revert to saved document?"),
4220                                                             bformat(text, file), 1, 1,
4221                                                             _("&Revert"), _("&Cancel"));
4222                                 }
4223                         }
4224
4225                         if (ret == 0) {
4226                                 doc_buffer->markClean();
4227                                 reloadBuffer(*doc_buffer);
4228                                 dr.forceBufferUpdate();
4229                         }
4230                         break;
4231                 }
4232
4233                 case LFUN_BUFFER_RESET_EXPORT:
4234                         LASSERT(doc_buffer, break);
4235                         doc_buffer->requireFreshStart(true);
4236                         dr.setMessage(_("Buffer export reset."));
4237                         break;
4238
4239                 case LFUN_BUFFER_WRITE:
4240                         LASSERT(doc_buffer, break);
4241                         saveBuffer(*doc_buffer);
4242                         break;
4243
4244                 case LFUN_BUFFER_WRITE_AS:
4245                         LASSERT(doc_buffer, break);
4246                         renameBuffer(*doc_buffer, cmd.argument());
4247                         break;
4248
4249                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4250                         LASSERT(doc_buffer, break);
4251                         renameBuffer(*doc_buffer, cmd.argument(),
4252                                      LV_WRITE_AS_TEMPLATE);
4253                         break;
4254
4255                 case LFUN_BUFFER_WRITE_ALL: {
4256                         Buffer * first = theBufferList().first();
4257                         if (!first)
4258                                 break;
4259                         message(_("Saving all documents..."));
4260                         // We cannot use a for loop as the buffer list cycles.
4261                         Buffer * b = first;
4262                         do {
4263                                 if (!b->isClean()) {
4264                                         saveBuffer(*b);
4265                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4266                                 }
4267                                 b = theBufferList().next(b);
4268                         } while (b != first);
4269                         dr.setMessage(_("All documents saved."));
4270                         break;
4271                 }
4272
4273                 case LFUN_MASTER_BUFFER_FORALL: {
4274                         if (!doc_buffer)
4275                                 break;
4276
4277                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4278                         funcToRun.allowAsync(false);
4279
4280                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4281                                 // Switch to other buffer view and resend cmd
4282                                 lyx::dispatch(FuncRequest(
4283                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4284                                 lyx::dispatch(funcToRun);
4285                         }
4286                         // switch back
4287                         lyx::dispatch(FuncRequest(
4288                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4289                         break;
4290                 }
4291
4292                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4293                         LASSERT(doc_buffer, break);
4294                         doc_buffer->clearExternalModification();
4295                         break;
4296
4297                 case LFUN_BUFFER_CLOSE:
4298                         closeBuffer();
4299                         break;
4300
4301                 case LFUN_BUFFER_CLOSE_ALL:
4302                         closeBufferAll();
4303                         break;
4304
4305                 case LFUN_DEVEL_MODE_TOGGLE:
4306                         devel_mode_ = !devel_mode_;
4307                         if (devel_mode_)
4308                                 dr.setMessage(_("Developer mode is now enabled."));
4309                         else
4310                                 dr.setMessage(_("Developer mode is now disabled."));
4311                         break;
4312
4313                 case LFUN_TOOLBAR_SET: {
4314                         string const name = cmd.getArg(0);
4315                         string const state = cmd.getArg(1);
4316                         if (GuiToolbar * t = toolbar(name))
4317                                 t->setState(state);
4318                         break;
4319                 }
4320
4321                 case LFUN_TOOLBAR_TOGGLE: {
4322                         string const name = cmd.getArg(0);
4323                         if (GuiToolbar * t = toolbar(name))
4324                                 t->toggle();
4325                         break;
4326                 }
4327
4328                 case LFUN_TOOLBAR_MOVABLE: {
4329                         string const name = cmd.getArg(0);
4330                         if (name == "*") {
4331                                 // toggle (all) toolbars movablility
4332                                 toolbarsMovable_ = !toolbarsMovable_;
4333                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4334                                         GuiToolbar * tb = toolbar(ti.name);
4335                                         if (tb && tb->isMovable() != toolbarsMovable_)
4336                                                 // toggle toolbar movablity if it does not fit lock
4337                                                 // (all) toolbars positions state silent = true, since
4338                                                 // status bar notifications are slow
4339                                                 tb->movable(true);
4340                                 }
4341                                 if (toolbarsMovable_)
4342                                         dr.setMessage(_("Toolbars unlocked."));
4343                                 else
4344                                         dr.setMessage(_("Toolbars locked."));
4345                         } else if (GuiToolbar * t = toolbar(name)) {
4346                                 // toggle current toolbar movablity
4347                                 t->movable();
4348                                 // update lock (all) toolbars positions
4349                                 updateLockToolbars();
4350                         }
4351                         break;
4352                 }
4353
4354                 case LFUN_ICON_SIZE: {
4355                         QSize size = d.iconSize(cmd.argument());
4356                         setIconSize(size);
4357                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4358                                                 size.width(), size.height()));
4359                         break;
4360                 }
4361
4362                 case LFUN_DIALOG_UPDATE: {
4363                         string const name = to_utf8(cmd.argument());
4364                         if (name == "prefs" || name == "document")
4365                                 updateDialog(name, string());
4366                         else if (name == "paragraph")
4367                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4368                         else if (currentBufferView()) {
4369                                 Inset * inset = currentBufferView()->editedInset(name);
4370                                 // Can only update a dialog connected to an existing inset
4371                                 if (inset) {
4372                                         // FIXME: get rid of this indirection; GuiView ask the inset
4373                                         // if he is kind enough to update itself...
4374                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4375                                         //FIXME: pass DispatchResult here?
4376                                         inset->dispatch(currentBufferView()->cursor(), fr);
4377                                 }
4378                         }
4379                         break;
4380                 }
4381
4382                 case LFUN_DIALOG_TOGGLE: {
4383                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4384                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4385                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4386                         break;
4387                 }
4388
4389                 case LFUN_DIALOG_DISCONNECT_INSET:
4390                         disconnectDialog(to_utf8(cmd.argument()));
4391                         break;
4392
4393                 case LFUN_DIALOG_HIDE: {
4394                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4395                         break;
4396                 }
4397
4398                 case LFUN_DIALOG_SHOW: {
4399                         string const name = cmd.getArg(0);
4400                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4401
4402                         if (name == "latexlog") {
4403                                 // getStatus checks that
4404                                 LASSERT(doc_buffer, break);
4405                                 Buffer::LogType type;
4406                                 string const logfile = doc_buffer->logName(&type);
4407                                 switch (type) {
4408                                 case Buffer::latexlog:
4409                                         sdata = "latex ";
4410                                         break;
4411                                 case Buffer::buildlog:
4412                                         sdata = "literate ";
4413                                         break;
4414                                 }
4415                                 sdata += Lexer::quoteString(logfile);
4416                                 showDialog("log", sdata);
4417                         } else if (name == "vclog") {
4418                                 // getStatus checks that
4419                                 LASSERT(doc_buffer, break);
4420                                 string const sdata2 = "vc " +
4421                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4422                                 showDialog("log", sdata2);
4423                         } else if (name == "symbols") {
4424                                 sdata = bv->cursor().getEncoding()->name();
4425                                 if (!sdata.empty())
4426                                         showDialog("symbols", sdata);
4427                         // bug 5274
4428                         } else if (name == "prefs" && isFullScreen()) {
4429                                 lfunUiToggle("fullscreen");
4430                                 showDialog("prefs", sdata);
4431                         } else
4432                                 showDialog(name, sdata);
4433                         break;
4434                 }
4435
4436                 case LFUN_MESSAGE:
4437                         dr.setMessage(cmd.argument());
4438                         break;
4439
4440                 case LFUN_UI_TOGGLE: {
4441                         string arg = cmd.getArg(0);
4442                         if (!lfunUiToggle(arg)) {
4443                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4444                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4445                         }
4446                         // Make sure the keyboard focus stays in the work area.
4447                         setFocus();
4448                         break;
4449                 }
4450
4451                 case LFUN_VIEW_SPLIT: {
4452                         LASSERT(doc_buffer, break);
4453                         string const orientation = cmd.getArg(0);
4454                         d.splitter_->setOrientation(orientation == "vertical"
4455                                 ? Qt::Vertical : Qt::Horizontal);
4456                         TabWorkArea * twa = addTabWorkArea();
4457                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4458                         setCurrentWorkArea(wa);
4459                         break;
4460                 }
4461                 case LFUN_TAB_GROUP_CLOSE:
4462                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4463                                 closeTabWorkArea(twa);
4464                                 d.current_work_area_ = nullptr;
4465                                 twa = d.currentTabWorkArea();
4466                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4467                                 if (twa) {
4468                                         // Make sure the work area is up to date.
4469                                         setCurrentWorkArea(twa->currentWorkArea());
4470                                 } else {
4471                                         setCurrentWorkArea(nullptr);
4472                                 }
4473                         }
4474                         break;
4475
4476                 case LFUN_VIEW_CLOSE:
4477                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4478                                 closeWorkArea(twa->currentWorkArea());
4479                                 d.current_work_area_ = nullptr;
4480                                 twa = d.currentTabWorkArea();
4481                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4482                                 if (twa) {
4483                                         // Make sure the work area is up to date.
4484                                         setCurrentWorkArea(twa->currentWorkArea());
4485                                 } else {
4486                                         setCurrentWorkArea(nullptr);
4487                                 }
4488                         }
4489                         break;
4490
4491                 case LFUN_COMPLETION_INLINE:
4492                         if (d.current_work_area_)
4493                                 d.current_work_area_->completer().showInline();
4494                         break;
4495
4496                 case LFUN_COMPLETION_POPUP:
4497                         if (d.current_work_area_)
4498                                 d.current_work_area_->completer().showPopup();
4499                         break;
4500
4501
4502                 case LFUN_COMPLETE:
4503                         if (d.current_work_area_)
4504                                 d.current_work_area_->completer().tab();
4505                         break;
4506
4507                 case LFUN_COMPLETION_CANCEL:
4508                         if (d.current_work_area_) {
4509                                 if (d.current_work_area_->completer().popupVisible())
4510                                         d.current_work_area_->completer().hidePopup();
4511                                 else
4512                                         d.current_work_area_->completer().hideInline();
4513                         }
4514                         break;
4515
4516                 case LFUN_COMPLETION_ACCEPT:
4517                         if (d.current_work_area_)
4518                                 d.current_work_area_->completer().activate();
4519                         break;
4520
4521                 case LFUN_BUFFER_ZOOM_IN:
4522                 case LFUN_BUFFER_ZOOM_OUT:
4523                 case LFUN_BUFFER_ZOOM: {
4524                         if (cmd.argument().empty()) {
4525                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4526                                         zoom_ratio_ = 1.0;
4527                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4528                                         zoom_ratio_ += 0.1;
4529                                 else
4530                                         zoom_ratio_ -= 0.1;
4531                         } else {
4532                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4533                                         zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4534                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4535                                         zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4536                                 else
4537                                         zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4538                         }
4539
4540                         // Actual zoom value: default zoom + fractional extra value
4541                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4542                         if (zoom < static_cast<int>(zoom_min_))
4543                                 zoom = zoom_min_;
4544
4545                         lyxrc.currentZoom = zoom;
4546
4547                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4548                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4549
4550                         guiApp->fontLoader().update();
4551                         dr.screenUpdate(Update::Force | Update::FitCursor);
4552                         break;
4553                 }
4554
4555                 case LFUN_VC_REGISTER:
4556                 case LFUN_VC_RENAME:
4557                 case LFUN_VC_COPY:
4558                 case LFUN_VC_CHECK_IN:
4559                 case LFUN_VC_CHECK_OUT:
4560                 case LFUN_VC_REPO_UPDATE:
4561                 case LFUN_VC_LOCKING_TOGGLE:
4562                 case LFUN_VC_REVERT:
4563                 case LFUN_VC_UNDO_LAST:
4564                 case LFUN_VC_COMMAND:
4565                 case LFUN_VC_COMPARE:
4566                         dispatchVC(cmd, dr);
4567                         break;
4568
4569                 case LFUN_SERVER_GOTO_FILE_ROW:
4570                         if(goToFileRow(to_utf8(cmd.argument())))
4571                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4572                         break;
4573
4574                 case LFUN_LYX_ACTIVATE:
4575                         activateWindow();
4576                         break;
4577
4578                 case LFUN_WINDOW_RAISE:
4579                         raise();
4580                         activateWindow();
4581                         showNormal();
4582                         break;
4583
4584                 case LFUN_FORWARD_SEARCH: {
4585                         // it seems safe to assume we have a document buffer, since
4586                         // getStatus wants one.
4587                         LASSERT(doc_buffer, break);
4588                         Buffer const * doc_master = doc_buffer->masterBuffer();
4589                         FileName const path(doc_master->temppath());
4590                         string const texname = doc_master->isChild(doc_buffer)
4591                                 ? DocFileName(changeExtension(
4592                                         doc_buffer->absFileName(),
4593                                                 "tex")).mangledFileName()
4594                                 : doc_buffer->latexName();
4595                         string const fulltexname =
4596                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4597                         string const mastername =
4598                                 removeExtension(doc_master->latexName());
4599                         FileName const dviname(addName(path.absFileName(),
4600                                         addExtension(mastername, "dvi")));
4601                         FileName const pdfname(addName(path.absFileName(),
4602                                         addExtension(mastername, "pdf")));
4603                         bool const have_dvi = dviname.exists();
4604                         bool const have_pdf = pdfname.exists();
4605                         if (!have_dvi && !have_pdf) {
4606                                 dr.setMessage(_("Please, preview the document first."));
4607                                 break;
4608                         }
4609                         string outname = dviname.onlyFileName();
4610                         string command = lyxrc.forward_search_dvi;
4611                         if (!have_dvi || (have_pdf &&
4612                             pdfname.lastModified() > dviname.lastModified())) {
4613                                 outname = pdfname.onlyFileName();
4614                                 command = lyxrc.forward_search_pdf;
4615                         }
4616
4617                         DocIterator cur = bv->cursor();
4618                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4619                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4620                                    << " cur:" << cur);
4621                         if (row == -1 || command.empty()) {
4622                                 dr.setMessage(_("Couldn't proceed."));
4623                                 break;
4624                         }
4625                         string texrow = convert<string>(row);
4626
4627                         command = subst(command, "$$n", texrow);
4628                         command = subst(command, "$$f", fulltexname);
4629                         command = subst(command, "$$t", texname);
4630                         command = subst(command, "$$o", outname);
4631
4632                         volatile PathChanger p(path);
4633                         Systemcall one;
4634                         one.startscript(Systemcall::DontWait, command);
4635                         break;
4636                 }
4637
4638                 case LFUN_SPELLING_CONTINUOUSLY:
4639                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4640                         dr.screenUpdate(Update::Force);
4641                         break;
4642
4643                 case LFUN_CITATION_OPEN: {
4644                         string pdfv, psv;
4645                         if (theFormats().getFormat("pdf"))
4646                                 pdfv = theFormats().getFormat("pdf")->viewer();
4647                         if (theFormats().getFormat("ps"))
4648                                 psv = theFormats().getFormat("ps")->viewer();
4649                         frontend::showTarget(argument, pdfv, psv);
4650                         break;
4651                 }
4652
4653                 default:
4654                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4655                         // let's try that:
4656                         dispatchToBufferView(cmd, dr);
4657                         break;
4658         }
4659
4660         // Part of automatic menu appearance feature.
4661         if (isFullScreen()) {
4662                 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4663                         menuBar()->hide();
4664         }
4665
4666         // Need to update bv because many LFUNs here might have destroyed it
4667         bv = currentBufferView();
4668
4669         // Clear non-empty selections
4670         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4671         if (bv) {
4672                 Cursor & cur = bv->cursor();
4673                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4674                         cur.clearSelection();
4675                 }
4676         }
4677 }
4678
4679
4680 bool GuiView::lfunUiToggle(string const & ui_component)
4681 {
4682         if (ui_component == "scrollbar") {
4683                 // hide() is of no help
4684                 if (d.current_work_area_->verticalScrollBarPolicy() ==
4685                         Qt::ScrollBarAlwaysOff)
4686
4687                         d.current_work_area_->setVerticalScrollBarPolicy(
4688                                 Qt::ScrollBarAsNeeded);
4689                 else
4690                         d.current_work_area_->setVerticalScrollBarPolicy(
4691                                 Qt::ScrollBarAlwaysOff);
4692         } else if (ui_component == "statusbar") {
4693                 statusBar()->setVisible(!statusBar()->isVisible());
4694         } else if (ui_component == "menubar") {
4695                 menuBar()->setVisible(!menuBar()->isVisible());
4696         } else
4697         if (ui_component == "frame") {
4698                 int const l = contentsMargins().left();
4699
4700                 //are the frames in default state?
4701                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4702                 if (l == 0) {
4703 #if QT_VERSION >  0x050903
4704                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4705 #endif
4706                         setContentsMargins(-2, -2, -2, -2);
4707                 } else {
4708 #if QT_VERSION >  0x050903
4709                         setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4710 #endif
4711                         setContentsMargins(0, 0, 0, 0);
4712                 }
4713         } else
4714         if (ui_component == "fullscreen") {
4715                 toggleFullScreen();
4716         } else
4717                 return false;
4718         return true;
4719 }
4720
4721
4722 void GuiView::toggleFullScreen()
4723 {
4724         setWindowState(windowState() ^ Qt::WindowFullScreen);
4725 }
4726
4727
4728 Buffer const * GuiView::updateInset(Inset const * inset)
4729 {
4730         if (!inset)
4731                 return nullptr;
4732
4733         Buffer const * inset_buffer = &(inset->buffer());
4734
4735         for (int i = 0; i != d.splitter_->count(); ++i) {
4736                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4737                 if (!wa)
4738                         continue;
4739                 Buffer const * buffer = &(wa->bufferView().buffer());
4740                 if (inset_buffer == buffer)
4741                         wa->scheduleRedraw(true);
4742         }
4743         return inset_buffer;
4744 }
4745
4746
4747 void GuiView::restartCaret()
4748 {
4749         /* When we move around, or type, it's nice to be able to see
4750          * the caret immediately after the keypress.
4751          */
4752         if (d.current_work_area_)
4753                 d.current_work_area_->startBlinkingCaret();
4754
4755         // Take this occasion to update the other GUI elements.
4756         updateDialogs();
4757         updateStatusBar();
4758 }
4759
4760
4761 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4762 {
4763         if (d.current_work_area_)
4764                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4765 }
4766
4767 namespace {
4768
4769 // This list should be kept in sync with the list of insets in
4770 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
4771 // dialog should have the same name as the inset.
4772 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4773 // docs in LyXAction.cpp.
4774
4775 char const * const dialognames[] = {
4776
4777 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4778 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4779 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4780 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4781 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4782 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4783 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4784 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4785
4786 char const * const * const end_dialognames =
4787         dialognames + (sizeof(dialognames) / sizeof(char *));
4788
4789 class cmpCStr {
4790 public:
4791         cmpCStr(char const * name) : name_(name) {}
4792         bool operator()(char const * other) {
4793                 return strcmp(other, name_) == 0;
4794         }
4795 private:
4796         char const * name_;
4797 };
4798
4799
4800 bool isValidName(string const & name)
4801 {
4802         return find_if(dialognames, end_dialognames,
4803                 cmpCStr(name.c_str())) != end_dialognames;
4804 }
4805
4806 } // namespace
4807
4808
4809 void GuiView::resetDialogs()
4810 {
4811         // Make sure that no LFUN uses any GuiView.
4812         guiApp->setCurrentView(nullptr);
4813         saveLayout();
4814         saveUISettings();
4815         menuBar()->clear();
4816         constructToolbars();
4817         guiApp->menus().fillMenuBar(menuBar(), this, false);
4818         d.layout_->updateContents(true);
4819         // Now update controls with current buffer.
4820         guiApp->setCurrentView(this);
4821         restoreLayout();
4822         restartCaret();
4823 }
4824
4825
4826 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4827 {
4828         for (QObject * child: widget->children()) {
4829                 if (child->inherits("QGroupBox")) {
4830                         QGroupBox * box = (QGroupBox*) child;
4831                         box->setFlat(flag);
4832                 } else {
4833                         flatGroupBoxes(child, flag);
4834                 }
4835         }
4836 }
4837
4838
4839 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4840 {
4841         if (!isValidName(name))
4842                 return nullptr;
4843
4844         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4845
4846         if (it != d.dialogs_.end()) {
4847                 if (hide_it)
4848                         it->second->hideView();
4849                 return it->second.get();
4850         }
4851
4852         Dialog * dialog = build(name);
4853         d.dialogs_[name].reset(dialog);
4854 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4855         // Force a uniform style for group boxes
4856         // On Mac non-flat works better, on Linux flat is standard
4857         flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4858 #endif
4859         if (lyxrc.allow_geometry_session)
4860                 dialog->restoreSession();
4861         if (hide_it)
4862                 dialog->hideView();
4863         return dialog;
4864 }
4865
4866
4867 void GuiView::showDialog(string const & name, string const & sdata,
4868         Inset * inset)
4869 {
4870         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4871 }
4872
4873
4874 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4875         Inset * inset)
4876 {
4877         if (d.in_show_)
4878                 return;
4879
4880         const string name = fromqstr(qname);
4881         const string sdata = fromqstr(qdata);
4882
4883         d.in_show_ = true;
4884         try {
4885                 Dialog * dialog = findOrBuild(name, false);
4886                 if (dialog) {
4887                         bool const visible = dialog->isVisibleView();
4888                         dialog->showData(sdata);
4889                         if (currentBufferView())
4890                                 currentBufferView()->editInset(name, inset);
4891                         // We only set the focus to the new dialog if it was not yet
4892                         // visible in order not to change the existing previous behaviour
4893                         if (visible) {
4894                                 // activateWindow is needed for floating dockviews
4895                                 dialog->asQWidget()->raise();
4896                                 dialog->asQWidget()->activateWindow();
4897                                 dialog->asQWidget()->setFocus();
4898                         }
4899                 }
4900         }
4901         catch (ExceptionMessage const &) {
4902                 d.in_show_ = false;
4903                 throw;
4904         }
4905         d.in_show_ = false;
4906 }
4907
4908
4909 bool GuiView::isDialogVisible(string const & name) const
4910 {
4911         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4912         if (it == d.dialogs_.end())
4913                 return false;
4914         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4915 }
4916
4917
4918 void GuiView::hideDialog(string const & name, Inset * inset)
4919 {
4920         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4921         if (it == d.dialogs_.end())
4922                 return;
4923
4924         if (inset) {
4925                 if (!currentBufferView())
4926                         return;
4927                 if (inset != currentBufferView()->editedInset(name))
4928                         return;
4929         }
4930
4931         Dialog * const dialog = it->second.get();
4932         if (dialog->isVisibleView())
4933                 dialog->hideView();
4934         if (currentBufferView())
4935                 currentBufferView()->editInset(name, nullptr);
4936 }
4937
4938
4939 void GuiView::disconnectDialog(string const & name)
4940 {
4941         if (!isValidName(name))
4942                 return;
4943         if (currentBufferView())
4944                 currentBufferView()->editInset(name, nullptr);
4945 }
4946
4947
4948 void GuiView::hideAll() const
4949 {
4950         for(auto const & dlg_p : d.dialogs_)
4951                 dlg_p.second->hideView();
4952 }
4953
4954
4955 void GuiView::updateDialogs()
4956 {
4957         for(auto const & dlg_p : d.dialogs_) {
4958                 Dialog * dialog = dlg_p.second.get();
4959                 if (dialog) {
4960                         if (dialog->needBufferOpen() && !documentBufferView())
4961                                 hideDialog(fromqstr(dialog->name()), nullptr);
4962                         else if (dialog->isVisibleView())
4963                                 dialog->checkStatus();
4964                 }
4965         }
4966         updateToolbars();
4967         updateLayoutList();
4968 }
4969
4970
4971 Dialog * GuiView::build(string const & name)
4972 {
4973         return createDialog(*this, name);
4974 }
4975
4976
4977 SEMenu::SEMenu(QWidget * parent)
4978 {
4979         QAction * action = addAction(qt_("Disable Shell Escape"));
4980         connect(action, SIGNAL(triggered()),
4981                 parent, SLOT(disableShellEscape()));
4982 }
4983
4984 } // namespace frontend
4985 } // namespace lyx
4986
4987 #include "moc_GuiView.cpp"