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