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