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