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