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