]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiView.cpp
f828517b50ba0f7f52e794ee61451c15cfbb9476
[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 "FileDialog.h"
20 #include "GuiApplication.h"
21 #include "GuiWorkArea.h"
22 #include "GuiKeySymbol.h"
23 #include "GuiToolbar.h"
24 #include "GuiToolbars.h"
25 #include "Menus.h"
26
27 #include "qt_helpers.h"
28
29 #include "frontends/alert.h"
30
31 #include "buffer_funcs.h"
32 #include "Buffer.h"
33 #include "BufferList.h"
34 #include "BufferParams.h"
35 #include "BufferView.h"
36 #include "Converter.h"
37 #include "Cursor.h"
38 #include "ErrorList.h"
39 #include "Format.h"
40 #include "FuncRequest.h"
41 #include "support/gettext.h"
42 #include "Intl.h"
43 #include "Layout.h"
44 #include "Lexer.h"
45 #include "LyXFunc.h"
46 #include "LyX.h"
47 #include "LyXRC.h"
48 #include "LyXVC.h"
49 #include "Paragraph.h"
50 #include "TextClass.h"
51 #include "Text.h"
52 #include "ToolbarBackend.h"
53 #include "version.h"
54
55 #include "support/debug.h"
56 #include "support/FileFilterList.h"
57 #include "support/FileName.h"
58 #include "support/filetools.h"
59 #include "support/ForkedCalls.h"
60 #include "support/lstrings.h"
61 #include "support/os.h"
62 #include "support/Package.h"
63 #include "support/Timeout.h"
64
65 #include <QAction>
66 #include <QApplication>
67 #include <QCloseEvent>
68 #include <QDebug>
69 #include <QDesktopWidget>
70 #include <QDragEnterEvent>
71 #include <QDropEvent>
72 #include <QList>
73 #include <QMenu>
74 #include <QMenuBar>
75 #include <QPainter>
76 #include <QPixmap>
77 #include <QPoint>
78 #include <QPushButton>
79 #include <QSettings>
80 #include <QShowEvent>
81 #include <QSplitter>
82 #include <QStackedWidget>
83 #include <QStatusBar>
84 #include <QTimer>
85 #include <QToolBar>
86 #include <QUrl>
87
88 #include <boost/assert.hpp>
89 #include <boost/bind.hpp>
90
91 #ifdef HAVE_SYS_TIME_H
92 # include <sys/time.h>
93 #endif
94 #ifdef HAVE_UNISTD_H
95 # include <unistd.h>
96 #endif
97
98 using namespace std;
99 using namespace lyx::support;
100
101 namespace lyx {
102
103 extern bool quitting;
104
105 namespace frontend {
106
107 namespace {
108
109 class BackgroundWidget : public QWidget
110 {
111 public:
112         BackgroundWidget()
113         {
114                 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
115                 /// The text to be written on top of the pixmap
116                 QString const text = lyx_version ? lyx_version : qt_("unknown version");
117                 splash_ = QPixmap(":/images/banner.png");
118
119                 QPainter pain(&splash_);
120                 pain.setPen(QColor(255, 255, 0));
121                 QFont font;
122                 // The font used to display the version info
123                 font.setStyleHint(QFont::SansSerif);
124                 font.setWeight(QFont::Bold);
125                 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
126                 pain.setFont(font);
127                 pain.drawText(260, 270, text);
128         }
129
130         void paintEvent(QPaintEvent *)
131         {
132                 int x = (width() - splash_.width()) / 2;
133                 int y = (height() - splash_.height()) / 2;
134                 QPainter pain(this);
135                 pain.drawPixmap(x, y, splash_);
136         }
137
138 private:
139         QPixmap splash_;
140 };
141
142 } // namespace anon
143
144
145 typedef boost::shared_ptr<Dialog> DialogPtr;
146
147 struct GuiView::GuiViewPrivate
148 {
149         GuiViewPrivate()
150                 : current_work_area_(0), layout_(0),
151                 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
152         {
153                 // hardcode here the platform specific icon size
154                 smallIconSize = 14;     // scaling problems
155                 normalIconSize = 20;    // ok, default
156                 bigIconSize = 26;               // better for some math icons
157
158                 splitter_ = new QSplitter;
159                 bg_widget_ = new BackgroundWidget;
160                 stack_widget_ = new QStackedWidget;
161                 stack_widget_->addWidget(bg_widget_);
162                 stack_widget_->addWidget(splitter_);
163                 setBackground();
164         }
165
166         ~GuiViewPrivate()
167         {
168                 delete splitter_;
169                 delete bg_widget_;
170                 delete stack_widget_;
171                 delete toolbars_;
172         }
173
174         QMenu * toolBarPopup(GuiView * parent)
175         {
176                 // FIXME: translation
177                 QMenu * menu = new QMenu(parent);
178                 QActionGroup * iconSizeGroup = new QActionGroup(parent);
179
180                 QAction * smallIcons = new QAction(iconSizeGroup);
181                 smallIcons->setText(qt_("Small-sized icons"));
182                 smallIcons->setCheckable(true);
183                 QObject::connect(smallIcons, SIGNAL(triggered()),
184                         parent, SLOT(smallSizedIcons()));
185                 menu->addAction(smallIcons);
186
187                 QAction * normalIcons = new QAction(iconSizeGroup);
188                 normalIcons->setText(qt_("Normal-sized icons"));
189                 normalIcons->setCheckable(true);
190                 QObject::connect(normalIcons, SIGNAL(triggered()),
191                         parent, SLOT(normalSizedIcons()));
192                 menu->addAction(normalIcons);
193
194                 QAction * bigIcons = new QAction(iconSizeGroup);
195                 bigIcons->setText(qt_("Big-sized icons"));
196                 bigIcons->setCheckable(true);
197                 QObject::connect(bigIcons, SIGNAL(triggered()),
198                         parent, SLOT(bigSizedIcons()));
199                 menu->addAction(bigIcons);
200
201                 unsigned int cur = parent->iconSize().width();
202                 if ( cur == parent->d.smallIconSize)
203                         smallIcons->setChecked(true);
204                 else if (cur == parent->d.normalIconSize)
205                         normalIcons->setChecked(true);
206                 else if (cur == parent->d.bigIconSize)
207                         bigIcons->setChecked(true);
208
209                 return menu;
210         }
211
212         void setBackground()
213         {
214                 stack_widget_->setCurrentWidget(bg_widget_);
215                 bg_widget_->setUpdatesEnabled(true);
216         }
217
218         TabWorkArea * tabWorkArea(int i)
219         {
220                 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
221         }
222
223         TabWorkArea * currentTabWorkArea()
224         {
225                 if (splitter_->count() == 1)
226                         // The first TabWorkArea is always the first one, if any.
227                         return tabWorkArea(0);
228
229                 TabWorkArea * tab_widget = 0;
230                 for (int i = 0; i != splitter_->count(); ++i) {
231                         QWidget * w = splitter_->widget(i);
232                         if (!w->hasFocus())
233                                 continue;
234                         tab_widget = dynamic_cast<TabWorkArea *>(w);
235                         if (tab_widget)
236                                 break;
237                 }
238
239                 return tab_widget;
240         }
241
242 public:
243         GuiWorkArea * current_work_area_;
244         QSplitter * splitter_;
245         QStackedWidget * stack_widget_;
246         BackgroundWidget * bg_widget_;
247         /// view's toolbars
248         GuiToolbars * toolbars_;
249         /// The main layout box.
250         /** 
251          * \warning Don't Delete! The layout box is actually owned by
252          * whichever toolbar contains it. All the GuiView class needs is a
253          * means of accessing it.
254          *
255          * FIXME: replace that with a proper model so that we are not limited
256          * to only one dialog.
257          */
258         GuiLayoutBox * layout_;
259
260         ///
261         map<string, Inset *> open_insets_;
262
263         ///
264         map<string, DialogPtr> dialogs_;
265
266         unsigned int smallIconSize;
267         unsigned int normalIconSize;
268         unsigned int bigIconSize;
269         ///
270         QTimer statusbar_timer_;
271         /// are we quitting by the menu?
272         bool quitting_by_menu_;
273         /// auto-saving of buffers
274         Timeout autosave_timeout_;
275         /// flag against a race condition due to multiclicks, see bug #1119
276         bool in_show_;
277 };
278
279
280 GuiView::GuiView(int id)
281         : d(*new GuiViewPrivate), id_(id)
282 {
283         // GuiToolbars *must* be initialised before the menu bar.
284         d.toolbars_ = new GuiToolbars(*this);
285
286         // Fill up the menu bar.
287         guiApp->menus().fillMenuBar(this);
288
289         setCentralWidget(d.stack_widget_);
290
291         // Start autosave timer
292         if (lyxrc.autosave) {
293                 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
294                 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
295                 d.autosave_timeout_.start();
296         }
297         connect(&d.statusbar_timer_, SIGNAL(timeout()),
298                 this, SLOT(clearMessage()));
299
300         // Qt bug? signal lastWindowClosed does not work
301         setAttribute(Qt::WA_QuitOnClose, false);
302         setAttribute(Qt::WA_DeleteOnClose, true);
303 #ifndef Q_WS_MACX
304         // assign an icon to main form. We do not do it under Qt/Mac,
305         // since the icon is provided in the application bundle.
306         setWindowIcon(QPixmap(":/images/lyx.png"));
307 #endif
308
309         // For Drag&Drop.
310         setAcceptDrops(true);
311
312         statusBar()->setSizeGripEnabled(true);
313
314         // Forbid too small unresizable window because it can happen
315         // with some window manager under X11.
316         setMinimumSize(300, 200);
317
318         if (!lyxrc.allow_geometry_session)
319                 // No session handling, default to a sane size.
320                 setGeometry(50, 50, 690, 510);
321
322         // Now take care of session management.
323         QSettings settings;
324         QString const key = "view-" + QString::number(id_);
325 #ifdef Q_WS_X11
326         QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
327         QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
328         resize(size);
329         move(pos);
330 #else
331         if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
332                 setGeometry(50, 50, 690, 510);
333 #endif
334         setIconSize(settings.value(key + "/icon_size").toSize());
335 }
336
337
338 GuiView::~GuiView()
339 {
340         delete &d;
341 }
342
343
344 void GuiView::close()
345 {
346         d.quitting_by_menu_ = true;
347         d.current_work_area_ = 0;
348         for (int i = 0; i != d.splitter_->count(); ++i) {
349                 TabWorkArea * twa = d.tabWorkArea(i);
350                 if (twa)
351                         twa->closeAll();
352         }
353         QMainWindow::close();
354         d.quitting_by_menu_ = false;
355 }
356
357
358 void GuiView::setFocus()
359 {
360         if (d.current_work_area_)
361                 d.current_work_area_->setFocus();
362         else
363                 QWidget::setFocus();
364 }
365
366
367 QMenu * GuiView::createPopupMenu()
368 {
369         return d.toolBarPopup(this);
370 }
371
372
373 void GuiView::showEvent(QShowEvent * e)
374 {
375         LYXERR(Debug::GUI, "Passed Geometry "
376                 << size().height() << "x" << size().width()
377                 << "+" << pos().x() << "+" << pos().y());
378
379         if (d.splitter_->count() == 0)
380                 // No work area, switch to the background widget.
381                 d.setBackground();
382
383         QMainWindow::showEvent(e);
384 }
385
386
387 void GuiView::closeEvent(QCloseEvent * close_event)
388 {
389         // we may have been called through the close window button
390         // which bypasses the LFUN machinery.
391         if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
392                 if (!quitWriteAll()) {
393                         close_event->ignore();
394                         return;
395                 }
396         }
397
398         // Make sure that no LFUN use this close to be closed View.
399         theLyXFunc().setLyXView(0);
400         // Make sure the timer time out will not trigger a statusbar update.
401         d.statusbar_timer_.stop();
402
403         if (lyxrc.allow_geometry_session) {
404                 QSettings settings;
405                 QString const key = "view-" + QString::number(id_);
406 #ifdef Q_WS_X11
407                 settings.setValue(key + "/pos", pos());
408                 settings.setValue(key + "/size", size());
409 #else
410                 settings.setValue(key + "/geometry", saveGeometry());
411 #endif
412                 settings.setValue(key + "/icon_size", iconSize());
413                 d.toolbars_->saveToolbarInfo();
414                 // Now take care of all other dialogs:
415                 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
416                 for (; it!= d.dialogs_.end(); ++it)
417                         it->second->saveSession();
418         }
419
420         guiApp->unregisterView(id_);
421         if (guiApp->viewCount() > 0) {
422                 // Just close the window and do nothing else if this is not the
423                 // last window.
424                 close_event->accept();
425                 return;
426         }
427
428         quitting = true;
429
430         // this is the place where we leave the frontend.
431         // it is the only point at which we start quitting.
432         close_event->accept();
433         // quit the event loop
434         qApp->quit();
435 }
436
437
438 void GuiView::dragEnterEvent(QDragEnterEvent * event)
439 {
440         if (event->mimeData()->hasUrls())
441                 event->accept();
442         /// \todo Ask lyx-devel is this is enough:
443         /// if (event->mimeData()->hasFormat("text/plain"))
444         ///     event->acceptProposedAction();
445 }
446
447
448 void GuiView::dropEvent(QDropEvent* event)
449 {
450         QList<QUrl> files = event->mimeData()->urls();
451         if (files.isEmpty())
452                 return;
453
454         LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
455         for (int i = 0; i != files.size(); ++i) {
456                 string const file = os::internal_path(fromqstr(
457                         files.at(i).toLocalFile()));
458                 if (!file.empty())
459                         lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
460         }
461 }
462
463
464 void GuiView::message(docstring const & str)
465 {
466         if (ForkedProcess::iAmAChild())
467                 return;
468
469         statusBar()->showMessage(toqstr(str));
470         d.statusbar_timer_.stop();
471         d.statusbar_timer_.start(3000);
472 }
473
474
475 void GuiView::smallSizedIcons()
476 {
477         setIconSize(QSize(d.smallIconSize, d.smallIconSize));
478 }
479
480
481 void GuiView::normalSizedIcons()
482 {
483         setIconSize(QSize(d.normalIconSize, d.normalIconSize));
484 }
485
486
487 void GuiView::bigSizedIcons()
488 {
489         setIconSize(QSize(d.bigIconSize, d.bigIconSize));
490 }
491
492
493 void GuiView::clearMessage()
494 {
495         if (!hasFocus())
496                 return;
497         theLyXFunc().setLyXView(this);
498         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
499         d.statusbar_timer_.stop();
500 }
501
502
503 void GuiView::updateWindowTitle(GuiWorkArea * wa)
504 {
505         if (wa != d.current_work_area_)
506                 return;
507         setWindowTitle(qt_("LyX: ") + wa->windowTitle());
508         setWindowIconText(wa->windowIconText());
509 }
510
511
512 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
513 {
514         disconnectBuffer();
515         disconnectBufferView();
516         connectBufferView(wa->bufferView());
517         connectBuffer(wa->bufferView().buffer());
518         d.current_work_area_ = wa;
519         QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
520                 this, SLOT(updateWindowTitle(GuiWorkArea *)));
521         updateWindowTitle(wa);
522
523         updateToc();
524         // Buffer-dependent dialogs should be updated or
525         // hidden. This should go here because some dialogs (eg ToC)
526         // require bv_->text.
527         updateBufferDependent(true);
528         updateToolbars();
529         updateLayoutList();
530         updateStatusBar();
531 }
532
533
534 void GuiView::updateStatusBar()
535 {
536         // let the user see the explicit message
537         if (d.statusbar_timer_.isActive())
538                 return;
539
540         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
541 }
542
543
544 bool GuiView::hasFocus() const
545 {
546         return qApp->activeWindow() == this;
547 }
548
549
550 bool GuiView::event(QEvent * e)
551 {
552         switch (e->type())
553         {
554         // Useful debug code:
555         //case QEvent::ActivationChange:
556         //case QEvent::WindowDeactivate:
557         //case QEvent::Paint:
558         //case QEvent::Enter:
559         //case QEvent::Leave:
560         //case QEvent::HoverEnter:
561         //case QEvent::HoverLeave:
562         //case QEvent::HoverMove:
563         //case QEvent::StatusTip:
564         //case QEvent::DragEnter:
565         //case QEvent::DragLeave:
566         //case QEvent::Drop:
567         //      break;
568
569         case QEvent::WindowActivate: {
570                 guiApp->setCurrentView(*this);
571                 if (d.current_work_area_) {
572                         BufferView & bv = d.current_work_area_->bufferView();
573                         connectBufferView(bv);
574                         connectBuffer(bv.buffer());
575                         // The document structure, name and dialogs might have
576                         // changed in another view.
577                         updateBufferDependent(true);
578                 } else {
579                         setWindowTitle(qt_("LyX"));
580                         setWindowIconText(qt_("LyX"));
581                 }
582                 return QMainWindow::event(e);
583         }
584
585         case QEvent::ShortcutOverride: {
586                 if (d.current_work_area_)
587                         // Nothing special to do.
588                         return QMainWindow::event(e);
589
590                 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
591
592                 // Let Qt handle menu access and the Tab keys to navigate keys to navigate
593                 // between controls.
594                 if (ke->modifiers() & Qt::AltModifier || ke->key() == Qt::Key_Tab 
595                         || ke->key() == Qt::Key_Backtab)
596                         return QMainWindow::event(e);
597
598                 // Allow processing of shortcuts that are allowed even when no Buffer
599                 // is viewed.
600                 theLyXFunc().setLyXView(this);
601                 KeySymbol sym;
602                 setKeySymbol(&sym, ke);
603                 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
604                 e->accept();
605                 return true;
606         }
607
608         default:
609                 return QMainWindow::event(e);
610         }
611 }
612
613
614 bool GuiView::focusNextPrevChild(bool /*next*/)
615 {
616         setFocus();
617         return true;
618 }
619
620
621 void GuiView::setBusy(bool busy)
622 {
623         if (d.current_work_area_) {
624                 d.current_work_area_->setUpdatesEnabled(!busy);
625                 if (busy)
626                         d.current_work_area_->stopBlinkingCursor();
627                 else
628                         d.current_work_area_->startBlinkingCursor();
629         }
630
631         if (busy)
632                 QApplication::setOverrideCursor(Qt::WaitCursor);
633         else
634                 QApplication::restoreOverrideCursor();
635 }
636
637
638 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
639 {
640         GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
641
642         if (tbinfo.flags & ToolbarInfo::TOP) {
643                 if (newline)
644                         addToolBarBreak(Qt::TopToolBarArea);
645                 addToolBar(Qt::TopToolBarArea, toolBar);
646         }
647
648         if (tbinfo.flags & ToolbarInfo::BOTTOM) {
649 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
650 #if (QT_VERSION >= 0x040202)
651                 if (newline)
652                         addToolBarBreak(Qt::BottomToolBarArea);
653 #endif
654                 addToolBar(Qt::BottomToolBarArea, toolBar);
655         }
656
657         if (tbinfo.flags & ToolbarInfo::LEFT) {
658 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
659 #if (QT_VERSION >= 0x040202)
660                 if (newline)
661                         addToolBarBreak(Qt::LeftToolBarArea);
662 #endif
663                 addToolBar(Qt::LeftToolBarArea, toolBar);
664         }
665
666         if (tbinfo.flags & ToolbarInfo::RIGHT) {
667 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
668 #if (QT_VERSION >= 0x040202)
669                 if (newline)
670                         addToolBarBreak(Qt::RightToolBarArea);
671 #endif
672                 addToolBar(Qt::RightToolBarArea, toolBar);
673         }
674
675         // The following does not work so I cannot restore to exact toolbar location
676         /*
677         ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
678         toolBar->move(tbinfo.posx, tbinfo.posy);
679         */
680
681         return toolBar;
682 }
683
684
685 GuiWorkArea * GuiView::workArea(Buffer & buffer)
686 {
687         for (int i = 0; i != d.splitter_->count(); ++i) {
688                 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
689                 if (wa)
690                         return wa;
691         }
692         return 0;
693 }
694
695
696 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
697 {
698
699         // Automatically create a TabWorkArea if there are none yet.
700         if (!d.splitter_->count())
701                 addTabWorkArea();
702
703         TabWorkArea * tab_widget = d.currentTabWorkArea();
704         return tab_widget->addWorkArea(buffer, *this);
705 }
706
707
708 void GuiView::addTabWorkArea()
709 {
710         TabWorkArea * twa = new TabWorkArea;
711         QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
712                 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
713         d.splitter_->addWidget(twa);
714         d.stack_widget_->setCurrentWidget(d.splitter_);
715 }
716
717
718 GuiWorkArea const * GuiView::currentWorkArea() const
719 {
720         return d.current_work_area_;
721 }
722
723
724 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
725 {
726         BOOST_ASSERT(wa);
727
728         // Changing work area can result from opening a file so
729         // update the toc in any case.
730         updateToc();
731
732         d.current_work_area_ = wa;
733         for (int i = 0; i != d.splitter_->count(); ++i) {
734                 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
735                         return;
736         }
737 }
738
739
740 void GuiView::removeWorkArea(GuiWorkArea * wa)
741 {
742         BOOST_ASSERT(wa);
743         if (wa == d.current_work_area_) {
744                 disconnectBuffer();
745                 disconnectBufferView();
746                 hideBufferDependent();
747                 d.current_work_area_ = 0;
748         }
749
750         for (int i = 0; i != d.splitter_->count(); ++i) {
751                 TabWorkArea * twa = d.tabWorkArea(i);
752                 if (!twa->removeWorkArea(wa))
753                         // Not found in this tab group.
754                         continue;
755
756                 // We found and removed the GuiWorkArea.
757                 if (!twa->count()) {
758                         // No more WorkAreas in this tab group, so delete it.
759                         delete twa;
760                         break;
761                 }
762
763                 if (d.current_work_area_)
764                         // This means that we are not closing the current GuiWorkArea;
765                         break;
766
767                 // Switch to the next GuiWorkArea in the found TabWorkArea.
768                 d.current_work_area_ = twa->currentWorkArea();
769                 break;
770         }
771
772         if (d.splitter_->count() == 0)
773                 // No more work area, switch to the background widget.
774                 d.setBackground();
775 }
776
777
778 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
779 {
780         d.layout_ = layout;
781 }
782
783
784 void GuiView::updateLayoutList()
785 {
786         if (d.layout_)
787                 d.layout_->updateContents(false);
788 }
789
790
791 void GuiView::updateToolbars()
792 {
793         if (d.current_work_area_) {
794                 bool const math =
795                         d.current_work_area_->bufferView().cursor().inMathed();
796                 bool const table =
797                         lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
798                 bool const review =
799                         lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
800                         lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
801                 bool const mathmacrotemplate =
802                         lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
803
804                 d.toolbars_->update(math, table, review, mathmacrotemplate);
805         } else
806                 d.toolbars_->update(false, false, false, false);
807
808         // update read-only status of open dialogs.
809         checkStatus();
810 }
811
812
813 Buffer * GuiView::buffer()
814 {
815         if (d.current_work_area_)
816                 return &d.current_work_area_->bufferView().buffer();
817         return 0;
818 }
819
820
821 Buffer const * GuiView::buffer() const
822 {
823         if (d.current_work_area_)
824                 return &d.current_work_area_->bufferView().buffer();
825         return 0;
826 }
827
828
829 void GuiView::setBuffer(Buffer * newBuffer)
830 {
831         BOOST_ASSERT(newBuffer);
832         setBusy(true);
833
834         GuiWorkArea * wa = workArea(*newBuffer);
835         if (wa == 0) {
836                 updateLabels(*newBuffer->masterBuffer());
837                 wa = addWorkArea(*newBuffer);
838         } else {
839                 //Disconnect the old buffer...there's no new one.
840                 disconnectBuffer();
841         }
842         connectBuffer(*newBuffer);
843         connectBufferView(wa->bufferView());
844         setCurrentWorkArea(wa);
845
846         setBusy(false);
847 }
848
849
850 void GuiView::connectBuffer(Buffer & buf)
851 {
852         buf.setGuiDelegate(this);
853 }
854
855
856 void GuiView::disconnectBuffer()
857 {
858         if (d.current_work_area_)
859                 d.current_work_area_->bufferView().setGuiDelegate(0);
860 }
861
862
863 void GuiView::connectBufferView(BufferView & bv)
864 {
865         bv.setGuiDelegate(this);
866 }
867
868
869 void GuiView::disconnectBufferView()
870 {
871         if (d.current_work_area_)
872                 d.current_work_area_->bufferView().setGuiDelegate(0);
873 }
874
875
876 void GuiView::errors(string const & error_type)
877 {
878         ErrorList & el = buffer()->errorList(error_type);
879         if (!el.empty())
880                 showDialog("errorlist", error_type);
881 }
882
883
884 void GuiView::updateDialog(string const & name, string const & data)
885 {
886         if (!isDialogVisible(name))
887                 return;
888
889         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
890         if (it == d.dialogs_.end())
891                 return;
892
893         Dialog * const dialog = it->second.get();
894         if (dialog->isVisibleView())
895                 dialog->updateData(data);
896 }
897
898
899 BufferView * GuiView::view()
900 {
901         return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
902 }
903
904
905 void GuiView::updateToc()
906 {
907         updateDialog("toc", "");
908 }
909
910
911 void GuiView::updateEmbeddedFiles()
912 {
913         updateDialog("embedding", "");
914 }
915
916
917 void GuiView::autoSave()
918 {
919         LYXERR(Debug::INFO, "Running autoSave()");
920
921         if (buffer())
922                 view()->buffer().autoSave();
923 }
924
925
926 void GuiView::resetAutosaveTimers()
927 {
928         if (lyxrc.autosave)
929                 d.autosave_timeout_.restart();
930 }
931
932
933 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
934 {
935         FuncStatus flag;
936         bool enable = true;
937         Buffer * buf = buffer();
938
939         /* In LyX/Mac, when a dialog is open, the menus of the
940            application can still be accessed without giving focus to
941            the main window. In this case, we want to disable the menu
942            entries that are buffer-related.
943
944            Note that this code is not perfect, as bug 1941 attests:
945            http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
946         */
947         if (cmd.origin == FuncRequest::MENU && !hasFocus())
948                 buf = 0;
949
950         switch(cmd.action) {
951         case LFUN_BUFFER_WRITE:
952                 enable = buf && (buf->isUnnamed() || !buf->isClean());
953                 break;
954
955         case LFUN_BUFFER_WRITE_AS:
956                 enable = buf;
957                 break;
958
959         case LFUN_TOOLBAR_TOGGLE:
960                 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
961                 break;
962
963         case LFUN_DIALOG_TOGGLE:
964                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
965                 // fall through to set "enable"
966         case LFUN_DIALOG_SHOW: {
967                 string const name = cmd.getArg(0);
968                 if (!buf)
969                         enable = name == "aboutlyx"
970                                 || name == "file" //FIXME: should be removed.
971                                 || name == "prefs"
972                                 || name == "texinfo";
973                 else if (name == "print")
974                         enable = buf->isExportable("dvi")
975                                 && lyxrc.print_command != "none";
976                 else if (name == "character") {
977                         if (!view())
978                                 enable = false;
979                         else {
980                                 InsetCode ic = view()->cursor().inset().lyxCode();
981                                 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
982                         }
983                 }
984                 else if (name == "symbols") {
985                         if (!view() || view()->cursor().inMathed())
986                                 enable = false;
987                         else {
988                                 InsetCode ic = view()->cursor().inset().lyxCode();
989                                 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
990                         }
991                 }
992                 else if (name == "latexlog")
993                         enable = FileName(buf->logName()).isReadableFile();
994                 else if (name == "spellchecker")
995 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
996                         enable = !buf->isReadonly();
997 #else
998                         enable = false;
999 #endif
1000                 else if (name == "vclog")
1001                         enable = buf->lyxvc().inUse();
1002                 break;
1003         }
1004
1005         case LFUN_DIALOG_UPDATE: {
1006                 string const name = cmd.getArg(0);
1007                 if (!buf)
1008                         enable = name == "prefs";
1009                 break;
1010         }
1011
1012         case LFUN_INSET_APPLY: {
1013                 if (!buf) {
1014                         enable = false;
1015                         break;
1016                 }
1017                 string const name = cmd.getArg(0);
1018                 Inset * inset = getOpenInset(name);
1019                 if (inset) {
1020                         FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1021                         FuncStatus fs;
1022                         if (!inset->getStatus(view()->cursor(), fr, fs)) {
1023                                 // Every inset is supposed to handle this
1024                                 BOOST_ASSERT(false);
1025                         }
1026                         flag |= fs;
1027                 } else {
1028                         FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1029                         flag |= getStatus(fr);
1030                 }
1031                 enable = flag.enabled();
1032                 break;
1033         }
1034
1035         default:
1036                 if (!view()) {
1037                         enable = false;
1038                         break;
1039                 }
1040         }
1041
1042         if (!enable)
1043                 flag.enabled(false);
1044
1045         return flag;
1046 }
1047
1048
1049 static FileName selectTemplateFile()
1050 {
1051         FileDialog dlg(_("Select template file"));
1052         dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1053         dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1054
1055         FileDialog::Result result =
1056                 dlg.open(from_utf8(lyxrc.template_path),
1057                              FileFilterList(_("LyX Documents (*.lyx)")),
1058                              docstring());
1059
1060         if (result.first == FileDialog::Later)
1061                 return FileName();
1062         if (result.second.empty())
1063                 return FileName();
1064         return FileName(to_utf8(result.second));
1065 }
1066
1067
1068 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
1069 {
1070         setBusy(true);
1071
1072         Buffer * newBuffer = checkAndLoadLyXFile(filename);
1073
1074         if (!newBuffer) {
1075                 message(_("Document not loaded."));
1076                 setBusy(false);
1077                 return 0;
1078         }
1079
1080         setBuffer(newBuffer);
1081
1082         // scroll to the position when the file was last closed
1083         if (lyxrc.use_lastfilepos) {
1084                 LastFilePosSection::FilePos filepos =
1085                         LyX::ref().session().lastFilePos().load(filename);
1086                 view()->moveToPosition(filepos.pit, filepos.pos, 0, 0);
1087         }
1088
1089         if (tolastfiles)
1090                 LyX::ref().session().lastFiles().add(filename);
1091
1092         setBusy(false);
1093         return newBuffer;
1094 }
1095
1096
1097 void GuiView::openDocument(string const & fname)
1098 {
1099         string initpath = lyxrc.document_path;
1100
1101         if (buffer()) {
1102                 string const trypath = buffer()->filePath();
1103                 // If directory is writeable, use this as default.
1104                 if (FileName(trypath).isDirWritable())
1105                         initpath = trypath;
1106         }
1107
1108         string filename;
1109
1110         if (fname.empty()) {
1111                 FileDialog dlg(_("Select document to open"), LFUN_FILE_OPEN);
1112                 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1113                 dlg.setButton2(_("Examples|#E#e"),
1114                                 from_utf8(addPath(package().system_support().absFilename(), "examples")));
1115
1116                 FileDialog::Result result =
1117                         dlg.open(from_utf8(initpath),
1118                                      FileFilterList(_("LyX Documents (*.lyx)")),
1119                                      docstring());
1120
1121                 if (result.first == FileDialog::Later)
1122                         return;
1123
1124                 filename = to_utf8(result.second);
1125
1126                 // check selected filename
1127                 if (filename.empty()) {
1128                         message(_("Canceled."));
1129                         return;
1130                 }
1131         } else
1132                 filename = fname;
1133
1134         // get absolute path of file and add ".lyx" to the filename if
1135         // necessary. 
1136         FileName const fullname = 
1137                         fileSearch(string(), filename, "lyx", support::may_not_exist);
1138         if (!fullname.empty())
1139                 filename = fullname.absFilename();
1140
1141         // if the file doesn't exist, let the user create one
1142         if (!fullname.exists()) {
1143                 // the user specifically chose this name. Believe him.
1144                 Buffer * const b = newFile(filename, string(), true);
1145                 if (b)
1146                         setBuffer(b);
1147                 return;
1148         }
1149
1150         docstring const disp_fn = makeDisplayPath(filename);
1151         message(bformat(_("Opening document %1$s..."), disp_fn));
1152
1153         docstring str2;
1154         Buffer * buf = loadDocument(fullname);
1155         if (buf) {
1156                 updateLabels(*buf);
1157                 setBuffer(buf);
1158                 buf->errors("Parse");
1159                 str2 = bformat(_("Document %1$s opened."), disp_fn);
1160         } else {
1161                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1162         }
1163         message(str2);
1164 }
1165
1166 // FIXME: clean that
1167 static bool import(GuiView * lv, FileName const & filename,
1168         string const & format, ErrorList & errorList)
1169 {
1170         FileName const lyxfile(changeExtension(filename.absFilename(), ".lyx"));
1171
1172         string loader_format;
1173         vector<string> loaders = theConverters().loaders();
1174         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
1175                 for (vector<string>::const_iterator it = loaders.begin();
1176                      it != loaders.end(); ++it) {
1177                         if (!theConverters().isReachable(format, *it))
1178                                 continue;
1179
1180                         string const tofile =
1181                                 changeExtension(filename.absFilename(),
1182                                 formats.extension(*it));
1183                         if (!theConverters().convert(0, filename, FileName(tofile),
1184                                 filename, format, *it, errorList))
1185                                 return false;
1186                         loader_format = *it;
1187                         break;
1188                 }
1189                 if (loader_format.empty()) {
1190                         frontend::Alert::error(_("Couldn't import file"),
1191                                      bformat(_("No information for importing the format %1$s."),
1192                                          formats.prettyName(format)));
1193                         return false;
1194                 }
1195         } else
1196                 loader_format = format;
1197
1198         if (loader_format == "lyx") {
1199                 Buffer * buf = lv->loadDocument(lyxfile);
1200                 if (!buf)
1201                         return false;
1202                 updateLabels(*buf);
1203                 lv->setBuffer(buf);
1204                 buf->errors("Parse");
1205         } else {
1206                 Buffer * const b = newFile(lyxfile.absFilename(), string(), true);
1207                 if (!b)
1208                         return false;
1209                 lv->setBuffer(b);
1210                 bool as_paragraphs = loader_format == "textparagraph";
1211                 string filename2 = (loader_format == format) ? filename.absFilename()
1212                         : changeExtension(filename.absFilename(),
1213                                           formats.extension(loader_format));
1214                 lv->view()->insertPlaintextFile(FileName(filename2), as_paragraphs);
1215                 theLyXFunc().setLyXView(lv);
1216                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
1217         }
1218
1219         return true;
1220 }
1221
1222
1223 void GuiView::importDocument(string const & argument)
1224 {
1225         string format;
1226         string filename = split(argument, format, ' ');
1227
1228         LYXERR(Debug::INFO, format << " file: " << filename);
1229
1230         // need user interaction
1231         if (filename.empty()) {
1232                 string initpath = lyxrc.document_path;
1233
1234                 Buffer const * buf = buffer();
1235                 if (buf) {
1236                         string const trypath = buf->filePath();
1237                         // If directory is writeable, use this as default.
1238                         if (FileName(trypath).isDirWritable())
1239                                 initpath = trypath;
1240                 }
1241
1242                 docstring const text = bformat(_("Select %1$s file to import"),
1243                         formats.prettyName(format));
1244
1245                 FileDialog dlg(text, LFUN_BUFFER_IMPORT);
1246                 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1247                 dlg.setButton2(_("Examples|#E#e"),
1248                         from_utf8(addPath(package().system_support().absFilename(), "examples")));
1249
1250                 docstring filter = formats.prettyName(format);
1251                 filter += " (*.";
1252                 // FIXME UNICODE
1253                 filter += from_utf8(formats.extension(format));
1254                 filter += ')';
1255
1256                 FileDialog::Result result =
1257                         dlg.open(from_utf8(initpath),
1258                                      FileFilterList(filter),
1259                                      docstring());
1260
1261                 if (result.first == FileDialog::Later)
1262                         return;
1263
1264                 filename = to_utf8(result.second);
1265
1266                 // check selected filename
1267                 if (filename.empty())
1268                         message(_("Canceled."));
1269         }
1270
1271         if (filename.empty())
1272                 return;
1273
1274         // get absolute path of file
1275         FileName const fullname(makeAbsPath(filename));
1276
1277         FileName const lyxfile(changeExtension(fullname.absFilename(), ".lyx"));
1278
1279         // Check if the document already is open
1280         Buffer * buf = theBufferList().getBuffer(lyxfile.absFilename());
1281         if (buf) {
1282                 setBuffer(buf);
1283                 if (!closeBuffer()) {
1284                         message(_("Canceled."));
1285                         return;
1286                 }
1287         }
1288
1289         docstring const displaypath = makeDisplayPath(lyxfile.absFilename(), 30);
1290
1291         // if the file exists already, and we didn't do
1292         // -i lyx thefile.lyx, warn
1293         if (lyxfile.exists() && fullname != lyxfile) {
1294
1295                 docstring text = bformat(_("The document %1$s already exists.\n\n"
1296                         "Do you want to overwrite that document?"), displaypath);
1297                 int const ret = Alert::prompt(_("Overwrite document?"),
1298                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
1299
1300                 if (ret == 1) {
1301                         message(_("Canceled."));
1302                         return;
1303                 }
1304         }
1305
1306         message(bformat(_("Importing %1$s..."), displaypath));
1307         ErrorList errorList;
1308         if (import(this, fullname, format, errorList))
1309                 message(_("imported."));
1310         else
1311                 message(_("file not imported!"));
1312
1313         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
1314 }
1315
1316
1317 void GuiView::newDocument(string const & filename, bool from_template)
1318 {
1319         FileName initpath(lyxrc.document_path);
1320         Buffer * buf = buffer();
1321         if (buf) {
1322                 FileName const trypath(buf->filePath());
1323                 // If directory is writeable, use this as default.
1324                 if (trypath.isDirWritable())
1325                         initpath = trypath;
1326         }
1327
1328         string templatefile = from_template ?
1329                 selectTemplateFile().absFilename() : string();
1330         Buffer * b;
1331         if (filename.empty())
1332                 b = newUnnamedFile(templatefile, initpath);
1333         else
1334                 b = newFile(filename, templatefile, true);
1335
1336         if (b)
1337                 setBuffer(b);
1338         // Ensure the cursor is correctly positionned on screen.
1339         view()->showCursor();
1340 }
1341
1342
1343 void GuiView::insertLyXFile(docstring const & fname)
1344 {
1345         BufferView * bv = view();
1346         if (!bv)
1347                 return;
1348
1349         // FIXME UNICODE
1350         FileName filename(to_utf8(fname));
1351         
1352         if (!filename.empty()) {
1353                 bv->insertLyXFile(filename);
1354                 return;
1355         }
1356
1357         // Launch a file browser
1358         // FIXME UNICODE
1359         string initpath = lyxrc.document_path;
1360         string const trypath = bv->buffer().filePath();
1361         // If directory is writeable, use this as default.
1362         if (FileName(trypath).isDirWritable())
1363                 initpath = trypath;
1364
1365         // FIXME UNICODE
1366         FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1367         dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1368         dlg.setButton2(_("Examples|#E#e"),
1369                 from_utf8(addPath(package().system_support().absFilename(),
1370                 "examples")));
1371
1372         FileDialog::Result result =
1373                 dlg.open(from_utf8(initpath),
1374                              FileFilterList(_("LyX Documents (*.lyx)")),
1375                              docstring());
1376
1377         if (result.first == FileDialog::Later)
1378                 return;
1379
1380         // FIXME UNICODE
1381         filename.set(to_utf8(result.second));
1382
1383         // check selected filename
1384         if (filename.empty()) {
1385                 // emit message signal.
1386                 message(_("Canceled."));
1387                 return;
1388         }
1389
1390         bv->insertLyXFile(filename);
1391 }
1392
1393
1394 void GuiView::insertPlaintextFile(docstring const & fname,
1395         bool asParagraph)
1396 {
1397         BufferView * bv = view();
1398         if (!bv)
1399                 return;
1400
1401         // FIXME UNICODE
1402         FileName filename(to_utf8(fname));
1403         
1404         if (!filename.empty()) {
1405                 bv->insertPlaintextFile(filename, asParagraph);
1406                 return;
1407         }
1408
1409         FileDialog dlg(_("Select file to insert"), (asParagraph ?
1410                 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1411
1412         FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1413                 FileFilterList(), docstring());
1414
1415         if (result.first == FileDialog::Later)
1416                 return;
1417
1418         // FIXME UNICODE
1419         filename.set(to_utf8(result.second));
1420
1421         // check selected filename
1422         if (filename.empty()) {
1423                 // emit message signal.
1424                 message(_("Canceled."));
1425                 return;
1426         }
1427
1428         bv->insertPlaintextFile(filename, asParagraph);
1429 }
1430
1431
1432 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1433 {
1434         FileName fname = b.fileName();
1435         FileName const oldname = fname;
1436
1437         if (!newname.empty()) {
1438                 // FIXME UNICODE
1439                 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1440         } else {
1441                 // Switch to this Buffer.
1442                 setBuffer(&b);
1443
1444                 /// No argument? Ask user through dialog.
1445                 // FIXME UNICODE
1446                 FileDialog dlg(_("Choose a filename to save document as"),
1447                                    LFUN_BUFFER_WRITE_AS);
1448                 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1449                 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1450
1451                 if (!isLyXFilename(fname.absFilename()))
1452                         fname.changeExtension(".lyx");
1453
1454                 FileFilterList const filter(_("LyX Documents (*.lyx)"));
1455
1456                 FileDialog::Result result =
1457                         dlg.save(from_utf8(fname.onlyPath().absFilename()),
1458                                      filter,
1459                                      from_utf8(fname.onlyFileName()));
1460
1461                 if (result.first == FileDialog::Later)
1462                         return false;
1463
1464                 fname.set(to_utf8(result.second));
1465
1466                 if (fname.empty())
1467                         return false;
1468
1469                 if (!isLyXFilename(fname.absFilename()))
1470                         fname.changeExtension(".lyx");
1471         }
1472
1473         if (FileName(fname).exists()) {
1474                 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1475                 docstring text = bformat(_("The document %1$s already "
1476                                            "exists.\n\nDo you want to "
1477                                            "overwrite that document?"), 
1478                                          file);
1479                 int const ret = Alert::prompt(_("Overwrite document?"),
1480                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1481                 switch (ret) {
1482                 case 0: break;
1483                 case 1: return renameBuffer(b, docstring());
1484                 case 2: return false;
1485                 }
1486         }
1487
1488         // Ok, change the name of the buffer
1489         b.setFileName(fname.absFilename());
1490         b.markDirty();
1491         bool unnamed = b.isUnnamed();
1492         b.setUnnamed(false);
1493         b.saveCheckSum(fname);
1494
1495         if (!saveBuffer(b)) {
1496                 b.setFileName(oldname.absFilename());
1497                 b.setUnnamed(unnamed);
1498                 b.saveCheckSum(oldname);
1499                 return false;
1500         }
1501
1502         return true;
1503 }
1504
1505
1506 bool GuiView::saveBuffer(Buffer & b)
1507 {
1508         if (b.isUnnamed())
1509                 return renameBuffer(b, docstring());
1510
1511         if (b.save()) {
1512                 LyX::ref().session().lastFiles().add(b.fileName());
1513                 return true;
1514         }
1515
1516         // Switch to this Buffer.
1517         setBuffer(&b);
1518
1519         // FIXME: we don't tell the user *WHY* the save failed !!
1520         docstring const file = makeDisplayPath(b.absFileName(), 30);
1521         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1522                                    "Do you want to rename the document and "
1523                                    "try again?"), file);
1524         int const ret = Alert::prompt(_("Rename and save?"),
1525                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1526         switch (ret) {
1527         case 0:
1528                 if (!renameBuffer(b, docstring()))
1529                         return false;
1530                 break;
1531         case 1:
1532                 break;
1533         case 2:
1534                 return false;
1535         }
1536
1537         return saveBuffer(b);
1538 }
1539
1540
1541 bool GuiView::closeBuffer()
1542 {
1543         Buffer * buf = buffer();
1544         return buf && closeBuffer(*buf);
1545 }
1546
1547
1548 bool GuiView::closeBuffer(Buffer & buf)
1549 {
1550         if (buf.isClean() || buf.paragraphs().empty()) {
1551                 theBufferList().release(&buf);
1552                 return true;
1553         }
1554         // Switch to this Buffer.
1555         setBuffer(&buf);
1556
1557         docstring file;
1558         // FIXME: Unicode?
1559         if (buf.isUnnamed())
1560                 file = from_utf8(buf.fileName().onlyFileName());
1561         else
1562                 file = buf.fileName().displayName(30);
1563
1564         docstring const text = bformat(_("The document %1$s has unsaved changes."
1565                 "\n\nDo you want to save the document or discard the changes?"), file);
1566         int const ret = Alert::prompt(_("Save changed document?"),
1567                 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1568
1569         switch (ret) {
1570         case 0:
1571                 if (!saveBuffer(buf))
1572                         return false;
1573                 break;
1574         case 1:
1575                 // if we crash after this we could
1576                 // have no autosave file but I guess
1577                 // this is really improbable (Jug)
1578                 removeAutosaveFile(buf.absFileName());
1579                 break;
1580         case 2:
1581                 return false;
1582         }
1583
1584         // save file names to .lyx/session
1585         // if master/slave are both open, do not save slave since it
1586         // will be automatically loaded when the master is loaded
1587         if (buf.masterBuffer() == &buf)
1588                 LyX::ref().session().lastOpened().add(buf.fileName());
1589
1590         theBufferList().release(&buf);
1591         return true;
1592 }
1593
1594
1595 bool GuiView::quitWriteAll()
1596 {
1597         while (!theBufferList().empty()) {
1598                 Buffer * b = theBufferList().first();
1599                 if (!closeBuffer(*b))
1600                         return false;
1601         }
1602         return true;
1603 }
1604
1605
1606 bool GuiView::dispatch(FuncRequest const & cmd)
1607 {
1608         BufferView * bv = view();       
1609         // By default we won't need any update.
1610         if (bv)
1611                 bv->cursor().updateFlags(Update::None);
1612
1613         switch(cmd.action) {
1614                 case LFUN_FILE_OPEN:
1615                         openDocument(to_utf8(cmd.argument()));
1616                         break;
1617
1618                 case LFUN_BUFFER_IMPORT:
1619                         importDocument(to_utf8(cmd.argument()));
1620                         break;
1621
1622                 case LFUN_BUFFER_SWITCH:
1623                         setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1624                         break;
1625
1626                 case LFUN_BUFFER_NEXT:
1627                         setBuffer(theBufferList().next(buffer()));
1628                         break;
1629
1630                 case LFUN_BUFFER_PREVIOUS:
1631                         setBuffer(theBufferList().previous(buffer()));
1632                         break;
1633
1634                 case LFUN_COMMAND_EXECUTE: {
1635                         bool const show_it = cmd.argument() != "off";
1636                         d.toolbars_->showCommandBuffer(show_it);
1637                         break;
1638                 }
1639                 case LFUN_DROP_LAYOUTS_CHOICE:
1640                         if (d.layout_)
1641                                 d.layout_->showPopup();
1642                         break;
1643
1644                 case LFUN_MENU_OPEN:
1645                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument())))
1646                                 menu->exec(QCursor::pos());
1647                         break;
1648
1649                 case LFUN_FILE_INSERT:
1650                         insertLyXFile(cmd.argument());
1651                         break;
1652                 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1653                         insertPlaintextFile(cmd.argument(), true);
1654                         break;
1655
1656                 case LFUN_FILE_INSERT_PLAINTEXT:
1657                         insertPlaintextFile(cmd.argument(), false);
1658                         break;
1659
1660                 case LFUN_BUFFER_WRITE:
1661                         if (bv)
1662                                 saveBuffer(bv->buffer());
1663                         break;
1664
1665                 case LFUN_BUFFER_WRITE_AS:
1666                         if (bv)
1667                                 renameBuffer(bv->buffer(), cmd.argument());
1668                         break;
1669
1670                 case LFUN_BUFFER_WRITE_ALL: {
1671                         Buffer * first = theBufferList().first();
1672                         if (!first)
1673                                 break;
1674                         message(_("Saving all documents..."));
1675                         // We cannot use a for loop as the buffer list cycles.
1676                         Buffer * b = first;
1677                         do {
1678                                 if (b->isClean())
1679                                         continue;
1680                                 saveBuffer(*b);
1681                                 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1682                                 b = theBufferList().next(b);
1683                         } while (b != first); 
1684                         message(_("All documents saved."));
1685                         break;
1686                 }
1687
1688                 case LFUN_TOOLBAR_TOGGLE: {
1689                         string const name = cmd.getArg(0);
1690                         bool const allowauto = cmd.getArg(1) == "allowauto";
1691                         // it is possible to get current toolbar status like this,...
1692                         // but I decide to obey the order of ToolbarBackend::flags
1693                         // and disregard real toolbar status.
1694                         // toolbars_->saveToolbarInfo();
1695                         //
1696                         // toggle state on/off/auto
1697                         d.toolbars_->toggleToolbarState(name, allowauto);
1698                         // update toolbar
1699                         updateToolbars();
1700
1701                         ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1702                         if (!tbi) {
1703                                 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1704                                 break;
1705                         }
1706                         docstring state;
1707                         if (tbi->flags & ToolbarInfo::ON)
1708                                 state = _("on");
1709                         else if (tbi->flags & ToolbarInfo::OFF)
1710                                 state = _("off");
1711                         else if (tbi->flags & ToolbarInfo::AUTO)
1712                                 state = _("auto");
1713
1714                         message(bformat(_("Toolbar \"%1$s\" state set to %2$s"), 
1715                                            _(tbi->gui_name), state));
1716                         break;
1717                 }
1718
1719                 case LFUN_DIALOG_UPDATE: {
1720                         string const name = to_utf8(cmd.argument());
1721                         // Can only update a dialog connected to an existing inset
1722                         Inset * inset = getOpenInset(name);
1723                         if (inset) {
1724                                 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1725                                 inset->dispatch(view()->cursor(), fr);
1726                         } else if (name == "paragraph") {
1727                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1728                         } else if (name == "prefs") {
1729                                 updateDialog(name, string());
1730                         }
1731                         break;
1732                 }
1733
1734                 case LFUN_DIALOG_TOGGLE: {
1735                         if (isDialogVisible(cmd.getArg(0)))
1736                                 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1737                         else
1738                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1739                         break;
1740                 }
1741
1742                 case LFUN_DIALOG_DISCONNECT_INSET:
1743                         disconnectDialog(to_utf8(cmd.argument()));
1744                         break;
1745
1746                 case LFUN_DIALOG_HIDE: {
1747                         if (quitting)
1748                                 break;
1749                         guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1750                         break;
1751                 }
1752
1753                 case LFUN_DIALOG_SHOW: {
1754                         string const name = cmd.getArg(0);
1755                         string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1756
1757                         if (name == "character") {
1758                                 data = freefont2string();
1759                                 if (!data.empty())
1760                                         showDialog("character", data);
1761                         } else if (name == "latexlog") {
1762                                 Buffer::LogType type; 
1763                                 string const logfile = buffer()->logName(&type);
1764                                 switch (type) {
1765                                 case Buffer::latexlog:
1766                                         data = "latex ";
1767                                         break;
1768                                 case Buffer::buildlog:
1769                                         data = "literate ";
1770                                         break;
1771                                 }
1772                                 data += Lexer::quoteString(logfile);
1773                                 showDialog("log", data);
1774                         } else if (name == "vclog") {
1775                                 string const data = "vc " +
1776                                         Lexer::quoteString(buffer()->lyxvc().getLogFile());
1777                                 showDialog("log", data);
1778                         } else if (name == "symbols") {
1779                                 data = bv->cursor().getEncoding()->name();
1780                                 if (!data.empty())
1781                                         showDialog("symbols", data);
1782                         } else
1783                                 showDialog(name, data);
1784                         break;
1785                 }
1786
1787                 case LFUN_INSET_APPLY: {
1788                         string const name = cmd.getArg(0);
1789                         Inset * inset = getOpenInset(name);
1790                         if (inset) {
1791                                 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1792                                 inset->dispatch(view()->cursor(), fr);
1793                         } else {
1794                                 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1795                                 lyx::dispatch(fr);
1796                         }
1797                         break;
1798                 }
1799
1800                 case LFUN_MENUBAR_TOGGLE:
1801                         menuBar()->setVisible(!menuBar()->isVisible());
1802                         break;
1803
1804                 case LFUN_STATUSBAR_TOGGLE:
1805                         statusBar()->setVisible(!statusBar()->isVisible());
1806                         break;
1807
1808                 default:
1809                         return false;
1810         }
1811
1812         return true;
1813 }
1814
1815
1816 Buffer const * GuiView::updateInset(Inset const * inset)
1817 {
1818         if (!d.current_work_area_)
1819                 return 0;
1820
1821         if (inset)
1822                 d.current_work_area_->scheduleRedraw();
1823
1824         return &d.current_work_area_->bufferView().buffer();
1825 }
1826
1827
1828 void GuiView::restartCursor()
1829 {
1830         /* When we move around, or type, it's nice to be able to see
1831          * the cursor immediately after the keypress.
1832          */
1833         if (d.current_work_area_)
1834                 d.current_work_area_->startBlinkingCursor();
1835
1836         // Take this occasion to update the toobars and layout list.
1837         updateLayoutList();
1838         updateToolbars();
1839 }
1840
1841 namespace {
1842
1843 // This list should be kept in sync with the list of insets in
1844 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
1845 // dialog should have the same name as the inset.
1846
1847 char const * const dialognames[] = {
1848 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1849 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1850 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1851 "mathdelimiter", "mathmatrix", "note", "paragraph", "prefs", "print", 
1852 "ref", "sendto", "spellchecker", "symbols", "tabular", "tabularcreate",
1853
1854 #ifdef HAVE_LIBAIKSAURUS
1855 "thesaurus",
1856 #endif
1857
1858 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1859
1860 char const * const * const end_dialognames =
1861         dialognames + (sizeof(dialognames) / sizeof(char *));
1862
1863 class cmpCStr {
1864 public:
1865         cmpCStr(char const * name) : name_(name) {}
1866         bool operator()(char const * other) {
1867                 return strcmp(other, name_) == 0;
1868         }
1869 private:
1870         char const * name_;
1871 };
1872
1873
1874 bool isValidName(string const & name)
1875 {
1876         return find_if(dialognames, end_dialognames,
1877                             cmpCStr(name.c_str())) != end_dialognames;
1878 }
1879
1880 } // namespace anon
1881
1882
1883 void GuiView::resetDialogs()
1884 {
1885         // Make sure that no LFUN uses any LyXView.
1886         theLyXFunc().setLyXView(0);
1887         // FIXME: the "math panels" toolbar takes an awful lot of time to
1888         // initialise so we don't do that for the time being.
1889         //d.toolbars_->init();
1890         guiApp->menus().fillMenuBar(this);
1891         if (d.layout_)
1892                 d.layout_->updateContents(true);
1893         // Now update controls with current buffer.
1894         theLyXFunc().setLyXView(this);
1895         restartCursor();
1896 }
1897
1898
1899 Dialog * GuiView::find_or_build(string const & name)
1900 {
1901         if (!isValidName(name))
1902                 return 0;
1903
1904         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1905
1906         if (it != d.dialogs_.end())
1907                 return it->second.get();
1908
1909         Dialog * dialog = build(name);
1910         d.dialogs_[name].reset(dialog);
1911         if (lyxrc.allow_geometry_session)
1912                 dialog->restoreSession();
1913         return dialog;
1914 }
1915
1916
1917 void GuiView::showDialog(string const & name, string const & data,
1918         Inset * inset)
1919 {
1920         if (d.in_show_)
1921                 return;
1922
1923         d.in_show_ = true;
1924         Dialog * dialog = find_or_build(name);
1925         if (dialog) {
1926                 dialog->showData(data);
1927                 if (inset)
1928                         d.open_insets_[name] = inset;
1929         }
1930         d.in_show_ = false;
1931 }
1932
1933
1934 bool GuiView::isDialogVisible(string const & name) const
1935 {
1936         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1937         if (it == d.dialogs_.end())
1938                 return false;
1939         return it->second.get()->isVisibleView();
1940 }
1941
1942
1943 void GuiView::hideDialog(string const & name, Inset * inset)
1944 {
1945         // Don't send the signal if we are quitting, because on MSVC it is
1946         // destructed before the cut stack in CutAndPaste.cpp, and this method
1947         // is called from some inset destructor if the cut stack is not empty
1948         // on exit.
1949         if (quitting)
1950                 return;
1951
1952         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1953         if (it == d.dialogs_.end())
1954                 return;
1955
1956         if (inset && inset != getOpenInset(name))
1957                 return;
1958
1959         Dialog * const dialog = it->second.get();
1960         if (dialog->isVisibleView())
1961                 dialog->hideView();
1962         d.open_insets_[name] = 0;
1963 }
1964
1965
1966 void GuiView::disconnectDialog(string const & name)
1967 {
1968         if (!isValidName(name))
1969                 return;
1970
1971         if (d.open_insets_.find(name) != d.open_insets_.end())
1972                 d.open_insets_[name] = 0;
1973 }
1974
1975
1976 Inset * GuiView::getOpenInset(string const & name) const
1977 {
1978         if (!isValidName(name))
1979                 return 0;
1980
1981         map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1982         return it == d.open_insets_.end() ? 0 : it->second;
1983 }
1984
1985
1986 void GuiView::hideAll() const
1987 {
1988         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
1989         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1990
1991         for(; it != end; ++it)
1992                 it->second->hideView();
1993 }
1994
1995
1996 void GuiView::hideBufferDependent() const
1997 {
1998         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
1999         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2000
2001         for(; it != end; ++it) {
2002                 Dialog * dialog = it->second.get();
2003                 if (dialog->isBufferDependent())
2004                         dialog->hideView();
2005         }
2006 }
2007
2008
2009 void GuiView::updateBufferDependent(bool switched) const
2010 {
2011         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
2012         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2013
2014         for(; it != end; ++it) {
2015                 Dialog * dialog = it->second.get();
2016                 if (!dialog->isVisibleView())
2017                         continue;
2018                 if (switched && dialog->isBufferDependent()) {
2019                         if (dialog->initialiseParams(""))
2020                                 dialog->updateView();
2021                         else
2022                                 dialog->hideView();
2023                 } else {
2024                         // A bit clunky, but the dialog will request
2025                         // that the kernel provides it with the necessary
2026                         // data.
2027                         dialog->updateDialog();
2028                 }
2029         }
2030 }
2031
2032
2033 void GuiView::checkStatus()
2034 {
2035         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
2036         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2037
2038         for(; it != end; ++it) {
2039                 Dialog * const dialog = it->second.get();
2040                 if (dialog && dialog->isVisibleView())
2041                         dialog->checkStatus();
2042         }
2043 }
2044
2045
2046
2047 // will be replaced by a proper factory...
2048 Dialog * createGuiAbout(GuiView & lv);
2049 Dialog * createGuiBibitem(GuiView & lv);
2050 Dialog * createGuiBibtex(GuiView & lv);
2051 Dialog * createGuiBox(GuiView & lv);
2052 Dialog * createGuiBranch(GuiView & lv);
2053 Dialog * createGuiChanges(GuiView & lv);
2054 Dialog * createGuiCharacter(GuiView & lv);
2055 Dialog * createGuiCitation(GuiView & lv);
2056 Dialog * createGuiDelimiter(GuiView & lv);
2057 Dialog * createGuiDocument(GuiView & lv);
2058 Dialog * createGuiErrorList(GuiView & lv);
2059 Dialog * createGuiERT(GuiView & lv);
2060 Dialog * createGuiExternal(GuiView & lv);
2061 Dialog * createGuiFloat(GuiView & lv);
2062 Dialog * createGuiGraphics(GuiView & lv);
2063 Dialog * createGuiInclude(GuiView & lv);
2064 Dialog * createGuiIndex(GuiView & lv);
2065 Dialog * createGuiLabel(GuiView & lv);
2066 Dialog * createGuiListings(GuiView & lv);
2067 Dialog * createGuiLog(GuiView & lv);
2068 Dialog * createGuiMathMatrix(GuiView & lv);
2069 Dialog * createGuiNomenclature(GuiView & lv);
2070 Dialog * createGuiNote(GuiView & lv);
2071 Dialog * createGuiParagraph(GuiView & lv);
2072 Dialog * createGuiPreferences(GuiView & lv);
2073 Dialog * createGuiPrint(GuiView & lv);
2074 Dialog * createGuiRef(GuiView & lv);
2075 Dialog * createGuiSearch(GuiView & lv);
2076 Dialog * createGuiSendTo(GuiView & lv);
2077 Dialog * createGuiShowFile(GuiView & lv);
2078 Dialog * createGuiSpellchecker(GuiView & lv);
2079 Dialog * createGuiSymbols(GuiView & lv);
2080 Dialog * createGuiTabularCreate(GuiView & lv);
2081 Dialog * createGuiTabular(GuiView & lv);
2082 Dialog * createGuiTexInfo(GuiView & lv);
2083 Dialog * createGuiToc(GuiView & lv);
2084 Dialog * createGuiThesaurus(GuiView & lv);
2085 Dialog * createGuiHyperlink(GuiView & lv);
2086 Dialog * createGuiVSpace(GuiView & lv);
2087 Dialog * createGuiViewSource(GuiView & lv);
2088 Dialog * createGuiWrap(GuiView & lv);
2089
2090
2091 Dialog * GuiView::build(string const & name)
2092 {
2093         BOOST_ASSERT(isValidName(name));
2094
2095         if (name == "aboutlyx")
2096                 return createGuiAbout(*this);
2097         if (name == "bibitem")
2098                 return createGuiBibitem(*this);
2099         if (name == "bibtex")
2100                 return createGuiBibtex(*this);
2101         if (name == "box")
2102                 return createGuiBox(*this);
2103         if (name == "branch")
2104                 return createGuiBranch(*this);
2105         if (name == "changes")
2106                 return createGuiChanges(*this);
2107         if (name == "character")
2108                 return createGuiCharacter(*this);
2109         if (name == "citation")
2110                 return createGuiCitation(*this);
2111         if (name == "document")
2112                 return createGuiDocument(*this);
2113         if (name == "errorlist")
2114                 return createGuiErrorList(*this);
2115         if (name == "ert")
2116                 return createGuiERT(*this);
2117         if (name == "external")
2118                 return createGuiExternal(*this);
2119         if (name == "file")
2120                 return createGuiShowFile(*this);
2121         if (name == "findreplace")
2122                 return createGuiSearch(*this);
2123         if (name == "float")
2124                 return createGuiFloat(*this);
2125         if (name == "graphics")
2126                 return createGuiGraphics(*this);
2127         if (name == "include")
2128                 return createGuiInclude(*this);
2129         if (name == "index")
2130                 return createGuiIndex(*this);
2131         if (name == "nomenclature")
2132                 return createGuiNomenclature(*this);
2133         if (name == "label")
2134                 return createGuiLabel(*this);
2135         if (name == "log")
2136                 return createGuiLog(*this);
2137         if (name == "view-source")
2138                 return createGuiViewSource(*this);
2139         if (name == "mathdelimiter")
2140                 return createGuiDelimiter(*this);
2141         if (name == "mathmatrix")
2142                 return createGuiMathMatrix(*this);
2143         if (name == "note")
2144                 return createGuiNote(*this);
2145         if (name == "paragraph")
2146                 return createGuiParagraph(*this);
2147         if (name == "prefs")
2148                 return createGuiPreferences(*this);
2149         if (name == "print")
2150                 return createGuiPrint(*this);
2151         if (name == "ref")
2152                 return createGuiRef(*this);
2153         if (name == "sendto")
2154                 return createGuiSendTo(*this);
2155         if (name == "spellchecker")
2156                 return createGuiSpellchecker(*this);
2157         if (name == "symbols")
2158                 return createGuiSymbols(*this);
2159         if (name == "tabular")
2160                 return createGuiTabular(*this);
2161         if (name == "tabularcreate")
2162                 return createGuiTabularCreate(*this);
2163         if (name == "texinfo")
2164                 return createGuiTexInfo(*this);
2165 #ifdef HAVE_LIBAIKSAURUS
2166         if (name == "thesaurus")
2167                 return createGuiThesaurus(*this);
2168 #endif
2169         if (name == "toc")
2170                 return createGuiToc(*this);
2171         if (name == "href")
2172                 return createGuiHyperlink(*this);
2173         if (name == "vspace")
2174                 return createGuiVSpace(*this);
2175         if (name == "wrap")
2176                 return createGuiWrap(*this);
2177         if (name == "listings")
2178                 return createGuiListings(*this);
2179
2180         return 0;
2181 }
2182
2183
2184 } // namespace frontend
2185 } // namespace lyx
2186
2187 #include "GuiView_moc.cpp"