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