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