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