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