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