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