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