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