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