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