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