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