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