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