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