]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiView.cpp
Remove obsolete (and false) comment.
[lyx.git] / src / frontends / qt / GuiView.cpp
1 /**
2  * \file GuiView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
18 #include "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() || !doc_buffer->isClean());
2000                 break;
2001
2002         //FIXME: This LFUN should be moved to GuiApplication.
2003         case LFUN_BUFFER_WRITE_ALL: {
2004                 // We enable the command only if there are some modified buffers
2005                 Buffer * first = theBufferList().first();
2006                 enable = false;
2007                 if (!first)
2008                         break;
2009                 Buffer * b = first;
2010                 // We cannot use a for loop as the buffer list is a cycle.
2011                 do {
2012                         if (!b->isClean()) {
2013                                 enable = true;
2014                                 break;
2015                         }
2016                         b = theBufferList().next(b);
2017                 } while (b != first);
2018                 break;
2019         }
2020
2021         case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2022                 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2023                 break;
2024
2025         case LFUN_BUFFER_EXPORT: {
2026                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2027                         enable = false;
2028                         break;
2029                 }
2030                 return doc_buffer->getStatus(cmd, flag);
2031         }
2032
2033         case LFUN_BUFFER_EXPORT_AS:
2034                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2035                         enable = false;
2036                         break;
2037                 }
2038                 // fall through
2039         case LFUN_BUFFER_WRITE_AS:
2040         case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2041                 enable = doc_buffer != nullptr;
2042                 break;
2043
2044         case LFUN_EXPORT_CANCEL:
2045                 enable = d.processing_thread_watcher_.isRunning();
2046                 break;
2047
2048         case LFUN_BUFFER_CLOSE:
2049         case LFUN_VIEW_CLOSE:
2050                 enable = doc_buffer != nullptr;
2051                 break;
2052
2053         case LFUN_BUFFER_CLOSE_ALL:
2054                 enable = theBufferList().last() != theBufferList().first();
2055                 break;
2056
2057         case LFUN_BUFFER_CHKTEX: {
2058                 // hide if we have no checktex command
2059                 if (lyxrc.chktex_command.empty()) {
2060                         flag.setUnknown(true);
2061                         enable = false;
2062                         break;
2063                 }
2064                 if (!doc_buffer || !doc_buffer->params().isLatex()
2065                     || d.processing_thread_watcher_.isRunning()) {
2066                         // grey out, don't hide
2067                         enable = false;
2068                         break;
2069                 }
2070                 enable = true;
2071                 break;
2072         }
2073
2074         case LFUN_VIEW_SPLIT:
2075                 if (cmd.getArg(0) == "vertical")
2076                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2077                                          d.splitter_->orientation() == Qt::Vertical);
2078                 else
2079                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2080                                          d.splitter_->orientation() == Qt::Horizontal);
2081                 break;
2082
2083         case LFUN_TAB_GROUP_CLOSE:
2084                 enable = d.tabWorkAreaCount() > 1;
2085                 break;
2086
2087         case LFUN_DEVEL_MODE_TOGGLE:
2088                 flag.setOnOff(devel_mode_);
2089                 break;
2090
2091         case LFUN_TOOLBAR_TOGGLE: {
2092                 string const name = cmd.getArg(0);
2093                 if (GuiToolbar * t = toolbar(name))
2094                         flag.setOnOff(t->isVisible());
2095                 else {
2096                         enable = false;
2097                         docstring const msg =
2098                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2099                         flag.message(msg);
2100                 }
2101                 break;
2102         }
2103
2104         case LFUN_TOOLBAR_MOVABLE: {
2105                 string const name = cmd.getArg(0);
2106                 // use negation since locked == !movable
2107                 if (name == "*")
2108                         // toolbar name * locks all toolbars
2109                         flag.setOnOff(!toolbarsMovable_);
2110                 else if (GuiToolbar * t = toolbar(name))
2111                         flag.setOnOff(!(t->isMovable()));
2112                 else {
2113                         enable = false;
2114                         docstring const msg =
2115                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2116                         flag.message(msg);
2117                 }
2118                 break;
2119         }
2120
2121         case LFUN_ICON_SIZE:
2122                 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2123                 break;
2124
2125         case LFUN_DROP_LAYOUTS_CHOICE:
2126                 enable = buf != nullptr;
2127                 break;
2128
2129         case LFUN_UI_TOGGLE:
2130                 flag.setOnOff(isFullScreen());
2131                 break;
2132
2133         case LFUN_DIALOG_DISCONNECT_INSET:
2134                 break;
2135
2136         case LFUN_DIALOG_HIDE:
2137                 // FIXME: should we check if the dialog is shown?
2138                 break;
2139
2140         case LFUN_DIALOG_TOGGLE:
2141                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2142                 // to set "enable"
2143                 // fall through
2144         case LFUN_DIALOG_SHOW: {
2145                 string const name = cmd.getArg(0);
2146                 if (!doc_buffer)
2147                         enable = name == "aboutlyx"
2148                                 || name == "file" //FIXME: should be removed.
2149                                 || name == "lyxfiles"
2150                                 || name == "prefs"
2151                                 || name == "texinfo"
2152                                 || name == "progress"
2153                                 || name == "compare";
2154                 else if (name == "character" || name == "symbols"
2155                         || name == "mathdelimiter" || name == "mathmatrix") {
2156                         if (!buf || buf->isReadonly())
2157                                 enable = false;
2158                         else {
2159                                 Cursor const & cur = currentBufferView()->cursor();
2160                                 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2161                         }
2162                 }
2163                 else if (name == "latexlog")
2164                         enable = FileName(doc_buffer->logName()).isReadableFile();
2165                 else if (name == "spellchecker")
2166                         enable = theSpellChecker()
2167                                 && !doc_buffer->isReadonly()
2168                                 && !doc_buffer->text().empty();
2169                 else if (name == "vclog")
2170                         enable = doc_buffer->lyxvc().inUse();
2171                 break;
2172         }
2173
2174         case LFUN_DIALOG_UPDATE: {
2175                 string const name = cmd.getArg(0);
2176                 if (!buf)
2177                         enable = name == "prefs";
2178                 break;
2179         }
2180
2181         case LFUN_COMMAND_EXECUTE:
2182         case LFUN_MESSAGE:
2183         case LFUN_MENU_OPEN:
2184                 // Nothing to check.
2185                 break;
2186
2187         case LFUN_COMPLETION_INLINE:
2188                 if (!d.current_work_area_
2189                         || !d.current_work_area_->completer().inlinePossible(
2190                         currentBufferView()->cursor()))
2191                         enable = false;
2192                 break;
2193
2194         case LFUN_COMPLETION_POPUP:
2195                 if (!d.current_work_area_
2196                         || !d.current_work_area_->completer().popupPossible(
2197                         currentBufferView()->cursor()))
2198                         enable = false;
2199                 break;
2200
2201         case LFUN_COMPLETE:
2202                 if (!d.current_work_area_
2203                         || !d.current_work_area_->completer().inlinePossible(
2204                         currentBufferView()->cursor()))
2205                         enable = false;
2206                 break;
2207
2208         case LFUN_COMPLETION_ACCEPT:
2209                 if (!d.current_work_area_
2210                         || (!d.current_work_area_->completer().popupVisible()
2211                         && !d.current_work_area_->completer().inlineVisible()
2212                         && !d.current_work_area_->completer().completionAvailable()))
2213                         enable = false;
2214                 break;
2215
2216         case LFUN_COMPLETION_CANCEL:
2217                 if (!d.current_work_area_
2218                         || (!d.current_work_area_->completer().popupVisible()
2219                         && !d.current_work_area_->completer().inlineVisible()))
2220                         enable = false;
2221                 break;
2222
2223         case LFUN_BUFFER_ZOOM_OUT:
2224         case LFUN_BUFFER_ZOOM_IN: {
2225                 // only diff between these two is that the default for ZOOM_OUT
2226                 // is a neg. number
2227                 bool const neg_zoom =
2228                         convert<int>(cmd.argument()) < 0 ||
2229                         (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2230                 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2231                         docstring const msg =
2232                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2233                         flag.message(msg);
2234                         enable = false;
2235                 } else
2236                         enable = doc_buffer;
2237                 break;
2238         }
2239
2240         case LFUN_BUFFER_ZOOM: {
2241                 bool const less_than_min_zoom =
2242                         !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2243                 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2244                         docstring const msg =
2245                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2246                         flag.message(msg);
2247                         enable = false;
2248                 }
2249                 else
2250                         enable = doc_buffer;
2251                 break;
2252         }
2253
2254         case LFUN_BUFFER_MOVE_NEXT:
2255         case LFUN_BUFFER_MOVE_PREVIOUS:
2256                 // we do not cycle when moving
2257         case LFUN_BUFFER_NEXT:
2258         case LFUN_BUFFER_PREVIOUS:
2259                 // because we cycle, it doesn't matter whether on first or last
2260                 enable = (d.currentTabWorkArea()->count() > 1);
2261                 break;
2262         case LFUN_BUFFER_SWITCH:
2263                 // toggle on the current buffer, but do not toggle off
2264                 // the other ones (is that a good idea?)
2265                 if (doc_buffer
2266                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2267                         flag.setOnOff(true);
2268                 break;
2269
2270         case LFUN_VC_REGISTER:
2271                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2272                 break;
2273         case LFUN_VC_RENAME:
2274                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2275                 break;
2276         case LFUN_VC_COPY:
2277                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2278                 break;
2279         case LFUN_VC_CHECK_IN:
2280                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2281                 break;
2282         case LFUN_VC_CHECK_OUT:
2283                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2284                 break;
2285         case LFUN_VC_LOCKING_TOGGLE:
2286                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2287                         && doc_buffer->lyxvc().lockingToggleEnabled();
2288                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2289                 break;
2290         case LFUN_VC_REVERT:
2291                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2292                         && !doc_buffer->hasReadonlyFlag();
2293                 break;
2294         case LFUN_VC_UNDO_LAST:
2295                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2296                 break;
2297         case LFUN_VC_REPO_UPDATE:
2298                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2299                 break;
2300         case LFUN_VC_COMMAND: {
2301                 if (cmd.argument().empty())
2302                         enable = false;
2303                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2304                         enable = false;
2305                 break;
2306         }
2307         case LFUN_VC_COMPARE:
2308                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2309                 break;
2310
2311         case LFUN_SERVER_GOTO_FILE_ROW:
2312         case LFUN_LYX_ACTIVATE:
2313                 break;
2314         case LFUN_FORWARD_SEARCH:
2315                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2316                 break;
2317
2318         case LFUN_FILE_INSERT_PLAINTEXT:
2319         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2320                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2321                 break;
2322
2323         case LFUN_SPELLING_CONTINUOUSLY:
2324                 flag.setOnOff(lyxrc.spellcheck_continuously);
2325                 break;
2326
2327         default:
2328                 return false;
2329         }
2330
2331         if (!enable)
2332                 flag.setEnabled(false);
2333
2334         return true;
2335 }
2336
2337
2338 static FileName selectTemplateFile()
2339 {
2340         FileDialog dlg(qt_("Select template file"));
2341         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2342         dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2343
2344         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2345                                  QStringList(qt_("LyX Documents (*.lyx)")));
2346
2347         if (result.first == FileDialog::Later)
2348                 return FileName();
2349         if (result.second.isEmpty())
2350                 return FileName();
2351         return FileName(fromqstr(result.second));
2352 }
2353
2354
2355 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2356 {
2357         setBusy(true);
2358
2359         Buffer * newBuffer = nullptr;
2360         try {
2361                 newBuffer = checkAndLoadLyXFile(filename);
2362         } catch (ExceptionMessage const & e) {
2363                 setBusy(false);
2364                 throw(e);
2365         }
2366         setBusy(false);
2367
2368         if (!newBuffer) {
2369                 message(_("Document not loaded."));
2370                 return nullptr;
2371         }
2372
2373         setBuffer(newBuffer);
2374         newBuffer->errors("Parse");
2375
2376         if (tolastfiles) {
2377                 theSession().lastFiles().add(filename);
2378                 theSession().writeFile();
2379   }
2380
2381         return newBuffer;
2382 }
2383
2384
2385 void GuiView::openDocument(string const & fname)
2386 {
2387         string initpath = lyxrc.document_path;
2388
2389         if (documentBufferView()) {
2390                 string const trypath = documentBufferView()->buffer().filePath();
2391                 // If directory is writeable, use this as default.
2392                 if (FileName(trypath).isDirWritable())
2393                         initpath = trypath;
2394         }
2395
2396         string filename;
2397
2398         if (fname.empty()) {
2399                 FileDialog dlg(qt_("Select document to open"));
2400                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2401                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2402
2403                 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2404                 FileDialog::Result result =
2405                         dlg.open(toqstr(initpath), filter);
2406
2407                 if (result.first == FileDialog::Later)
2408                         return;
2409
2410                 filename = fromqstr(result.second);
2411
2412                 // check selected filename
2413                 if (filename.empty()) {
2414                         message(_("Canceled."));
2415                         return;
2416                 }
2417         } else
2418                 filename = fname;
2419
2420         // get absolute path of file and add ".lyx" to the filename if
2421         // necessary.
2422         FileName const fullname =
2423                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2424         if (!fullname.empty())
2425                 filename = fullname.absFileName();
2426
2427         if (!fullname.onlyPath().isDirectory()) {
2428                 Alert::warning(_("Invalid filename"),
2429                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2430                                 from_utf8(fullname.absFileName())));
2431                 return;
2432         }
2433
2434         // if the file doesn't exist and isn't already open (bug 6645),
2435         // let the user create one
2436         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2437             !LyXVC::file_not_found_hook(fullname)) {
2438                 // the user specifically chose this name. Believe him.
2439                 Buffer * const b = newFile(filename, string(), true);
2440                 if (b)
2441                         setBuffer(b);
2442                 return;
2443         }
2444
2445         docstring const disp_fn = makeDisplayPath(filename);
2446         message(bformat(_("Opening document %1$s..."), disp_fn));
2447
2448         docstring str2;
2449         Buffer * buf = loadDocument(fullname);
2450         if (buf) {
2451                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2452                 if (buf->lyxvc().inUse())
2453                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2454                                 " " + _("Version control detected.");
2455         } else {
2456                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2457         }
2458         message(str2);
2459 }
2460
2461 // FIXME: clean that
2462 static bool import(GuiView * lv, FileName const & filename,
2463         string const & format, ErrorList & errorList)
2464 {
2465         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2466
2467         string loader_format;
2468         vector<string> loaders = theConverters().loaders();
2469         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2470                 vector<string>::const_iterator it = loaders.begin();
2471                 vector<string>::const_iterator en = loaders.end();
2472                 for (; it != en; ++it) {
2473                         if (!theConverters().isReachable(format, *it))
2474                                 continue;
2475
2476                         string const tofile =
2477                                 support::changeExtension(filename.absFileName(),
2478                                 theFormats().extension(*it));
2479                         if (theConverters().convert(nullptr, filename, FileName(tofile),
2480                                 filename, format, *it, errorList) != Converters::SUCCESS)
2481                                 return false;
2482                         loader_format = *it;
2483                         break;
2484                 }
2485                 if (loader_format.empty()) {
2486                         frontend::Alert::error(_("Couldn't import file"),
2487                                          bformat(_("No information for importing the format %1$s."),
2488                                          theFormats().prettyName(format)));
2489                         return false;
2490                 }
2491         } else
2492                 loader_format = format;
2493
2494         if (loader_format == "lyx") {
2495                 Buffer * buf = lv->loadDocument(lyxfile);
2496                 if (!buf)
2497                         return false;
2498         } else {
2499                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2500                 if (!b)
2501                         return false;
2502                 lv->setBuffer(b);
2503                 bool as_paragraphs = loader_format == "textparagraph";
2504                 string filename2 = (loader_format == format) ? filename.absFileName()
2505                         : support::changeExtension(filename.absFileName(),
2506                                           theFormats().extension(loader_format));
2507                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2508                         as_paragraphs);
2509                 guiApp->setCurrentView(lv);
2510                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2511         }
2512
2513         return true;
2514 }
2515
2516
2517 void GuiView::importDocument(string const & argument)
2518 {
2519         string format;
2520         string filename = split(argument, format, ' ');
2521
2522         LYXERR(Debug::INFO, format << " file: " << filename);
2523
2524         // need user interaction
2525         if (filename.empty()) {
2526                 string initpath = lyxrc.document_path;
2527                 if (documentBufferView()) {
2528                         string const trypath = documentBufferView()->buffer().filePath();
2529                         // If directory is writeable, use this as default.
2530                         if (FileName(trypath).isDirWritable())
2531                                 initpath = trypath;
2532                 }
2533
2534                 docstring const text = bformat(_("Select %1$s file to import"),
2535                         theFormats().prettyName(format));
2536
2537                 FileDialog dlg(toqstr(text));
2538                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2539                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2540
2541                 docstring filter = theFormats().prettyName(format);
2542                 filter += " (*.{";
2543                 // FIXME UNICODE
2544                 filter += from_utf8(theFormats().extensions(format));
2545                 filter += "})";
2546
2547                 FileDialog::Result result =
2548                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2549
2550                 if (result.first == FileDialog::Later)
2551                         return;
2552
2553                 filename = fromqstr(result.second);
2554
2555                 // check selected filename
2556                 if (filename.empty())
2557                         message(_("Canceled."));
2558         }
2559
2560         if (filename.empty())
2561                 return;
2562
2563         // get absolute path of file
2564         FileName const fullname(support::makeAbsPath(filename));
2565
2566         // Can happen if the user entered a path into the dialog
2567         // (see bug #7437)
2568         if (fullname.onlyFileName().empty()) {
2569                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2570                                           "Aborting import."),
2571                                         from_utf8(fullname.absFileName()));
2572                 frontend::Alert::error(_("File name error"), msg);
2573                 message(_("Canceled."));
2574                 return;
2575         }
2576
2577
2578         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2579
2580         // Check if the document already is open
2581         Buffer * buf = theBufferList().getBuffer(lyxfile);
2582         if (buf) {
2583                 setBuffer(buf);
2584                 if (!closeBuffer()) {
2585                         message(_("Canceled."));
2586                         return;
2587                 }
2588         }
2589
2590         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2591
2592         // if the file exists already, and we didn't do
2593         // -i lyx thefile.lyx, warn
2594         if (lyxfile.exists() && fullname != lyxfile) {
2595
2596                 docstring text = bformat(_("The document %1$s already exists.\n\n"
2597                         "Do you want to overwrite that document?"), displaypath);
2598                 int const ret = Alert::prompt(_("Overwrite document?"),
2599                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
2600
2601                 if (ret == 1) {
2602                         message(_("Canceled."));
2603                         return;
2604                 }
2605         }
2606
2607         message(bformat(_("Importing %1$s..."), displaypath));
2608         ErrorList errorList;
2609         if (import(this, fullname, format, errorList))
2610                 message(_("imported."));
2611         else
2612                 message(_("file not imported!"));
2613
2614         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2615 }
2616
2617
2618 void GuiView::newDocument(string const & filename, string templatefile,
2619                           bool from_template)
2620 {
2621         FileName initpath(lyxrc.document_path);
2622         if (documentBufferView()) {
2623                 FileName const trypath(documentBufferView()->buffer().filePath());
2624                 // If directory is writeable, use this as default.
2625                 if (trypath.isDirWritable())
2626                         initpath = trypath;
2627         }
2628
2629         if (from_template) {
2630                 if (templatefile.empty())
2631                         templatefile =  selectTemplateFile().absFileName();
2632                 if (templatefile.empty())
2633                         return;
2634         }
2635
2636         Buffer * b;
2637         if (filename.empty())
2638                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2639         else
2640                 b = newFile(filename, templatefile, true);
2641
2642         if (b)
2643                 setBuffer(b);
2644
2645         // If no new document could be created, it is unsure
2646         // whether there is a valid BufferView.
2647         if (currentBufferView())
2648                 // Ensure the cursor is correctly positioned on screen.
2649                 currentBufferView()->showCursor();
2650 }
2651
2652
2653 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2654 {
2655         BufferView * bv = documentBufferView();
2656         if (!bv)
2657                 return;
2658
2659         // FIXME UNICODE
2660         FileName filename(to_utf8(fname));
2661         if (filename.empty()) {
2662                 // Launch a file browser
2663                 // FIXME UNICODE
2664                 string initpath = lyxrc.document_path;
2665                 string const trypath = bv->buffer().filePath();
2666                 // If directory is writeable, use this as default.
2667                 if (FileName(trypath).isDirWritable())
2668                         initpath = trypath;
2669
2670                 // FIXME UNICODE
2671                 FileDialog dlg(qt_("Select LyX document to insert"));
2672                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2673                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2674
2675                 FileDialog::Result result = dlg.open(toqstr(initpath),
2676                                          QStringList(qt_("LyX Documents (*.lyx)")));
2677
2678                 if (result.first == FileDialog::Later)
2679                         return;
2680
2681                 // FIXME UNICODE
2682                 filename.set(fromqstr(result.second));
2683
2684                 // check selected filename
2685                 if (filename.empty()) {
2686                         // emit message signal.
2687                         message(_("Canceled."));
2688                         return;
2689                 }
2690         }
2691
2692         bv->insertLyXFile(filename, ignorelang);
2693         bv->buffer().errors("Parse");
2694 }
2695
2696
2697 string const GuiView::getTemplatesPath(Buffer & b)
2698 {
2699         // We start off with the user's templates path
2700         string result = addPath(package().user_support().absFileName(), "templates");
2701         // Check for the document language
2702         string const langcode = b.params().language->code();
2703         string const shortcode = langcode.substr(0, 2);
2704         if (!langcode.empty() && shortcode != "en") {
2705                 string subpath = addPath(result, shortcode);
2706                 string subpath_long = addPath(result, langcode);
2707                 // If we have a subdirectory for the language already,
2708                 // navigate there
2709                 FileName sp = FileName(subpath);
2710                 if (sp.isDirectory())
2711                         result = subpath;
2712                 else if (FileName(subpath_long).isDirectory())
2713                         result = subpath_long;
2714                 else {
2715                         // Ask whether we should create such a subdirectory
2716                         docstring const text =
2717                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2718                                           "appropriate to the document language (%1$s).\n"
2719                                           "This subdirectory does not exists yet.\n"
2720                                           "Do you want to create it?"),
2721                                         _(b.params().language->display()));
2722                         if (Alert::prompt(_("Create Language Directory?"),
2723                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2724                                 // If the user agreed, we try to create it and report if this failed.
2725                                 if (!sp.createDirectory(0777))
2726                                         Alert::error(_("Subdirectory creation failed!"),
2727                                                      _("Could not create subdirectory.\n"
2728                                                        "The template will be saved in the parent directory."));
2729                                 else
2730                                         result = subpath;
2731                         }
2732                 }
2733         }
2734         // Do we have a layout category?
2735         string const cat = b.params().baseClass() ?
2736                                 b.params().baseClass()->category()
2737                               : string();
2738         if (!cat.empty()) {
2739                 string subpath = addPath(result, cat);
2740                 // If we have a subdirectory for the category already,
2741                 // navigate there
2742                 FileName sp = FileName(subpath);
2743                 if (sp.isDirectory())
2744                         result = subpath;
2745                 else {
2746                         // Ask whether we should create such a subdirectory
2747                         docstring const text =
2748                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2749                                           "appropriate to the layout category (%1$s).\n"
2750                                           "This subdirectory does not exists yet.\n"
2751                                           "Do you want to create it?"),
2752                                         _(cat));
2753                         if (Alert::prompt(_("Create Category Directory?"),
2754                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2755                                 // If the user agreed, we try to create it and report if this failed.
2756                                 if (!sp.createDirectory(0777))
2757                                         Alert::error(_("Subdirectory creation failed!"),
2758                                                      _("Could not create subdirectory.\n"
2759                                                        "The template will be saved in the parent directory."));
2760                                 else
2761                                         result = subpath;
2762                         }
2763                 }
2764         }
2765         return result;
2766 }
2767
2768
2769 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2770 {
2771         FileName fname = b.fileName();
2772         FileName const oldname = fname;
2773         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2774
2775         if (!newname.empty()) {
2776                 // FIXME UNICODE
2777                 if (as_template)
2778                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2779                 else
2780                         fname = support::makeAbsPath(to_utf8(newname),
2781                                                      oldname.onlyPath().absFileName());
2782         } else {
2783                 // Switch to this Buffer.
2784                 setBuffer(&b);
2785
2786                 // No argument? Ask user through dialog.
2787                 // FIXME UNICODE
2788                 QString const title = as_template ? qt_("Choose a filename to save template as")
2789                                                   : qt_("Choose a filename to save document as");
2790                 FileDialog dlg(title);
2791                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2792                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2793
2794                 if (!isLyXFileName(fname.absFileName()))
2795                         fname.changeExtension(".lyx");
2796
2797                 string const path = as_template ?
2798                                         getTemplatesPath(b)
2799                                       : fname.onlyPath().absFileName();
2800                 FileDialog::Result result =
2801                         dlg.save(toqstr(path),
2802                                    QStringList(qt_("LyX Documents (*.lyx)")),
2803                                          toqstr(fname.onlyFileName()));
2804
2805                 if (result.first == FileDialog::Later)
2806                         return false;
2807
2808                 fname.set(fromqstr(result.second));
2809
2810                 if (fname.empty())
2811                         return false;
2812
2813                 if (!isLyXFileName(fname.absFileName()))
2814                         fname.changeExtension(".lyx");
2815         }
2816
2817         // fname is now the new Buffer location.
2818
2819         // if there is already a Buffer open with this name, we do not want
2820         // to have another one. (the second test makes sure we're not just
2821         // trying to overwrite ourselves, which is fine.)
2822         if (theBufferList().exists(fname) && fname != oldname
2823                   && theBufferList().getBuffer(fname) != &b) {
2824                 docstring const text =
2825                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
2826                             "Please close it before attempting to overwrite it.\n"
2827                             "Do you want to choose a new filename?"),
2828                                 from_utf8(fname.absFileName()));
2829                 int const ret = Alert::prompt(_("Chosen File Already Open"),
2830                         text, 0, 1, _("&Rename"), _("&Cancel"));
2831                 switch (ret) {
2832                 case 0: return renameBuffer(b, docstring(), kind);
2833                 case 1: return false;
2834                 }
2835                 //return false;
2836         }
2837
2838         bool const existsLocal = fname.exists();
2839         bool const existsInVC = LyXVC::fileInVC(fname);
2840         if (existsLocal || existsInVC) {
2841                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2842                 if (kind != LV_WRITE_AS && existsInVC) {
2843                         // renaming to a name that is already in VC
2844                         // would not work
2845                         docstring text = bformat(_("The document %1$s "
2846                                         "is already registered.\n\n"
2847                                         "Do you want to choose a new name?"),
2848                                 file);
2849                         docstring const title = (kind == LV_VC_RENAME) ?
2850                                 _("Rename document?") : _("Copy document?");
2851                         docstring const button = (kind == LV_VC_RENAME) ?
2852                                 _("&Rename") : _("&Copy");
2853                         int const ret = Alert::prompt(title, text, 0, 1,
2854                                 button, _("&Cancel"));
2855                         switch (ret) {
2856                         case 0: return renameBuffer(b, docstring(), kind);
2857                         case 1: return false;
2858                         }
2859                 }
2860
2861                 if (existsLocal) {
2862                         docstring text = bformat(_("The document %1$s "
2863                                         "already exists.\n\n"
2864                                         "Do you want to overwrite that document?"),
2865                                 file);
2866                         int const ret = Alert::prompt(_("Overwrite document?"),
2867                                         text, 0, 2, _("&Overwrite"),
2868                                         _("&Rename"), _("&Cancel"));
2869                         switch (ret) {
2870                         case 0: break;
2871                         case 1: return renameBuffer(b, docstring(), kind);
2872                         case 2: return false;
2873                         }
2874                 }
2875         }
2876
2877         switch (kind) {
2878         case LV_VC_RENAME: {
2879                 string msg = b.lyxvc().rename(fname);
2880                 if (msg.empty())
2881                         return false;
2882                 message(from_utf8(msg));
2883                 break;
2884         }
2885         case LV_VC_COPY: {
2886                 string msg = b.lyxvc().copy(fname);
2887                 if (msg.empty())
2888                         return false;
2889                 message(from_utf8(msg));
2890                 break;
2891         }
2892         case LV_WRITE_AS:
2893         case LV_WRITE_AS_TEMPLATE:
2894                 break;
2895         }
2896         // LyXVC created the file already in case of LV_VC_RENAME or
2897         // LV_VC_COPY, but call saveBuffer() nevertheless to get
2898         // relative paths of included stuff right if we moved e.g. from
2899         // /a/b.lyx to /a/c/b.lyx.
2900
2901         bool const saved = saveBuffer(b, fname);
2902         if (saved)
2903                 b.reload();
2904         return saved;
2905 }
2906
2907
2908 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2909 {
2910         FileName fname = b.fileName();
2911
2912         FileDialog dlg(qt_("Choose a filename to export the document as"));
2913         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2914
2915         QStringList types;
2916         QString const anyformat = qt_("Guess from extension (*.*)");
2917         types << anyformat;
2918
2919         vector<Format const *> export_formats;
2920         for (Format const & f : theFormats())
2921                 if (f.documentFormat())
2922                         export_formats.push_back(&f);
2923         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2924         map<QString, string> fmap;
2925         QString filter;
2926         string ext;
2927         for (Format const * f : export_formats) {
2928                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2929                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2930                                                      loc_prettyname,
2931                                                      from_ascii(f->extension())));
2932                 types << loc_filter;
2933                 fmap[loc_filter] = f->name();
2934                 if (from_ascii(f->name()) == iformat) {
2935                         filter = loc_filter;
2936                         ext = f->extension();
2937                 }
2938         }
2939         string ofname = fname.onlyFileName();
2940         if (!ext.empty())
2941                 ofname = support::changeExtension(ofname, ext);
2942         FileDialog::Result result =
2943                 dlg.save(toqstr(fname.onlyPath().absFileName()),
2944                          types,
2945                          toqstr(ofname),
2946                          &filter);
2947         if (result.first != FileDialog::Chosen)
2948                 return false;
2949
2950         string fmt_name;
2951         fname.set(fromqstr(result.second));
2952         if (filter == anyformat)
2953                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2954         else
2955                 fmt_name = fmap[filter];
2956         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2957                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2958
2959         if (fmt_name.empty() || fname.empty())
2960                 return false;
2961
2962         // fname is now the new Buffer location.
2963         if (FileName(fname).exists()) {
2964                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2965                 docstring text = bformat(_("The document %1$s already "
2966                                            "exists.\n\nDo you want to "
2967                                            "overwrite that document?"),
2968                                          file);
2969                 int const ret = Alert::prompt(_("Overwrite document?"),
2970                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2971                 switch (ret) {
2972                 case 0: break;
2973                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2974                 case 2: return false;
2975                 }
2976         }
2977
2978         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2979         DispatchResult dr;
2980         dispatch(cmd, dr);
2981         return dr.dispatched();
2982 }
2983
2984
2985 bool GuiView::saveBuffer(Buffer & b)
2986 {
2987         return saveBuffer(b, FileName());
2988 }
2989
2990
2991 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2992 {
2993         if (workArea(b) && workArea(b)->inDialogMode())
2994                 return true;
2995
2996         if (fn.empty() && b.isUnnamed())
2997                 return renameBuffer(b, docstring());
2998
2999         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3000         if (success) {
3001                 theSession().lastFiles().add(b.fileName());
3002                 theSession().writeFile();
3003                 return true;
3004         }
3005
3006         // Switch to this Buffer.
3007         setBuffer(&b);
3008
3009         // FIXME: we don't tell the user *WHY* the save failed !!
3010         docstring const file = makeDisplayPath(b.absFileName(), 30);
3011         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3012                                    "Do you want to rename the document and "
3013                                    "try again?"), file);
3014         int const ret = Alert::prompt(_("Rename and save?"),
3015                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3016         switch (ret) {
3017         case 0:
3018                 if (!renameBuffer(b, docstring()))
3019                         return false;
3020                 break;
3021         case 1:
3022                 break;
3023         case 2:
3024                 return false;
3025         }
3026
3027         return saveBuffer(b, fn);
3028 }
3029
3030
3031 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3032 {
3033         return closeWorkArea(wa, false);
3034 }
3035
3036
3037 // We only want to close the buffer if it is not visible in other workareas
3038 // of the same view, nor in other views, and if this is not a child
3039 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3040 {
3041         Buffer & buf = wa->bufferView().buffer();
3042
3043         bool last_wa = d.countWorkAreasOf(buf) == 1
3044                 && !inOtherView(buf) && !buf.parent();
3045
3046         bool close_buffer = last_wa;
3047
3048         if (last_wa) {
3049                 if (lyxrc.close_buffer_with_last_view == "yes")
3050                         ; // Nothing to do
3051                 else if (lyxrc.close_buffer_with_last_view == "no")
3052                         close_buffer = false;
3053                 else {
3054                         docstring file;
3055                         if (buf.isUnnamed())
3056                                 file = from_utf8(buf.fileName().onlyFileName());
3057                         else
3058                                 file = buf.fileName().displayName(30);
3059                         docstring const text = bformat(
3060                                 _("Last view on document %1$s is being closed.\n"
3061                                   "Would you like to close or hide the document?\n"
3062                                   "\n"
3063                                   "Hidden documents can be displayed back through\n"
3064                                   "the menu: View->Hidden->...\n"
3065                                   "\n"
3066                                   "To remove this question, set your preference in:\n"
3067                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3068                                 ), file);
3069                         int ret = Alert::prompt(_("Close or hide document?"),
3070                                 text, 0, 1, _("&Close"), _("&Hide"));
3071                         close_buffer = (ret == 0);
3072                 }
3073         }
3074
3075         return closeWorkArea(wa, close_buffer);
3076 }
3077
3078
3079 bool GuiView::closeBuffer()
3080 {
3081         GuiWorkArea * wa = currentMainWorkArea();
3082         // coverity complained about this
3083         // it seems unnecessary, but perhaps is worth the check
3084         LASSERT(wa, return false);
3085
3086         setCurrentWorkArea(wa);
3087         Buffer & buf = wa->bufferView().buffer();
3088         return closeWorkArea(wa, !buf.parent());
3089 }
3090
3091
3092 void GuiView::writeSession() const {
3093         GuiWorkArea const * active_wa = currentMainWorkArea();
3094         for (int i = 0; i < d.splitter_->count(); ++i) {
3095                 TabWorkArea * twa = d.tabWorkArea(i);
3096                 for (int j = 0; j < twa->count(); ++j) {
3097                         GuiWorkArea * wa = twa->workArea(j);
3098                         Buffer & buf = wa->bufferView().buffer();
3099                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3100                 }
3101         }
3102 }
3103
3104
3105 bool GuiView::closeBufferAll()
3106 {
3107         // Close the workareas in all other views
3108         QList<int> const ids = guiApp->viewIds();
3109         for (int i = 0; i != ids.size(); ++i) {
3110                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3111                         return false;
3112         }
3113
3114         // Close our own workareas
3115         if (!closeWorkAreaAll())
3116                 return false;
3117
3118         // Now close the hidden buffers. We prevent hidden buffers from being
3119         // dirty, so we can just close them.
3120         theBufferList().closeAll();
3121         return true;
3122 }
3123
3124
3125 bool GuiView::closeWorkAreaAll()
3126 {
3127         setCurrentWorkArea(currentMainWorkArea());
3128
3129         // We might be in a situation that there is still a tabWorkArea, but
3130         // there are no tabs anymore. This can happen when we get here after a
3131         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3132         // many TabWorkArea's have no documents anymore.
3133         int empty_twa = 0;
3134
3135         // We have to call count() each time, because it can happen that
3136         // more than one splitter will disappear in one iteration (bug 5998).
3137         while (d.splitter_->count() > empty_twa) {
3138                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3139
3140                 if (twa->count() == 0)
3141                         ++empty_twa;
3142                 else {
3143                         setCurrentWorkArea(twa->currentWorkArea());
3144                         if (!closeTabWorkArea(twa))
3145                                 return false;
3146                 }
3147         }
3148         return true;
3149 }
3150
3151
3152 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3153 {
3154         if (!wa)
3155                 return false;
3156
3157         Buffer & buf = wa->bufferView().buffer();
3158
3159         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3160                 Alert::warning(_("Close document"),
3161                         _("Document could not be closed because it is being processed by LyX."));
3162                 return false;
3163         }
3164
3165         if (close_buffer)
3166                 return closeBuffer(buf);
3167         else {
3168                 if (!inMultiTabs(wa))
3169                         if (!saveBufferIfNeeded(buf, true))
3170                                 return false;
3171                 removeWorkArea(wa);
3172                 return true;
3173         }
3174 }
3175
3176
3177 bool GuiView::closeBuffer(Buffer & buf)
3178 {
3179         bool success = true;
3180         ListOfBuffers clist = buf.getChildren();
3181         ListOfBuffers::const_iterator it = clist.begin();
3182         ListOfBuffers::const_iterator const bend = clist.end();
3183         for (; it != bend; ++it) {
3184                 Buffer * child_buf = *it;
3185                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3186                         child_buf->setParent(nullptr);
3187                         continue;
3188                 }
3189
3190                 // FIXME: should we look in other tabworkareas?
3191                 // ANSWER: I don't think so. I've tested, and if the child is
3192                 // open in some other window, it closes without a problem.
3193                 GuiWorkArea * child_wa = workArea(*child_buf);
3194                 if (child_wa) {
3195                         if (closing_)
3196                                 // If we are in a close_event all children will be closed in some time,
3197                                 // so no need to do it here. This will ensure that the children end up
3198                                 // in the session file in the correct order. If we close the master
3199                                 // buffer, we can close or release the child buffers here too.
3200                                 continue;
3201                         success = closeWorkArea(child_wa, true);
3202                         if (!success)
3203                                 break;
3204                 } else {
3205                         // In this case the child buffer is open but hidden.
3206                         // Even in this case, children can be dirty (e.g.,
3207                         // after a label change in the master, see #11405).
3208                         // Therefore, check this
3209                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3210                                 // If we are in a close_event all children will be closed in some time,
3211                                 // so no need to do it here. This will ensure that the children end up
3212                                 // in the session file in the correct order. If we close the master
3213                                 // buffer, we can close or release the child buffers here too.
3214                                 continue;
3215                         // Save dirty buffers also if closing_!
3216                         if (saveBufferIfNeeded(*child_buf, false)) {
3217                                 child_buf->removeAutosaveFile();
3218                                 theBufferList().release(child_buf);
3219                         } else {
3220                                 // Saving of dirty children has been cancelled.
3221                                 // Cancel the whole process.
3222                                 success = false;
3223                                 break;
3224                         }
3225                 }
3226         }
3227         if (success) {
3228                 // goto bookmark to update bookmark pit.
3229                 // FIXME: we should update only the bookmarks related to this buffer!
3230                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3231                 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3232                         guiApp->gotoBookmark(i+1, false, false);
3233
3234                 if (saveBufferIfNeeded(buf, false)) {
3235                         buf.removeAutosaveFile();
3236                         theBufferList().release(&buf);
3237                         return true;
3238                 }
3239         }
3240         // open all children again to avoid a crash because of dangling
3241         // pointers (bug 6603)
3242         buf.updateBuffer();
3243         return false;
3244 }
3245
3246
3247 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3248 {
3249         while (twa == d.currentTabWorkArea()) {
3250                 twa->setCurrentIndex(twa->count() - 1);
3251
3252                 GuiWorkArea * wa = twa->currentWorkArea();
3253                 Buffer & b = wa->bufferView().buffer();
3254
3255                 // We only want to close the buffer if the same buffer is not visible
3256                 // in another view, and if this is not a child and if we are closing
3257                 // a view (not a tabgroup).
3258                 bool const close_buffer =
3259                         !inOtherView(b) && !b.parent() && closing_;
3260
3261                 if (!closeWorkArea(wa, close_buffer))
3262                         return false;
3263         }
3264         return true;
3265 }
3266
3267
3268 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3269 {
3270         if (buf.isClean() || buf.paragraphs().empty())
3271                 return true;
3272
3273         // Switch to this Buffer.
3274         setBuffer(&buf);
3275
3276         docstring file;
3277         bool exists;
3278         // FIXME: Unicode?
3279         if (buf.isUnnamed()) {
3280                 file = from_utf8(buf.fileName().onlyFileName());
3281                 exists = false;
3282         } else {
3283                 FileName filename = buf.fileName();
3284                 filename.refresh();
3285                 file = filename.displayName(30);
3286                 exists = filename.exists();
3287         }
3288
3289         // Bring this window to top before asking questions.
3290         raise();
3291         activateWindow();
3292
3293         int ret;
3294         if (hiding && buf.isUnnamed()) {
3295                 docstring const text = bformat(_("The document %1$s has not been "
3296                                                  "saved yet.\n\nDo you want to save "
3297                                                  "the document?"), file);
3298                 ret = Alert::prompt(_("Save new document?"),
3299                         text, 0, 1, _("&Save"), _("&Cancel"));
3300                 if (ret == 1)
3301                         ++ret;
3302         } else {
3303                 docstring const text = exists ?
3304                         bformat(_("The document %1$s has unsaved changes."
3305                                   "\n\nDo you want to save the document or "
3306                                   "discard the changes?"), file) :
3307                         bformat(_("The document %1$s has not been saved yet."
3308                                   "\n\nDo you want to save the document or "
3309                                   "discard it entirely?"), file);
3310                 docstring const title = exists ?
3311                         _("Save changed document?") : _("Save document?");
3312                 ret = Alert::prompt(title, text, 0, 2,
3313                                     _("&Save"), _("&Discard"), _("&Cancel"));
3314         }
3315
3316         switch (ret) {
3317         case 0:
3318                 if (!saveBuffer(buf))
3319                         return false;
3320                 break;
3321         case 1:
3322                 // If we crash after this we could have no autosave file
3323                 // but I guess this is really improbable (Jug).
3324                 // Sometimes improbable things happen:
3325                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3326                 // buf.removeAutosaveFile();
3327                 if (hiding)
3328                         // revert all changes
3329                         reloadBuffer(buf);
3330                 buf.markClean();
3331                 break;
3332         case 2:
3333                 return false;
3334         }
3335         return true;
3336 }
3337
3338
3339 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3340 {
3341         Buffer & buf = wa->bufferView().buffer();
3342
3343         for (int i = 0; i != d.splitter_->count(); ++i) {
3344                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3345                 if (wa_ && wa_ != wa)
3346                         return true;
3347         }
3348         return inOtherView(buf);
3349 }
3350
3351
3352 bool GuiView::inOtherView(Buffer & buf)
3353 {
3354         QList<int> const ids = guiApp->viewIds();
3355
3356         for (int i = 0; i != ids.size(); ++i) {
3357                 if (id_ == ids[i])
3358                         continue;
3359
3360                 if (guiApp->view(ids[i]).workArea(buf))
3361                         return true;
3362         }
3363         return false;
3364 }
3365
3366
3367 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3368 {
3369         if (!documentBufferView())
3370                 return;
3371
3372         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3373                 Buffer * const curbuf = &documentBufferView()->buffer();
3374                 int nwa = twa->count();
3375                 for (int i = 0; i < nwa; ++i) {
3376                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3377                                 int next_index;
3378                                 if (np == NEXTBUFFER)
3379                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3380                                 else
3381                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3382                                 if (move)
3383                                         twa->moveTab(i, next_index);
3384                                 else
3385                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3386                                 break;
3387                         }
3388                 }
3389         }
3390 }
3391
3392
3393 /// make sure the document is saved
3394 static bool ensureBufferClean(Buffer * buffer)
3395 {
3396         LASSERT(buffer, return false);
3397         if (buffer->isClean() && !buffer->isUnnamed())
3398                 return true;
3399
3400         docstring const file = buffer->fileName().displayName(30);
3401         docstring title;
3402         docstring text;
3403         if (!buffer->isUnnamed()) {
3404                 text = bformat(_("The document %1$s has unsaved "
3405                                                  "changes.\n\nDo you want to save "
3406                                                  "the document?"), file);
3407                 title = _("Save changed document?");
3408
3409         } else {
3410                 text = bformat(_("The document %1$s has not been "
3411                                                  "saved yet.\n\nDo you want to save "
3412                                                  "the document?"), file);
3413                 title = _("Save new document?");
3414         }
3415         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3416
3417         if (ret == 0)
3418                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3419
3420         return buffer->isClean() && !buffer->isUnnamed();
3421 }
3422
3423
3424 bool GuiView::reloadBuffer(Buffer & buf)
3425 {
3426         currentBufferView()->cursor().reset();
3427         Buffer::ReadStatus status = buf.reload();
3428         return status == Buffer::ReadSuccess;
3429 }
3430
3431
3432 void GuiView::checkExternallyModifiedBuffers()
3433 {
3434         BufferList::iterator bit = theBufferList().begin();
3435         BufferList::iterator const bend = theBufferList().end();
3436         for (; bit != bend; ++bit) {
3437                 Buffer * buf = *bit;
3438                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3439                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3440                                         " Reload now? Any local changes will be lost."),
3441                                         from_utf8(buf->absFileName()));
3442                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3443                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3444                         if (!ret)
3445                                 reloadBuffer(*buf);
3446                 }
3447         }
3448 }
3449
3450
3451 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3452 {
3453         Buffer * buffer = documentBufferView()
3454                 ? &(documentBufferView()->buffer()) : nullptr;
3455
3456         switch (cmd.action()) {
3457         case LFUN_VC_REGISTER:
3458                 if (!buffer || !ensureBufferClean(buffer))
3459                         break;
3460                 if (!buffer->lyxvc().inUse()) {
3461                         if (buffer->lyxvc().registrer()) {
3462                                 reloadBuffer(*buffer);
3463                                 dr.clearMessageUpdate();
3464                         }
3465                 }
3466                 break;
3467
3468         case LFUN_VC_RENAME:
3469         case LFUN_VC_COPY: {
3470                 if (!buffer || !ensureBufferClean(buffer))
3471                         break;
3472                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3473                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3474                                 // Some changes are not yet committed.
3475                                 // We test here and not in getStatus(), since
3476                                 // this test is expensive.
3477                                 string log;
3478                                 LyXVC::CommandResult ret =
3479                                         buffer->lyxvc().checkIn(log);
3480                                 dr.setMessage(log);
3481                                 if (ret == LyXVC::ErrorCommand ||
3482                                     ret == LyXVC::VCSuccess)
3483                                         reloadBuffer(*buffer);
3484                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3485                                         frontend::Alert::error(
3486                                                 _("Revision control error."),
3487                                                 _("Document could not be checked in."));
3488                                         break;
3489                                 }
3490                         }
3491                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3492                                 LV_VC_RENAME : LV_VC_COPY;
3493                         renameBuffer(*buffer, cmd.argument(), kind);
3494                 }
3495                 break;
3496         }
3497
3498         case LFUN_VC_CHECK_IN:
3499                 if (!buffer || !ensureBufferClean(buffer))
3500                         break;
3501                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3502                         string log;
3503                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3504                         dr.setMessage(log);
3505                         // Only skip reloading if the checkin was cancelled or
3506                         // an error occurred before the real checkin VCS command
3507                         // was executed, since the VCS might have changed the
3508                         // file even if it could not checkin successfully.
3509                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3510                                 reloadBuffer(*buffer);
3511                 }
3512                 break;
3513
3514         case LFUN_VC_CHECK_OUT:
3515                 if (!buffer || !ensureBufferClean(buffer))
3516                         break;
3517                 if (buffer->lyxvc().inUse()) {
3518                         dr.setMessage(buffer->lyxvc().checkOut());
3519                         reloadBuffer(*buffer);
3520                 }
3521                 break;
3522
3523         case LFUN_VC_LOCKING_TOGGLE:
3524                 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3525                         break;
3526                 if (buffer->lyxvc().inUse()) {
3527                         string res = buffer->lyxvc().lockingToggle();
3528                         if (res.empty()) {
3529                                 frontend::Alert::error(_("Revision control error."),
3530                                 _("Error when setting the locking property."));
3531                         } else {
3532                                 dr.setMessage(res);
3533                                 reloadBuffer(*buffer);
3534                         }
3535                 }
3536                 break;
3537
3538         case LFUN_VC_REVERT:
3539                 if (!buffer)
3540                         break;
3541                 if (buffer->lyxvc().revert()) {
3542                         reloadBuffer(*buffer);
3543                         dr.clearMessageUpdate();
3544                 }
3545                 break;
3546
3547         case LFUN_VC_UNDO_LAST:
3548                 if (!buffer)
3549                         break;
3550                 buffer->lyxvc().undoLast();
3551                 reloadBuffer(*buffer);
3552                 dr.clearMessageUpdate();
3553                 break;
3554
3555         case LFUN_VC_REPO_UPDATE:
3556                 if (!buffer)
3557                         break;
3558                 if (ensureBufferClean(buffer)) {
3559                         dr.setMessage(buffer->lyxvc().repoUpdate());
3560                         checkExternallyModifiedBuffers();
3561                 }
3562                 break;
3563
3564         case LFUN_VC_COMMAND: {
3565                 string flag = cmd.getArg(0);
3566                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3567                         break;
3568                 docstring message;
3569                 if (contains(flag, 'M')) {
3570                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3571                                 break;
3572                 }
3573                 string path = cmd.getArg(1);
3574                 if (contains(path, "$$p") && buffer)
3575                         path = subst(path, "$$p", buffer->filePath());
3576                 LYXERR(Debug::LYXVC, "Directory: " << path);
3577                 FileName pp(path);
3578                 if (!pp.isReadableDirectory()) {
3579                         lyxerr << _("Directory is not accessible.") << endl;
3580                         break;
3581                 }
3582                 support::PathChanger p(pp);
3583
3584                 string command = cmd.getArg(2);
3585                 if (command.empty())
3586                         break;
3587                 if (buffer) {
3588                         command = subst(command, "$$i", buffer->absFileName());
3589                         command = subst(command, "$$p", buffer->filePath());
3590                 }
3591                 command = subst(command, "$$m", to_utf8(message));
3592                 LYXERR(Debug::LYXVC, "Command: " << command);
3593                 Systemcall one;
3594                 one.startscript(Systemcall::Wait, command);
3595
3596                 if (!buffer)
3597                         break;
3598                 if (contains(flag, 'I'))
3599                         buffer->markDirty();
3600                 if (contains(flag, 'R'))
3601                         reloadBuffer(*buffer);
3602
3603                 break;
3604                 }
3605
3606         case LFUN_VC_COMPARE: {
3607                 if (cmd.argument().empty()) {
3608                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3609                         break;
3610                 }
3611                 if (!buffer)
3612                         break;
3613
3614                 string rev1 = cmd.getArg(0);
3615                 string f1, f2;
3616
3617                 // f1
3618                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3619                         break;
3620
3621                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3622                         f2 = buffer->absFileName();
3623                 } else {
3624                         string rev2 = cmd.getArg(1);
3625                         if (rev2.empty())
3626                                 break;
3627                         // f2
3628                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3629                                 break;
3630                 }
3631
3632                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3633                                         f1 << "\n"  << f2 << "\n" );
3634                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3635                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3636                 break;
3637         }
3638
3639         default:
3640                 break;
3641         }
3642 }
3643
3644
3645 void GuiView::openChildDocument(string const & fname)
3646 {
3647         LASSERT(documentBufferView(), return);
3648         Buffer & buffer = documentBufferView()->buffer();
3649         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3650         documentBufferView()->saveBookmark(false);
3651         Buffer * child = nullptr;
3652         if (theBufferList().exists(filename)) {
3653                 child = theBufferList().getBuffer(filename);
3654                 setBuffer(child);
3655         } else {
3656                 message(bformat(_("Opening child document %1$s..."),
3657                         makeDisplayPath(filename.absFileName())));
3658                 child = loadDocument(filename, false);
3659         }
3660         // Set the parent name of the child document.
3661         // This makes insertion of citations and references in the child work,
3662         // when the target is in the parent or another child document.
3663         if (child)
3664                 child->setParent(&buffer);
3665 }
3666
3667
3668 bool GuiView::goToFileRow(string const & argument)
3669 {
3670         string file_name;
3671         int row = -1;
3672         size_t i = argument.find_last_of(' ');
3673         if (i != string::npos) {
3674                 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3675                 istringstream is(argument.substr(i + 1));
3676                 is >> row;
3677                 if (is.fail())
3678                         i = string::npos;
3679         }
3680         if (i == string::npos) {
3681                 LYXERR0("Wrong argument: " << argument);
3682                 return false;
3683         }
3684         Buffer * buf = nullptr;
3685         string const realtmp = package().temp_dir().realPath();
3686         // We have to use os::path_prefix_is() here, instead of
3687         // simply prefixIs(), because the file name comes from
3688         // an external application and may need case adjustment.
3689         if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3690                 buf = theBufferList().getBufferFromTmp(file_name, true);
3691                 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3692                            << (buf ? " success" : " failed"));
3693         } else {
3694                 // Must replace extension of the file to be .lyx
3695                 // and get full path
3696                 FileName const s = fileSearch(string(),
3697                                                   support::changeExtension(file_name, ".lyx"), "lyx");
3698                 // Either change buffer or load the file
3699                 if (theBufferList().exists(s))
3700                         buf = theBufferList().getBuffer(s);
3701                 else if (s.exists()) {
3702                         buf = loadDocument(s);
3703                         if (!buf)
3704                                 return false;
3705                 } else {
3706                         message(bformat(
3707                                         _("File does not exist: %1$s"),
3708                                         makeDisplayPath(file_name)));
3709                         return false;
3710                 }
3711         }
3712         if (!buf) {
3713                 message(bformat(
3714                         _("No buffer for file: %1$s."),
3715                         makeDisplayPath(file_name))
3716                 );
3717                 return false;
3718         }
3719         setBuffer(buf);
3720         bool success = documentBufferView()->setCursorFromRow(row);
3721         if (!success) {
3722                 LYXERR(Debug::LATEX,
3723                        "setCursorFromRow: invalid position for row " << row);
3724                 frontend::Alert::error(_("Inverse Search Failed"),
3725                                        _("Invalid position requested by inverse search.\n"
3726                                          "You may need to update the viewed document."));
3727         }
3728         return success;
3729 }
3730
3731
3732 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3733 {
3734         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3735         menu->exec(QCursor::pos());
3736 }
3737
3738
3739 template<class T>
3740 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3741                 Buffer const * orig, Buffer * clone, string const & format)
3742 {
3743         Buffer::ExportStatus const status = func(format);
3744
3745         // the cloning operation will have produced a clone of the entire set of
3746         // documents, starting from the master. so we must delete those.
3747         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3748         delete mbuf;
3749         busyBuffers.remove(orig);
3750         return status;
3751 }
3752
3753
3754 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3755                 Buffer const * orig, Buffer * clone, string const & format)
3756 {
3757         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3758                         &Buffer::doExport;
3759         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3760 }
3761
3762
3763 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3764                 Buffer const * orig, Buffer * clone, string const & format)
3765 {
3766         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3767                         &Buffer::doExport;
3768         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3769 }
3770
3771
3772 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3773                 Buffer const * orig, Buffer * clone, string const & format)
3774 {
3775         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3776                         &Buffer::preview;
3777         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3778 }
3779
3780
3781 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3782                            string const & argument,
3783                            Buffer const * used_buffer,
3784                            docstring const & msg,
3785                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3786                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3787                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3788                            bool allow_async)
3789 {
3790         if (!used_buffer)
3791                 return false;
3792
3793         string format = argument;
3794         if (format.empty())
3795                 format = used_buffer->params().getDefaultOutputFormat();
3796         processing_format = format;
3797         if (!msg.empty()) {
3798                 progress_->clearMessages();
3799                 gv_->message(msg);
3800         }
3801 #if EXPORT_in_THREAD
3802         if (allow_async) {
3803                 GuiViewPrivate::busyBuffers.insert(used_buffer);
3804                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3805                 if (!cloned_buffer) {
3806                         Alert::error(_("Export Error"),
3807                                      _("Error cloning the Buffer."));
3808                         return false;
3809                 }
3810                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3811                                         asyncFunc,
3812                                         used_buffer,
3813                                         cloned_buffer,
3814                                         format);
3815                 setPreviewFuture(f);
3816                 last_export_format = used_buffer->params().bufferFormat();
3817                 (void) syncFunc;
3818                 (void) previewFunc;
3819                 // We are asynchronous, so we don't know here anything about the success
3820                 return true;
3821         } else {
3822                 Buffer::ExportStatus status;
3823                 if (syncFunc) {
3824                         status = (used_buffer->*syncFunc)(format, false);
3825                 } else if (previewFunc) {
3826                         status = (used_buffer->*previewFunc)(format);
3827                 } else
3828                         return false;
3829                 handleExportStatus(gv_, status, format);
3830                 (void) asyncFunc;
3831                 return (status == Buffer::ExportSuccess
3832                                 || status == Buffer::PreviewSuccess);
3833         }
3834 #else
3835         (void) allow_async;
3836         Buffer::ExportStatus status;
3837         if (syncFunc) {
3838                 status = (used_buffer->*syncFunc)(format, true);
3839         } else if (previewFunc) {
3840                 status = (used_buffer->*previewFunc)(format);
3841         } else
3842                 return false;
3843         handleExportStatus(gv_, status, format);
3844         (void) asyncFunc;
3845         return (status == Buffer::ExportSuccess
3846                         || status == Buffer::PreviewSuccess);
3847 #endif
3848 }
3849
3850 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3851 {
3852         BufferView * bv = currentBufferView();
3853         LASSERT(bv, return);
3854
3855         // Let the current BufferView dispatch its own actions.
3856         bv->dispatch(cmd, dr);
3857         if (dr.dispatched()) {
3858                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3859                         updateDialog("document", "");
3860                 return;
3861         }
3862
3863         // Try with the document BufferView dispatch if any.
3864         BufferView * doc_bv = documentBufferView();
3865         if (doc_bv && doc_bv != bv) {
3866                 doc_bv->dispatch(cmd, dr);
3867                 if (dr.dispatched()) {
3868                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3869                                 updateDialog("document", "");
3870                         return;
3871                 }
3872         }
3873
3874         // Then let the current Cursor dispatch its own actions.
3875         bv->cursor().dispatch(cmd);
3876
3877         // update completion. We do it here and not in
3878         // processKeySym to avoid another redraw just for a
3879         // changed inline completion
3880         if (cmd.origin() == FuncRequest::KEYBOARD) {
3881                 if (cmd.action() == LFUN_SELF_INSERT
3882                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3883                         updateCompletion(bv->cursor(), true, true);
3884                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3885                         updateCompletion(bv->cursor(), false, true);
3886                 else
3887                         updateCompletion(bv->cursor(), false, false);
3888         }
3889
3890         dr = bv->cursor().result();
3891 }
3892
3893
3894 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3895 {
3896         BufferView * bv = currentBufferView();
3897         // By default we won't need any update.
3898         dr.screenUpdate(Update::None);
3899         // assume cmd will be dispatched
3900         dr.dispatched(true);
3901
3902         Buffer * doc_buffer = documentBufferView()
3903                 ? &(documentBufferView()->buffer()) : nullptr;
3904
3905         if (cmd.origin() == FuncRequest::TOC) {
3906                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3907                 toc->doDispatch(bv->cursor(), cmd, dr);
3908                 return;
3909         }
3910
3911         string const argument = to_utf8(cmd.argument());
3912
3913         switch(cmd.action()) {
3914                 case LFUN_BUFFER_CHILD_OPEN:
3915                         openChildDocument(to_utf8(cmd.argument()));
3916                         break;
3917
3918                 case LFUN_BUFFER_IMPORT:
3919                         importDocument(to_utf8(cmd.argument()));
3920                         break;
3921
3922                 case LFUN_MASTER_BUFFER_EXPORT:
3923                         if (doc_buffer)
3924                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3925                         // fall through
3926                 case LFUN_BUFFER_EXPORT: {
3927                         if (!doc_buffer)
3928                                 break;
3929                         // GCC only sees strfwd.h when building merged
3930                         if (::lyx::operator==(cmd.argument(), "custom")) {
3931                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3932                                 // so the following test should not be needed.
3933                                 // In principle, we could try to switch to such a view...
3934                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3935                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3936                                 break;
3937                         }
3938
3939                         string const dest = cmd.getArg(1);
3940                         FileName target_dir;
3941                         if (!dest.empty() && FileName::isAbsolute(dest))
3942                                 target_dir = FileName(support::onlyPath(dest));
3943                         else
3944                                 target_dir = doc_buffer->fileName().onlyPath();
3945
3946                         string const format = (argument.empty() || argument == "default") ?
3947                                 doc_buffer->params().getDefaultOutputFormat() : argument;
3948
3949                         if ((dest.empty() && doc_buffer->isUnnamed())
3950                             || !target_dir.isDirWritable()) {
3951                                 exportBufferAs(*doc_buffer, from_utf8(format));
3952                                 break;
3953                         }
3954                         /* TODO/Review: Is it a problem to also export the children?
3955                                         See the update_unincluded flag */
3956                         d.asyncBufferProcessing(format,
3957                                                 doc_buffer,
3958                                                 _("Exporting ..."),
3959                                                 &GuiViewPrivate::exportAndDestroy,
3960                                                 &Buffer::doExport,
3961                                                 nullptr, cmd.allowAsync());
3962                         // TODO Inform user about success
3963                         break;
3964                 }
3965
3966                 case LFUN_BUFFER_EXPORT_AS: {
3967                         LASSERT(doc_buffer, break);
3968                         docstring f = cmd.argument();
3969                         if (f.empty())
3970                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3971                         exportBufferAs(*doc_buffer, f);
3972                         break;
3973                 }
3974
3975                 case LFUN_BUFFER_UPDATE: {
3976                         d.asyncBufferProcessing(argument,
3977                                                 doc_buffer,
3978                                                 _("Exporting ..."),
3979                                                 &GuiViewPrivate::compileAndDestroy,
3980                                                 &Buffer::doExport,
3981                                                 nullptr, cmd.allowAsync());
3982                         break;
3983                 }
3984                 case LFUN_BUFFER_VIEW: {
3985                         d.asyncBufferProcessing(argument,
3986                                                 doc_buffer,
3987                                                 _("Previewing ..."),
3988                                                 &GuiViewPrivate::previewAndDestroy,
3989                                                 nullptr,
3990                                                 &Buffer::preview, cmd.allowAsync());
3991                         break;
3992                 }
3993                 case LFUN_MASTER_BUFFER_UPDATE: {
3994                         d.asyncBufferProcessing(argument,
3995                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
3996                                                 docstring(),
3997                                                 &GuiViewPrivate::compileAndDestroy,
3998                                                 &Buffer::doExport,
3999                                                 nullptr, cmd.allowAsync());
4000                         break;
4001                 }
4002                 case LFUN_MASTER_BUFFER_VIEW: {
4003                         d.asyncBufferProcessing(argument,
4004                                                 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4005                                                 docstring(),
4006                                                 &GuiViewPrivate::previewAndDestroy,
4007                                                 nullptr, &Buffer::preview, cmd.allowAsync());
4008                         break;
4009                 }
4010                 case LFUN_EXPORT_CANCEL: {
4011                         Systemcall::killscript();
4012                         break;
4013                 }
4014                 case LFUN_BUFFER_SWITCH: {
4015                         string const file_name = to_utf8(cmd.argument());
4016                         if (!FileName::isAbsolute(file_name)) {
4017                                 dr.setError(true);
4018                                 dr.setMessage(_("Absolute filename expected."));
4019                                 break;
4020                         }
4021
4022                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4023                         if (!buffer) {
4024                                 dr.setError(true);
4025                                 dr.setMessage(_("Document not loaded"));
4026                                 break;
4027                         }
4028
4029                         // Do we open or switch to the buffer in this view ?
4030                         if (workArea(*buffer)
4031                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4032                                 setBuffer(buffer);
4033                                 break;
4034                         }
4035
4036                         // Look for the buffer in other views
4037                         QList<int> const ids = guiApp->viewIds();
4038                         int i = 0;
4039                         for (; i != ids.size(); ++i) {
4040                                 GuiView & gv = guiApp->view(ids[i]);
4041                                 if (gv.workArea(*buffer)) {
4042                                         gv.raise();
4043                                         gv.activateWindow();
4044                                         gv.setFocus();
4045                                         gv.setBuffer(buffer);
4046                                         break;
4047                                 }
4048                         }
4049
4050                         // If necessary, open a new window as a last resort
4051                         if (i == ids.size()) {
4052                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4053                                 lyx::dispatch(cmd);
4054                         }
4055                         break;
4056                 }
4057
4058                 case LFUN_BUFFER_NEXT:
4059                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4060                         break;
4061
4062                 case LFUN_BUFFER_MOVE_NEXT:
4063                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4064                         break;
4065
4066                 case LFUN_BUFFER_PREVIOUS:
4067                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
4068                         break;
4069
4070                 case LFUN_BUFFER_MOVE_PREVIOUS:
4071                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
4072                         break;
4073
4074                 case LFUN_BUFFER_CHKTEX:
4075                         LASSERT(doc_buffer, break);
4076                         doc_buffer->runChktex();
4077                         break;
4078
4079                 case LFUN_COMMAND_EXECUTE: {
4080                         command_execute_ = true;
4081                         minibuffer_focus_ = true;
4082                         break;
4083                 }
4084                 case LFUN_DROP_LAYOUTS_CHOICE:
4085                         d.layout_->showPopup();
4086                         break;
4087
4088                 case LFUN_MENU_OPEN:
4089                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4090                                 menu->exec(QCursor::pos());
4091                         break;
4092
4093                 case LFUN_FILE_INSERT: {
4094                         if (cmd.getArg(1) == "ignorelang")
4095                                 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4096                         else
4097                                 insertLyXFile(cmd.argument());
4098                         break;
4099                 }
4100
4101                 case LFUN_FILE_INSERT_PLAINTEXT:
4102                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4103                         string const fname = to_utf8(cmd.argument());
4104                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4105                                 dr.setMessage(_("Absolute filename expected."));
4106                                 break;
4107                         }
4108
4109                         FileName filename(fname);
4110                         if (fname.empty()) {
4111                                 FileDialog dlg(qt_("Select file to insert"));
4112
4113                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4114                                         QStringList(qt_("All Files (*)")));
4115
4116                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4117                                         dr.setMessage(_("Canceled."));
4118                                         break;
4119                                 }
4120
4121                                 filename.set(fromqstr(result.second));
4122                         }
4123
4124                         if (bv) {
4125                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4126                                 bv->dispatch(new_cmd, dr);
4127                         }
4128                         break;
4129                 }
4130
4131                 case LFUN_BUFFER_RELOAD: {
4132                         LASSERT(doc_buffer, break);
4133
4134                         // drop changes?
4135                         bool drop = (cmd.argument() == "dump");
4136
4137                         int ret = 0;
4138                         if (!drop && !doc_buffer->isClean()) {
4139                                 docstring const file =
4140                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4141                                 if (doc_buffer->notifiesExternalModification()) {
4142                                         docstring text = _("The current version will be lost. "
4143                                             "Are you sure you want to load the version on disk "
4144                                             "of the document %1$s?");
4145                                         ret = Alert::prompt(_("Reload saved document?"),
4146                                                             bformat(text, file), 1, 1,
4147                                                             _("&Reload"), _("&Cancel"));
4148                                 } else {
4149                                         docstring text = _("Any changes will be lost. "
4150                                             "Are you sure you want to revert to the saved version "
4151                                             "of the document %1$s?");
4152                                         ret = Alert::prompt(_("Revert to saved document?"),
4153                                                             bformat(text, file), 1, 1,
4154                                                             _("&Revert"), _("&Cancel"));
4155                                 }
4156                         }
4157
4158                         if (ret == 0) {
4159                                 doc_buffer->markClean();
4160                                 reloadBuffer(*doc_buffer);
4161                                 dr.forceBufferUpdate();
4162                         }
4163                         break;
4164                 }
4165
4166                 case LFUN_BUFFER_RESET_EXPORT:
4167                         LASSERT(doc_buffer, break);
4168                         doc_buffer->requireFreshStart(true);
4169                         dr.setMessage(_("Buffer export reset."));
4170                         break;
4171
4172                 case LFUN_BUFFER_WRITE:
4173                         LASSERT(doc_buffer, break);
4174                         saveBuffer(*doc_buffer);
4175                         break;
4176
4177                 case LFUN_BUFFER_WRITE_AS:
4178                         LASSERT(doc_buffer, break);
4179                         renameBuffer(*doc_buffer, cmd.argument());
4180                         break;
4181
4182                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4183                         LASSERT(doc_buffer, break);
4184                         renameBuffer(*doc_buffer, cmd.argument(),
4185                                      LV_WRITE_AS_TEMPLATE);
4186                         break;
4187
4188                 case LFUN_BUFFER_WRITE_ALL: {
4189                         Buffer * first = theBufferList().first();
4190                         if (!first)
4191                                 break;
4192                         message(_("Saving all documents..."));
4193                         // We cannot use a for loop as the buffer list cycles.
4194                         Buffer * b = first;
4195                         do {
4196                                 if (!b->isClean()) {
4197                                         saveBuffer(*b);
4198                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4199                                 }
4200                                 b = theBufferList().next(b);
4201                         } while (b != first);
4202                         dr.setMessage(_("All documents saved."));
4203                         break;
4204                 }
4205
4206                 case LFUN_MASTER_BUFFER_FORALL: {
4207                         if (!doc_buffer)
4208                                 break;
4209
4210                         FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4211                         funcToRun.allowAsync(false);
4212
4213                         for (Buffer const * buf : doc_buffer->allRelatives()) {
4214                                 // Switch to other buffer view and resend cmd
4215                                 lyx::dispatch(FuncRequest(
4216                                         LFUN_BUFFER_SWITCH, buf->absFileName()));
4217                                 lyx::dispatch(funcToRun);
4218                         }
4219                         // switch back
4220                         lyx::dispatch(FuncRequest(
4221                                 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4222                         break;
4223                 }
4224
4225                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4226                         LASSERT(doc_buffer, break);
4227                         doc_buffer->clearExternalModification();
4228                         break;
4229
4230                 case LFUN_BUFFER_CLOSE:
4231                         closeBuffer();
4232                         break;
4233
4234                 case LFUN_BUFFER_CLOSE_ALL:
4235                         closeBufferAll();
4236                         break;
4237
4238                 case LFUN_DEVEL_MODE_TOGGLE:
4239                         devel_mode_ = !devel_mode_;
4240                         if (devel_mode_)
4241                                 dr.setMessage(_("Developer mode is now enabled."));
4242                         else
4243                                 dr.setMessage(_("Developer mode is now disabled."));
4244                         break;
4245
4246                 case LFUN_TOOLBAR_TOGGLE: {
4247                         string const name = cmd.getArg(0);
4248                         if (GuiToolbar * t = toolbar(name))
4249                                 t->toggle();
4250                         break;
4251                 }
4252
4253                 case LFUN_TOOLBAR_MOVABLE: {
4254                         string const name = cmd.getArg(0);
4255                         if (name == "*") {
4256                                 // toggle (all) toolbars movablility
4257                                 toolbarsMovable_ = !toolbarsMovable_;
4258                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4259                                         GuiToolbar * tb = toolbar(ti.name);
4260                                         if (tb && tb->isMovable() != toolbarsMovable_)
4261                                                 // toggle toolbar movablity if it does not fit lock
4262                                                 // (all) toolbars positions state silent = true, since
4263                                                 // status bar notifications are slow
4264                                                 tb->movable(true);
4265                                 }
4266                                 if (toolbarsMovable_)
4267                                         dr.setMessage(_("Toolbars unlocked."));
4268                                 else
4269                                         dr.setMessage(_("Toolbars locked."));
4270                         } else if (GuiToolbar * t = toolbar(name)) {
4271                                 // toggle current toolbar movablity
4272                                 t->movable();
4273                                 // update lock (all) toolbars positions
4274                                 updateLockToolbars();
4275                         }
4276                         break;
4277                 }
4278
4279                 case LFUN_ICON_SIZE: {
4280                         QSize size = d.iconSize(cmd.argument());
4281                         setIconSize(size);
4282                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4283                                                 size.width(), size.height()));
4284                         break;
4285                 }
4286
4287                 case LFUN_DIALOG_UPDATE: {
4288                         string const name = to_utf8(cmd.argument());
4289                         if (name == "prefs" || name == "document")
4290                                 updateDialog(name, string());
4291                         else if (name == "paragraph")
4292                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4293                         else if (currentBufferView()) {
4294                                 Inset * inset = currentBufferView()->editedInset(name);
4295                                 // Can only update a dialog connected to an existing inset
4296                                 if (inset) {
4297                                         // FIXME: get rid of this indirection; GuiView ask the inset
4298                                         // if he is kind enough to update itself...
4299                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4300                                         //FIXME: pass DispatchResult here?
4301                                         inset->dispatch(currentBufferView()->cursor(), fr);
4302                                 }
4303                         }
4304                         break;
4305                 }
4306
4307                 case LFUN_DIALOG_TOGGLE: {
4308                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4309                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4310                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4311                         break;
4312                 }
4313
4314                 case LFUN_DIALOG_DISCONNECT_INSET:
4315                         disconnectDialog(to_utf8(cmd.argument()));
4316                         break;
4317
4318                 case LFUN_DIALOG_HIDE: {
4319                         guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4320                         break;
4321                 }
4322
4323                 case LFUN_DIALOG_SHOW: {
4324                         string const name = cmd.getArg(0);
4325                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4326
4327                         if (name == "latexlog") {
4328                                 // getStatus checks that
4329                                 LASSERT(doc_buffer, break);
4330                                 Buffer::LogType type;
4331                                 string const logfile = doc_buffer->logName(&type);
4332                                 switch (type) {
4333                                 case Buffer::latexlog:
4334                                         sdata = "latex ";
4335                                         break;
4336                                 case Buffer::buildlog:
4337                                         sdata = "literate ";
4338                                         break;
4339                                 }
4340                                 sdata += Lexer::quoteString(logfile);
4341                                 showDialog("log", sdata);
4342                         } else if (name == "vclog") {
4343                                 // getStatus checks that
4344                                 LASSERT(doc_buffer, break);
4345                                 string const sdata2 = "vc " +
4346                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4347                                 showDialog("log", sdata2);
4348                         } else if (name == "symbols") {
4349                                 sdata = bv->cursor().getEncoding()->name();
4350                                 if (!sdata.empty())
4351                                         showDialog("symbols", sdata);
4352                         // bug 5274
4353                         } else if (name == "prefs" && isFullScreen()) {
4354                                 lfunUiToggle("fullscreen");
4355                                 showDialog("prefs", sdata);
4356                         } else
4357                                 showDialog(name, sdata);
4358                         break;
4359                 }
4360
4361                 case LFUN_MESSAGE:
4362                         dr.setMessage(cmd.argument());
4363                         break;
4364
4365                 case LFUN_UI_TOGGLE: {
4366                         string arg = cmd.getArg(0);
4367                         if (!lfunUiToggle(arg)) {
4368                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4369                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4370                         }
4371                         // Make sure the keyboard focus stays in the work area.
4372                         setFocus();
4373                         break;
4374                 }
4375
4376                 case LFUN_VIEW_SPLIT: {
4377                         LASSERT(doc_buffer, break);
4378                         string const orientation = cmd.getArg(0);
4379                         d.splitter_->setOrientation(orientation == "vertical"
4380                                 ? Qt::Vertical : Qt::Horizontal);
4381                         TabWorkArea * twa = addTabWorkArea();
4382                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4383                         setCurrentWorkArea(wa);
4384                         break;
4385                 }
4386                 case LFUN_TAB_GROUP_CLOSE:
4387                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4388                                 closeTabWorkArea(twa);
4389                                 d.current_work_area_ = nullptr;
4390                                 twa = d.currentTabWorkArea();
4391                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4392                                 if (twa) {
4393                                         // Make sure the work area is up to date.
4394                                         setCurrentWorkArea(twa->currentWorkArea());
4395                                 } else {
4396                                         setCurrentWorkArea(nullptr);
4397                                 }
4398                         }
4399                         break;
4400
4401                 case LFUN_VIEW_CLOSE:
4402                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4403                                 closeWorkArea(twa->currentWorkArea());
4404                                 d.current_work_area_ = nullptr;
4405                                 twa = d.currentTabWorkArea();
4406                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4407                                 if (twa) {
4408                                         // Make sure the work area is up to date.
4409                                         setCurrentWorkArea(twa->currentWorkArea());
4410                                 } else {
4411                                         setCurrentWorkArea(nullptr);
4412                                 }
4413                         }
4414                         break;
4415
4416                 case LFUN_COMPLETION_INLINE:
4417                         if (d.current_work_area_)
4418                                 d.current_work_area_->completer().showInline();
4419                         break;
4420
4421                 case LFUN_COMPLETION_POPUP:
4422                         if (d.current_work_area_)
4423                                 d.current_work_area_->completer().showPopup();
4424                         break;
4425
4426
4427                 case LFUN_COMPLETE:
4428                         if (d.current_work_area_)
4429                                 d.current_work_area_->completer().tab();
4430                         break;
4431
4432                 case LFUN_COMPLETION_CANCEL:
4433                         if (d.current_work_area_) {
4434                                 if (d.current_work_area_->completer().popupVisible())
4435                                         d.current_work_area_->completer().hidePopup();
4436                                 else
4437                                         d.current_work_area_->completer().hideInline();
4438                         }
4439                         break;
4440
4441                 case LFUN_COMPLETION_ACCEPT:
4442                         if (d.current_work_area_)
4443                                 d.current_work_area_->completer().activate();
4444                         break;
4445
4446                 case LFUN_BUFFER_ZOOM_IN:
4447                 case LFUN_BUFFER_ZOOM_OUT:
4448                 case LFUN_BUFFER_ZOOM: {
4449                         if (cmd.argument().empty()) {
4450                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4451                                         zoom_ratio_ = 1.0;
4452                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4453                                         zoom_ratio_ += 0.1;
4454                                 else
4455                                         zoom_ratio_ -= 0.1;
4456                         } else {
4457                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4458                                         zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4459                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4460                                         zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4461                                 else
4462                                         zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4463                         }
4464
4465                         // Actual zoom value: default zoom + fractional extra value
4466                         int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4467                         if (zoom < static_cast<int>(zoom_min_))
4468                                 zoom = zoom_min_;
4469
4470                         lyxrc.currentZoom = zoom;
4471
4472                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4473                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4474
4475                         guiApp->fontLoader().update();
4476                         dr.screenUpdate(Update::Force | Update::FitCursor);
4477                         break;
4478                 }
4479
4480                 case LFUN_VC_REGISTER:
4481                 case LFUN_VC_RENAME:
4482                 case LFUN_VC_COPY:
4483                 case LFUN_VC_CHECK_IN:
4484                 case LFUN_VC_CHECK_OUT:
4485                 case LFUN_VC_REPO_UPDATE:
4486                 case LFUN_VC_LOCKING_TOGGLE:
4487                 case LFUN_VC_REVERT:
4488                 case LFUN_VC_UNDO_LAST:
4489                 case LFUN_VC_COMMAND:
4490                 case LFUN_VC_COMPARE:
4491                         dispatchVC(cmd, dr);
4492                         break;
4493
4494                 case LFUN_SERVER_GOTO_FILE_ROW:
4495                         if(goToFileRow(to_utf8(cmd.argument())))
4496                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4497                         break;
4498
4499                 case LFUN_LYX_ACTIVATE:
4500                         activateWindow();
4501                         break;
4502
4503                 case LFUN_FORWARD_SEARCH: {
4504                         // it seems safe to assume we have a document buffer, since
4505                         // getStatus wants one.
4506                         LASSERT(doc_buffer, break);
4507                         Buffer const * doc_master = doc_buffer->masterBuffer();
4508                         FileName const path(doc_master->temppath());
4509                         string const texname = doc_master->isChild(doc_buffer)
4510                                 ? DocFileName(changeExtension(
4511                                         doc_buffer->absFileName(),
4512                                                 "tex")).mangledFileName()
4513                                 : doc_buffer->latexName();
4514                         string const fulltexname =
4515                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4516                         string const mastername =
4517                                 removeExtension(doc_master->latexName());
4518                         FileName const dviname(addName(path.absFileName(),
4519                                         addExtension(mastername, "dvi")));
4520                         FileName const pdfname(addName(path.absFileName(),
4521                                         addExtension(mastername, "pdf")));
4522                         bool const have_dvi = dviname.exists();
4523                         bool const have_pdf = pdfname.exists();
4524                         if (!have_dvi && !have_pdf) {
4525                                 dr.setMessage(_("Please, preview the document first."));
4526                                 break;
4527                         }
4528                         string outname = dviname.onlyFileName();
4529                         string command = lyxrc.forward_search_dvi;
4530                         if (!have_dvi || (have_pdf &&
4531                             pdfname.lastModified() > dviname.lastModified())) {
4532                                 outname = pdfname.onlyFileName();
4533                                 command = lyxrc.forward_search_pdf;
4534                         }
4535
4536                         DocIterator cur = bv->cursor();
4537                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4538                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4539                                    << " cur:" << cur);
4540                         if (row == -1 || command.empty()) {
4541                                 dr.setMessage(_("Couldn't proceed."));
4542                                 break;
4543                         }
4544                         string texrow = convert<string>(row);
4545
4546                         command = subst(command, "$$n", texrow);
4547                         command = subst(command, "$$f", fulltexname);
4548                         command = subst(command, "$$t", texname);
4549                         command = subst(command, "$$o", outname);
4550
4551                         volatile PathChanger p(path);
4552                         Systemcall one;
4553                         one.startscript(Systemcall::DontWait, command);
4554                         break;
4555                 }
4556
4557                 case LFUN_SPELLING_CONTINUOUSLY:
4558                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4559                         dr.screenUpdate(Update::Force);
4560                         break;
4561
4562                 default:
4563                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4564                         // let's try that:
4565                         dispatchToBufferView(cmd, dr);
4566                         break;
4567         }
4568
4569         // Part of automatic menu appearance feature.
4570         if (isFullScreen()) {
4571                 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4572                         menuBar()->hide();
4573         }
4574
4575         // Need to update bv because many LFUNs here might have destroyed it
4576         bv = currentBufferView();
4577
4578         // Clear non-empty selections
4579         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4580         if (bv) {
4581                 Cursor & cur = bv->cursor();
4582                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4583                         cur.clearSelection();
4584                 }
4585         }
4586 }
4587
4588
4589 bool GuiView::lfunUiToggle(string const & ui_component)
4590 {
4591         if (ui_component == "scrollbar") {
4592                 // hide() is of no help
4593                 if (d.current_work_area_->verticalScrollBarPolicy() ==
4594                         Qt::ScrollBarAlwaysOff)
4595
4596                         d.current_work_area_->setVerticalScrollBarPolicy(
4597                                 Qt::ScrollBarAsNeeded);
4598                 else
4599                         d.current_work_area_->setVerticalScrollBarPolicy(
4600                                 Qt::ScrollBarAlwaysOff);
4601         } else if (ui_component == "statusbar") {
4602                 statusBar()->setVisible(!statusBar()->isVisible());
4603         } else if (ui_component == "menubar") {
4604                 menuBar()->setVisible(!menuBar()->isVisible());
4605         } else
4606         if (ui_component == "frame") {
4607                 int const l = contentsMargins().left();
4608
4609                 //are the frames in default state?
4610                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4611                 if (l == 0) {
4612                         setContentsMargins(-2, -2, -2, -2);
4613                 } else {
4614                         setContentsMargins(0, 0, 0, 0);
4615                 }
4616         } else
4617         if (ui_component == "fullscreen") {
4618                 toggleFullScreen();
4619         } else
4620                 return false;
4621         return true;
4622 }
4623
4624
4625 void GuiView::toggleFullScreen()
4626 {
4627         if (isFullScreen()) {
4628                 for (int i = 0; i != d.splitter_->count(); ++i)
4629                         d.tabWorkArea(i)->setFullScreen(false);
4630                 setContentsMargins(0, 0, 0, 0);
4631                 setWindowState(windowState() ^ Qt::WindowFullScreen);
4632                 restoreLayout();
4633                 menuBar()->show();
4634                 statusBar()->show();
4635         } else {
4636                 // bug 5274
4637                 hideDialogs("prefs", nullptr);
4638                 for (int i = 0; i != d.splitter_->count(); ++i)
4639                         d.tabWorkArea(i)->setFullScreen(true);
4640                 setContentsMargins(-2, -2, -2, -2);
4641                 saveLayout();
4642                 setWindowState(windowState() ^ Qt::WindowFullScreen);
4643                 if (lyxrc.full_screen_statusbar)
4644                         statusBar()->hide();
4645                 if (lyxrc.full_screen_menubar)
4646                         menuBar()->hide();
4647                 if (lyxrc.full_screen_toolbars) {
4648                         ToolbarMap::iterator end = d.toolbars_.end();
4649                         for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4650                                 it->second->hide();
4651                 }
4652         }
4653
4654         // give dialogs like the TOC a chance to adapt
4655         updateDialogs();
4656 }
4657
4658
4659 Buffer const * GuiView::updateInset(Inset const * inset)
4660 {
4661         if (!inset)
4662                 return nullptr;
4663
4664         Buffer const * inset_buffer = &(inset->buffer());
4665
4666         for (int i = 0; i != d.splitter_->count(); ++i) {
4667                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4668                 if (!wa)
4669                         continue;
4670                 Buffer const * buffer = &(wa->bufferView().buffer());
4671                 if (inset_buffer == buffer)
4672                         wa->scheduleRedraw(true);
4673         }
4674         return inset_buffer;
4675 }
4676
4677
4678 void GuiView::restartCaret()
4679 {
4680         /* When we move around, or type, it's nice to be able to see
4681          * the caret immediately after the keypress.
4682          */
4683         if (d.current_work_area_)
4684                 d.current_work_area_->startBlinkingCaret();
4685
4686         // Take this occasion to update the other GUI elements.
4687         updateDialogs();
4688         updateStatusBar();
4689 }
4690
4691
4692 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4693 {
4694         if (d.current_work_area_)
4695                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4696 }
4697
4698 namespace {
4699
4700 // This list should be kept in sync with the list of insets in
4701 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
4702 // dialog should have the same name as the inset.
4703 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4704 // docs in LyXAction.cpp.
4705
4706 char const * const dialognames[] = {
4707
4708 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4709 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4710 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4711 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4712 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4713 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4714 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4715 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4716
4717 char const * const * const end_dialognames =
4718         dialognames + (sizeof(dialognames) / sizeof(char *));
4719
4720 class cmpCStr {
4721 public:
4722         cmpCStr(char const * name) : name_(name) {}
4723         bool operator()(char const * other) {
4724                 return strcmp(other, name_) == 0;
4725         }
4726 private:
4727         char const * name_;
4728 };
4729
4730
4731 bool isValidName(string const & name)
4732 {
4733         return find_if(dialognames, end_dialognames,
4734                 cmpCStr(name.c_str())) != end_dialognames;
4735 }
4736
4737 } // namespace
4738
4739
4740 void GuiView::resetDialogs()
4741 {
4742         // Make sure that no LFUN uses any GuiView.
4743         guiApp->setCurrentView(nullptr);
4744         saveLayout();
4745         saveUISettings();
4746         menuBar()->clear();
4747         constructToolbars();
4748         guiApp->menus().fillMenuBar(menuBar(), this, false);
4749         d.layout_->updateContents(true);
4750         // Now update controls with current buffer.
4751         guiApp->setCurrentView(this);
4752         restoreLayout();
4753         restartCaret();
4754 }
4755
4756
4757 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4758 {
4759         if (!isValidName(name))
4760                 return nullptr;
4761
4762         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4763
4764         if (it != d.dialogs_.end()) {
4765                 if (hide_it)
4766                         it->second->hideView();
4767                 return it->second.get();
4768         }
4769
4770         Dialog * dialog = build(name);
4771         d.dialogs_[name].reset(dialog);
4772         if (lyxrc.allow_geometry_session)
4773                 dialog->restoreSession();
4774         if (hide_it)
4775                 dialog->hideView();
4776         return dialog;
4777 }
4778
4779
4780 void GuiView::showDialog(string const & name, string const & sdata,
4781         Inset * inset)
4782 {
4783         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4784 }
4785
4786
4787 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4788         Inset * inset)
4789 {
4790         if (d.in_show_)
4791                 return;
4792
4793         const string name = fromqstr(qname);
4794         const string sdata = fromqstr(qdata);
4795
4796         d.in_show_ = true;
4797         try {
4798                 Dialog * dialog = findOrBuild(name, false);
4799                 if (dialog) {
4800                         bool const visible = dialog->isVisibleView();
4801                         dialog->showData(sdata);
4802                         if (currentBufferView())
4803                                 currentBufferView()->editInset(name, inset);
4804                         // We only set the focus to the new dialog if it was not yet
4805                         // visible in order not to change the existing previous behaviour
4806                         if (visible) {
4807                                 // activateWindow is needed for floating dockviews
4808                                 dialog->asQWidget()->raise();
4809                                 dialog->asQWidget()->activateWindow();
4810                                 dialog->asQWidget()->setFocus();
4811                         }
4812                 }
4813         }
4814         catch (ExceptionMessage const & ex) {
4815                 d.in_show_ = false;
4816                 throw ex;
4817         }
4818         d.in_show_ = false;
4819 }
4820
4821
4822 bool GuiView::isDialogVisible(string const & name) const
4823 {
4824         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4825         if (it == d.dialogs_.end())
4826                 return false;
4827         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4828 }
4829
4830
4831 void GuiView::hideDialog(string const & name, Inset * inset)
4832 {
4833         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4834         if (it == d.dialogs_.end())
4835                 return;
4836
4837         if (inset) {
4838                 if (!currentBufferView())
4839                         return;
4840                 if (inset != currentBufferView()->editedInset(name))
4841                         return;
4842         }
4843
4844         Dialog * const dialog = it->second.get();
4845         if (dialog->isVisibleView())
4846                 dialog->hideView();
4847         if (currentBufferView())
4848                 currentBufferView()->editInset(name, nullptr);
4849 }
4850
4851
4852 void GuiView::disconnectDialog(string const & name)
4853 {
4854         if (!isValidName(name))
4855                 return;
4856         if (currentBufferView())
4857                 currentBufferView()->editInset(name, nullptr);
4858 }
4859
4860
4861 void GuiView::hideAll() const
4862 {
4863         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
4864         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4865
4866         for(; it != end; ++it)
4867                 it->second->hideView();
4868 }
4869
4870
4871 void GuiView::updateDialogs()
4872 {
4873         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
4874         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4875
4876         for(; it != end; ++it) {
4877                 Dialog * dialog = it->second.get();
4878                 if (dialog) {
4879                         if (dialog->needBufferOpen() && !documentBufferView())
4880                                 hideDialog(fromqstr(dialog->name()), nullptr);
4881                         else if (dialog->isVisibleView())
4882                                 dialog->checkStatus();
4883                 }
4884         }
4885         updateToolbars();
4886         updateLayoutList();
4887 }
4888
4889 Dialog * createDialog(GuiView & lv, string const & name);
4890
4891 // will be replaced by a proper factory...
4892 Dialog * createGuiAbout(GuiView & lv);
4893 Dialog * createGuiBibtex(GuiView & lv);
4894 Dialog * createGuiChanges(GuiView & lv);
4895 Dialog * createGuiCharacter(GuiView & lv);
4896 Dialog * createGuiCitation(GuiView & lv);
4897 Dialog * createGuiCompare(GuiView & lv);
4898 Dialog * createGuiCompareHistory(GuiView & lv);
4899 Dialog * createGuiDelimiter(GuiView & lv);
4900 Dialog * createGuiDocument(GuiView & lv);
4901 Dialog * createGuiErrorList(GuiView & lv);
4902 Dialog * createGuiExternal(GuiView & lv);
4903 Dialog * createGuiGraphics(GuiView & lv);
4904 Dialog * createGuiInclude(GuiView & lv);
4905 Dialog * createGuiIndex(GuiView & lv);
4906 Dialog * createGuiListings(GuiView & lv);
4907 Dialog * createGuiLog(GuiView & lv);
4908 Dialog * createGuiLyXFiles(GuiView & lv);
4909 Dialog * createGuiMathMatrix(GuiView & lv);
4910 Dialog * createGuiNote(GuiView & lv);
4911 Dialog * createGuiParagraph(GuiView & lv);
4912 Dialog * createGuiPhantom(GuiView & lv);
4913 Dialog * createGuiPreferences(GuiView & lv);
4914 Dialog * createGuiPrint(GuiView & lv);
4915 Dialog * createGuiPrintindex(GuiView & lv);
4916 Dialog * createGuiRef(GuiView & lv);
4917 Dialog * createGuiSearch(GuiView & lv);
4918 Dialog * createGuiSearchAdv(GuiView & lv);
4919 Dialog * createGuiSendTo(GuiView & lv);
4920 Dialog * createGuiShowFile(GuiView & lv);
4921 Dialog * createGuiSpellchecker(GuiView & lv);
4922 Dialog * createGuiSymbols(GuiView & lv);
4923 Dialog * createGuiTabularCreate(GuiView & lv);
4924 Dialog * createGuiTexInfo(GuiView & lv);
4925 Dialog * createGuiToc(GuiView & lv);
4926 Dialog * createGuiThesaurus(GuiView & lv);
4927 Dialog * createGuiViewSource(GuiView & lv);
4928 Dialog * createGuiWrap(GuiView & lv);
4929 Dialog * createGuiProgressView(GuiView & lv);
4930
4931
4932
4933 Dialog * GuiView::build(string const & name)
4934 {
4935         LASSERT(isValidName(name), return nullptr);
4936
4937         Dialog * dialog = createDialog(*this, name);
4938         if (dialog)
4939                 return dialog;
4940
4941         if (name == "aboutlyx")
4942                 return createGuiAbout(*this);
4943         if (name == "bibtex")
4944                 return createGuiBibtex(*this);
4945         if (name == "changes")
4946                 return createGuiChanges(*this);
4947         if (name == "character")
4948                 return createGuiCharacter(*this);
4949         if (name == "citation")
4950                 return createGuiCitation(*this);
4951         if (name == "compare")
4952                 return createGuiCompare(*this);
4953         if (name == "comparehistory")
4954                 return createGuiCompareHistory(*this);
4955         if (name == "document")
4956                 return createGuiDocument(*this);
4957         if (name == "errorlist")
4958                 return createGuiErrorList(*this);
4959         if (name == "external")
4960                 return createGuiExternal(*this);
4961         if (name == "file")
4962                 return createGuiShowFile(*this);
4963         if (name == "findreplace")
4964                 return createGuiSearch(*this);
4965         if (name == "findreplaceadv")
4966                 return createGuiSearchAdv(*this);
4967         if (name == "graphics")
4968                 return createGuiGraphics(*this);
4969         if (name == "include")
4970                 return createGuiInclude(*this);
4971         if (name == "index")
4972                 return createGuiIndex(*this);
4973         if (name == "index_print")
4974                 return createGuiPrintindex(*this);
4975         if (name == "listings")
4976                 return createGuiListings(*this);
4977         if (name == "log")
4978                 return createGuiLog(*this);
4979         if (name == "lyxfiles")
4980                 return createGuiLyXFiles(*this);
4981         if (name == "mathdelimiter")
4982                 return createGuiDelimiter(*this);
4983         if (name == "mathmatrix")
4984                 return createGuiMathMatrix(*this);
4985         if (name == "note")
4986                 return createGuiNote(*this);
4987         if (name == "paragraph")
4988                 return createGuiParagraph(*this);
4989         if (name == "phantom")
4990                 return createGuiPhantom(*this);
4991         if (name == "prefs")
4992                 return createGuiPreferences(*this);
4993         if (name == "ref")
4994                 return createGuiRef(*this);
4995         if (name == "sendto")
4996                 return createGuiSendTo(*this);
4997         if (name == "spellchecker")
4998                 return createGuiSpellchecker(*this);
4999         if (name == "symbols")
5000                 return createGuiSymbols(*this);
5001         if (name == "tabularcreate")
5002                 return createGuiTabularCreate(*this);
5003         if (name == "texinfo")
5004                 return createGuiTexInfo(*this);
5005         if (name == "thesaurus")
5006                 return createGuiThesaurus(*this);
5007         if (name == "toc")
5008                 return createGuiToc(*this);
5009         if (name == "view-source")
5010                 return createGuiViewSource(*this);
5011         if (name == "wrap")
5012                 return createGuiWrap(*this);
5013         if (name == "progress")
5014                 return createGuiProgressView(*this);
5015
5016         return nullptr;
5017 }
5018
5019
5020 SEMenu::SEMenu(QWidget * parent)
5021 {
5022         QAction * action = addAction(qt_("Disable Shell Escape"));
5023         connect(action, SIGNAL(triggered()),
5024                 parent, SLOT(disableShellEscape()));
5025 }
5026
5027 } // namespace frontend
5028 } // namespace lyx
5029
5030 #include "moc_GuiView.cpp"