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