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