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