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