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