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