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