]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiView.cpp
small simplification
[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 #include "Dialog.h"
18
19 #include <boost/assert.hpp>
20
21 using std::string;
22
23 #include "GuiView.h"
24
25 #include "GuiApplication.h"
26 #include "GuiWorkArea.h"
27 #include "GuiKeySymbol.h"
28 #include "GuiMenubar.h"
29 #include "GuiToolbar.h"
30 #include "GuiToolbars.h"
31
32 #include "qt_helpers.h"
33
34 #include "buffer_funcs.h"
35 #include "Buffer.h"
36 #include "BufferList.h"
37 #include "BufferParams.h"
38 #include "BufferView.h"
39 #include "Cursor.h"
40 #include "debug.h"
41 #include "ErrorList.h"
42 #include "FuncRequest.h"
43 #include "gettext.h"
44 #include "Intl.h"
45 #include "Layout.h"
46 #include "Lexer.h"
47 #include "LyXFunc.h"
48 #include "LyX.h"
49 #include "LyXRC.h"
50 #include "LyXVC.h"
51 #include "MenuBackend.h"
52 #include "Paragraph.h"
53 #include "TextClass.h"
54 #include "Text.h"
55 #include "ToolbarBackend.h"
56 #include "version.h"
57
58 #include "support/convert.h"
59 #include "support/FileName.h"
60 #include "support/lstrings.h"
61 #include "support/os.h"
62 #include "support/Timeout.h"
63
64 #include <QAction>
65 #include <QApplication>
66 #include <QCloseEvent>
67 #include <QDesktopWidget>
68 #include <QDragEnterEvent>
69 #include <QDropEvent>
70 #include <QList>
71 #include <QMenu>
72 #include <QPainter>
73 #include <QPixmap>
74 #include <QPoint>
75 #include <QPushButton>
76 #include <QSettings>
77 #include <QShowEvent>
78 #include <QSplitter>
79 #include <QStackedWidget>
80 #include <QStatusBar>
81 #include <QTimer>
82 #include <QToolBar>
83 #include <QUrl>
84
85 #include <boost/bind.hpp>
86 #include <boost/current_function.hpp>
87
88 #ifdef HAVE_SYS_TIME_H
89 # include <sys/time.h>
90 #endif
91 #ifdef HAVE_UNISTD_H
92 # include <unistd.h>
93 #endif
94
95 using std::endl;
96 using std::string;
97 using std::vector;
98
99 namespace lyx {
100
101 extern bool quitting;
102
103 namespace frontend {
104
105 using support::bformat;
106 using support::FileName;
107 using support::trim;
108
109 namespace {
110
111 class BackgroundWidget : public QWidget
112 {
113 public:
114         BackgroundWidget(QString const & file, QString const & text)
115                 : splash_(file)
116         {
117                 QPainter pain(&splash_);
118                 pain.setPen(QColor(255, 255, 0));
119                 QFont font;
120                 // The font used to display the version info
121                 font.setStyleHint(QFont::SansSerif);
122                 font.setWeight(QFont::Bold);
123                 font.setPointSize(convert<int>(lyxrc.font_sizes[FONT_SIZE_LARGE]));
124                 pain.setFont(font);
125                 pain.drawText(260, 270, text);
126         }
127
128         void paintEvent(QPaintEvent *)
129         {
130                 int x = (width() - splash_.width()) / 2;
131                 int y = (height() - splash_.height()) / 2;
132                 QPainter pain(this);
133                 pain.drawPixmap(x, y, splash_);
134         }
135
136 private:
137         QPixmap splash_;
138 };
139
140 } // namespace anon
141
142
143 typedef boost::shared_ptr<Dialog> DialogPtr;
144
145 struct GuiView::GuiViewPrivate
146 {
147         GuiViewPrivate()
148                 : current_work_area_(0), layout_(0),
149                 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
150         {
151                 // hardcode here the platform specific icon size
152                 smallIconSize = 14;     // scaling problems
153                 normalIconSize = 20;    // ok, default
154                 bigIconSize = 26;               // better for some math icons
155
156                 splitter_ = new QSplitter;
157                 initBackground();
158                 stack_widget_ = new QStackedWidget;
159                 stack_widget_->addWidget(bg_widget_);
160                 stack_widget_->addWidget(splitter_);
161                 setBackground();
162         }
163
164         ~GuiViewPrivate()
165         {
166                 delete splitter_;
167                 delete bg_widget_;
168                 delete stack_widget_;
169                 delete menubar_;
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 initBackground()
212         {
213                 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
214                 /// The text to be written on top of the pixmap
215                 QString const text = lyx_version ? QString(lyx_version) : qt_("unknown version");
216                 bg_widget_ = new BackgroundWidget(":/images/banner.png", text);
217         }
218
219         void setBackground()
220         {
221                 stack_widget_->setCurrentWidget(bg_widget_);
222                 bg_widget_->setUpdatesEnabled(true);
223         }
224
225         TabWorkArea * tabWorkArea(int i)
226         {
227                 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
228         }
229
230         TabWorkArea * currentTabWorkArea()
231         {
232                 if (splitter_->count() == 1)
233                         // The first TabWorkArea is always the first one, if any.
234                         return tabWorkArea(0);
235
236                 TabWorkArea * tab_widget = 0;
237                 for (int i = 0; i != splitter_->count(); ++i) {
238                         QWidget * w = splitter_->widget(i);
239                         if (!w->hasFocus())
240                                 continue;
241                         tab_widget = dynamic_cast<TabWorkArea *>(w);
242                         if (tab_widget)
243                                 break;
244                 }
245
246                 return tab_widget;
247         }
248
249 public:
250         ///
251         string cur_title;
252
253         GuiWorkArea * current_work_area_;
254         QSplitter * splitter_;
255         QStackedWidget * stack_widget_;
256         BackgroundWidget * bg_widget_;
257         /// view's menubar
258         GuiMenubar * menubar_;
259         /// view's toolbars
260         GuiToolbars * toolbars_;
261         /// The main layout box.
262         /** 
263          * \warning Don't Delete! The layout box is actually owned by
264          * whichever toolbar contains it. All the GuiView class needs is a
265          * means of accessing it.
266          *
267          * FIXME: replace that with a proper model so that we are not limited
268          * to only one dialog.
269          */
270         GuiLayoutBox * layout_;
271
272         ///
273         std::map<std::string, Inset *> open_insets_;
274
275         ///
276         std::map<std::string, DialogPtr> dialogs_;
277
278         unsigned int smallIconSize;
279         unsigned int normalIconSize;
280         unsigned int bigIconSize;
281         ///
282         QTimer statusbar_timer_;
283         /// are we quitting by the menu?
284         bool quitting_by_menu_;
285         /// auto-saving of buffers
286         Timeout autosave_timeout_;
287         ///
288         /// flag against a race condition due to multiclicks in Qt frontend,
289         /// see bug #1119
290         bool in_show_;
291 };
292
293
294 GuiView::GuiView(int id)
295         : d(*new GuiViewPrivate),  id_(id)
296 {
297         // GuiToolbars *must* be initialised before GuiMenubar.
298         d.toolbars_ = new GuiToolbars(*this);
299         d.menubar_ = new GuiMenubar(this, menubackend);
300
301         setCentralWidget(d.stack_widget_);
302
303         // Start autosave timer
304         if (lyxrc.autosave) {
305                 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
306                 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
307                 d.autosave_timeout_.start();
308         }
309         connect(&d.statusbar_timer_, SIGNAL(timeout()),
310                 this, SLOT(clearMessage()));
311
312         // Qt bug? signal lastWindowClosed does not work
313         setAttribute(Qt::WA_QuitOnClose, false);
314         setAttribute(Qt::WA_DeleteOnClose, true);
315 #ifndef Q_WS_MACX
316         // assign an icon to main form. We do not do it under Qt/Mac,
317         // since the icon is provided in the application bundle.
318         setWindowIcon(QPixmap(":/images/lyx.png"));
319 #endif
320
321         // For Drag&Drop.
322         setAcceptDrops(true);
323
324         statusBar()->setSizeGripEnabled(true);
325
326         // Forbid too small unresizable window because it can happen
327         // with some window manager under X11.
328         setMinimumSize(300, 200);
329
330         if (!lyxrc.allow_geometry_session)
331                 // No session handling, default to a sane size.
332                 setGeometry(50, 50, 690, 510);
333
334         // Now take care of session management.
335         QSettings settings;
336         QString const key = "view-" + QString::number(id_);
337 #ifdef Q_WS_X11
338         QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
339         QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
340         resize(size);
341         move(pos);
342 #else
343         if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
344                 setGeometry(50, 50, 690, 510);
345 #endif
346         setIconSize(settings.value(key + "/icon_size").toSize());
347 }
348
349
350 GuiView::~GuiView()
351 {
352         delete &d;
353 }
354
355
356 void GuiView::close()
357 {
358         d.quitting_by_menu_ = true;
359         d.current_work_area_ = 0;
360         for (int i = 0; i != d.splitter_->count(); ++i) {
361                 TabWorkArea * twa = d.tabWorkArea(i);
362                 if (twa)
363                         twa->closeAll();
364         }
365         QMainWindow::close();
366         d.quitting_by_menu_ = false;
367 }
368
369
370 void GuiView::setFocus()
371 {
372         if (d.current_work_area_)
373                 d.current_work_area_->setFocus();
374         else
375                 QWidget::setFocus();
376 }
377
378
379 QMenu* GuiView::createPopupMenu()
380 {
381         return d.toolBarPopup(this);
382 }
383
384
385 void GuiView::showEvent(QShowEvent * e)
386 {
387         LYXERR(Debug::GUI, "Passed Geometry "
388                 << size().height() << "x" << size().width()
389                 << "+" << pos().x() << "+" << pos().y());
390
391         if (d.splitter_->count() == 0)
392                 // No work area, switch to the background widget.
393                 d.setBackground();
394
395         QMainWindow::showEvent(e);
396 }
397
398
399 void GuiView::closeEvent(QCloseEvent * close_event)
400 {
401         // we may have been called through the close window button
402         // which bypasses the LFUN machinery.
403         if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
404                 if (!theBufferList().quitWriteAll()) {
405                         close_event->ignore();
406                         return;
407                 }
408         }
409
410         // Make sure that no LFUN use this close to be closed View.
411         theLyXFunc().setLyXView(0);
412         // Make sure the timer time out will not trigger a statusbar update.
413         d.statusbar_timer_.stop();
414
415         if (lyxrc.allow_geometry_session) {
416                 QSettings settings;
417                 QString const key = "view-" + QString::number(id_);
418 #ifdef Q_WS_X11
419                 settings.setValue(key + "/pos", pos());
420                 settings.setValue(key + "/size", size());
421 #else
422                 settings.setValue(key + "/geometry", saveGeometry());
423 #endif
424                 settings.setValue(key + "/icon_size", iconSize());
425                 d.toolbars_->saveToolbarInfo();
426         }
427
428         guiApp->unregisterView(id_);
429         if (guiApp->viewCount() > 0) {
430                 // Just close the window and do nothing else if this is not the
431                 // last window.
432                 close_event->accept();
433                 return;
434         }
435
436         quitting = true;
437
438         // this is the place where we leave the frontend.
439         // it is the only point at which we start quitting.
440         close_event->accept();
441         // quit the event loop
442         qApp->quit();
443 }
444
445
446 void GuiView::dragEnterEvent(QDragEnterEvent * event)
447 {
448         if (event->mimeData()->hasUrls())
449                 event->accept();
450         /// \todo Ask lyx-devel is this is enough:
451         /// if (event->mimeData()->hasFormat("text/plain"))
452         ///     event->acceptProposedAction();
453 }
454
455
456 void GuiView::dropEvent(QDropEvent* event)
457 {
458         QList<QUrl> files = event->mimeData()->urls();
459         if (files.isEmpty())
460                 return;
461
462         LYXERR(Debug::GUI, BOOST_CURRENT_FUNCTION << " got URLs!");
463         for (int i = 0; i != files.size(); ++i) {
464                 string const file = support::os::internal_path(fromqstr(
465                         files.at(i).toLocalFile()));
466                 if (!file.empty())
467                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
468         }
469 }
470
471
472 void GuiView::message(docstring const & str)
473 {
474         statusBar()->showMessage(toqstr(str));
475         d.statusbar_timer_.stop();
476         d.statusbar_timer_.start(3000);
477 }
478
479
480 void GuiView::smallSizedIcons()
481 {
482         setIconSize(QSize(d.smallIconSize, d.smallIconSize));
483 }
484
485
486 void GuiView::normalSizedIcons()
487 {
488         setIconSize(QSize(d.normalIconSize, d.normalIconSize));
489 }
490
491
492 void GuiView::bigSizedIcons()
493 {
494         setIconSize(QSize(d.bigIconSize, d.bigIconSize));
495 }
496
497
498 void GuiView::clearMessage()
499 {
500         if (!hasFocus())
501                 return;
502         theLyXFunc().setLyXView(this);
503         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
504         d.statusbar_timer_.stop();
505 }
506
507
508 void GuiView::updateWindowTitle(GuiWorkArea * wa)
509 {
510         if (wa != d.current_work_area_)
511                 return;
512         setWindowTitle(qt_("LyX: ") + wa->windowTitle());
513         setWindowIconText(wa->windowIconText());
514 }
515
516
517 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
518 {
519         disconnectBuffer();
520         disconnectBufferView();
521         connectBufferView(wa->bufferView());
522         connectBuffer(wa->bufferView().buffer());
523         d.current_work_area_ = wa;
524         QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
525                 this, SLOT(updateWindowTitle(GuiWorkArea *)));
526         updateWindowTitle(wa);
527
528         updateToc();
529         // Buffer-dependent dialogs should be updated or
530         // hidden. This should go here because some dialogs (eg ToC)
531         // require bv_->text.
532         updateBufferDependent(true);
533         updateToolbars();
534         updateLayoutList();
535         updateStatusBar();
536 }
537
538
539 void GuiView::updateStatusBar()
540 {
541         // let the user see the explicit message
542         if (d.statusbar_timer_.isActive())
543                 return;
544
545         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
546 }
547
548
549 bool GuiView::hasFocus() const
550 {
551         return qApp->activeWindow() == this;
552 }
553
554
555 bool GuiView::event(QEvent * e)
556 {
557         switch (e->type())
558         {
559         // Useful debug code:
560         //case QEvent::ActivationChange:
561         //case QEvent::WindowDeactivate:
562         //case QEvent::Paint:
563         //case QEvent::Enter:
564         //case QEvent::Leave:
565         //case QEvent::HoverEnter:
566         //case QEvent::HoverLeave:
567         //case QEvent::HoverMove:
568         //case QEvent::StatusTip:
569         //case QEvent::DragEnter:
570         //case QEvent::DragLeave:
571         //case QEvent::Drop:
572         //      break;
573
574         case QEvent::WindowActivate: {
575                 guiApp->setCurrentView(*this);
576                 if (d.current_work_area_) {
577                         BufferView & bv = d.current_work_area_->bufferView();
578                         connectBufferView(bv);
579                         connectBuffer(bv.buffer());
580                         // The document structure, name and dialogs might have
581                         // changed in another view.
582                         updateBufferDependent(true);
583                 } else {
584                         setWindowTitle(qt_("LyX"));
585                         setWindowIconText(qt_("LyX"));
586                 }
587                 return QMainWindow::event(e);
588         }
589         case QEvent::ShortcutOverride: {
590                 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
591                 if (!d.current_work_area_) {
592                         theLyXFunc().setLyXView(this);
593                         KeySymbol sym;
594                         setKeySymbol(&sym, ke);
595                         theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
596                         e->accept();
597                         return true;
598                 }
599                 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
600                         KeySymbol sym;
601                         setKeySymbol(&sym, ke);
602                         d.current_work_area_->processKeySym(sym, NoModifier);
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 yes)
621 {
622         if (d.current_work_area_) {
623                 d.current_work_area_->setUpdatesEnabled(!yes);
624                 if (yes)
625                         d.current_work_area_->stopBlinkingCursor();
626                 else
627                         d.current_work_area_->startBlinkingCursor();
628         }
629
630         if (yes)
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 * work_area)
724 {
725         BOOST_ASSERT(work_area);
726
727         // Changing work area can result from opening a file so
728         // update the toc in any case.
729         updateToc();
730
731         GuiWorkArea * wa = static_cast<GuiWorkArea *>(work_area);
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 * work_area)
741 {
742         BOOST_ASSERT(work_area);
743         GuiWorkArea * gwa = static_cast<GuiWorkArea *>(work_area);
744         if (gwa == d.current_work_area_) {
745                 disconnectBuffer();
746                 disconnectBufferView();
747                 hideBufferDependent();
748                 d.current_work_area_ = 0;
749         }
750
751         // removing a work area often results from closing a file so
752         // update the toc in any case.
753         updateToc();
754
755         for (int i = 0; i != d.splitter_->count(); ++i) {
756                 TabWorkArea * twa = d.tabWorkArea(i);
757                 if (!twa->removeWorkArea(gwa))
758                         // Not found in this tab group.
759                         continue;
760
761                 // We found and removed the GuiWorkArea.
762                 if (!twa->count()) {
763                         // No more WorkAreas in this tab group, so delete it.
764                         delete twa;
765                         break;
766                 }
767
768                 if (d.current_work_area_)
769                         // This means that we are not closing the current GuiWorkArea;
770                         break;
771
772                 // Switch to the next GuiWorkArea in the found TabWorkArea.
773                 d.current_work_area_ = twa->currentWorkArea();
774                 break;
775         }
776
777         if (d.splitter_->count() == 0)
778                 // No more work area, switch to the background widget.
779                 d.setBackground();
780 }
781
782
783 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
784 {
785         d.layout_ = layout;
786 }
787
788
789 void GuiView::updateLayoutList()
790 {
791         if (d.layout_)
792                 d.layout_->updateContents(false);
793 }
794
795
796 void GuiView::updateToolbars()
797 {
798         if (d.current_work_area_) {
799                 bool const math =
800                         d.current_work_area_->bufferView().cursor().inMathed();
801                 bool const table =
802                         lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
803                 bool const review =
804                         lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
805                         lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
806
807                 d.toolbars_->update(math, table, review);
808         } else
809                 d.toolbars_->update(false, false, false);
810
811         // update read-only status of open dialogs.
812         checkStatus();
813 }
814
815
816 Buffer * GuiView::buffer()
817 {
818         if (d.current_work_area_)
819                 return &d.current_work_area_->bufferView().buffer();
820         return 0;
821 }
822
823
824 Buffer const * GuiView::buffer() const
825 {
826         if (d.current_work_area_)
827                 return &d.current_work_area_->bufferView().buffer();
828         return 0;
829 }
830
831
832 void GuiView::setBuffer(Buffer * newBuffer)
833 {
834         BOOST_ASSERT(newBuffer);
835         setBusy(true);
836
837         GuiWorkArea * wa = workArea(*newBuffer);
838         if (wa == 0) {
839                 updateLabels(*newBuffer->masterBuffer());
840                 wa = addWorkArea(*newBuffer);
841         } else {
842                 //Disconnect the old buffer...there's no new one.
843                 disconnectBuffer();
844         }
845         connectBuffer(*newBuffer);
846         connectBufferView(wa->bufferView());
847         setCurrentWorkArea(wa);
848
849         setBusy(false);
850 }
851
852
853 void GuiView::connectBuffer(Buffer & buf)
854 {
855         buf.setGuiDelegate(this);
856 }
857
858
859 void GuiView::disconnectBuffer()
860 {
861         if (d.current_work_area_)
862                 d.current_work_area_->bufferView().setGuiDelegate(0);
863 }
864
865
866 void GuiView::connectBufferView(BufferView & bv)
867 {
868         bv.setGuiDelegate(this);
869 }
870
871
872 void GuiView::disconnectBufferView()
873 {
874         if (d.current_work_area_)
875                 d.current_work_area_->bufferView().setGuiDelegate(0);
876 }
877
878
879 void GuiView::errors(string const & error_type)
880 {
881         ErrorList & el = buffer()->errorList(error_type);
882         if (!el.empty())
883                 showDialog("errorlist", error_type);
884 }
885
886
887 void GuiView::updateDialog(string const & name, string const & data)
888 {
889         if (!isDialogVisible(name))
890                 return;
891
892         std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
893         if (it == d.dialogs_.end())
894                 return;
895
896         Dialog * const dialog = it->second.get();
897         if (dialog->isVisibleView())
898                 dialog->updateData(data);
899 }
900
901
902 BufferView * GuiView::view()
903 {
904         return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
905 }
906
907
908 void GuiView::updateToc()
909 {
910         updateDialog("toc", "");
911 }
912
913
914 void GuiView::updateEmbeddedFiles()
915 {
916         updateDialog("embedding", "");
917 }
918
919
920 void GuiView::autoSave()
921 {
922         LYXERR(Debug::INFO, "Running autoSave()");
923
924         if (buffer())
925                 view()->buffer().autoSave();
926 }
927
928
929 void GuiView::resetAutosaveTimers()
930 {
931         if (lyxrc.autosave)
932                 d.autosave_timeout_.restart();
933 }
934
935
936 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
937 {
938         FuncStatus flag;
939         bool enable = true;
940         Buffer * buf = buffer();
941
942         /* In LyX/Mac, when a dialog is open, the menus of the
943            application can still be accessed without giving focus to
944            the main window. In this case, we want to disable the menu
945            entries that are buffer-related.
946
947            Note that this code is not perfect, as bug 1941 attests:
948            http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
949         */
950         if (cmd.origin == FuncRequest::MENU && !hasFocus())
951                 buf = 0;
952
953         switch(cmd.action) {
954         case LFUN_TOOLBAR_TOGGLE:
955                 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
956                 break;
957
958         case LFUN_DIALOG_TOGGLE:
959                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
960                 // fall through to set "enable"
961         case LFUN_DIALOG_SHOW: {
962                 string const name = cmd.getArg(0);
963                 if (!buf)
964                         enable = name == "aboutlyx"
965                                 || name == "file" //FIXME: should be removed.
966                                 || name == "prefs"
967                                 || name == "texinfo";
968                 else if (name == "print")
969                         enable = buf->isExportable("dvi")
970                                 && lyxrc.print_command != "none";
971                 else if (name == "character") {
972                         if (!view())
973                                 enable = false;
974                         else {
975                                 InsetCode ic = view()->cursor().inset().lyxCode();
976                                 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
977                         }
978                 }
979                 else if (name == "latexlog")
980                         enable = FileName(buf->logName()).isFileReadable();
981                 else if (name == "spellchecker")
982 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
983                         enable = !buf->isReadonly();
984 #else
985                         enable = false;
986 #endif
987                 else if (name == "vclog")
988                         enable = buf->lyxvc().inUse();
989                 break;
990         }
991
992         case LFUN_DIALOG_UPDATE: {
993                 string const name = cmd.getArg(0);
994                 if (!buf)
995                         enable = name == "prefs";
996                 break;
997         }
998
999         default:
1000                 if (!view()) {
1001                         enable = false;
1002                         break;
1003                 }
1004         }
1005
1006         if (!enable)
1007                 flag.enabled(false);
1008
1009         return flag;
1010 }
1011
1012
1013 void GuiView::dispatch(FuncRequest const & cmd)
1014 {
1015         Buffer * buf = buffer();
1016         switch(cmd.action) {
1017                 case LFUN_BUFFER_SWITCH:
1018                         setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1019                         break;
1020
1021                 case LFUN_COMMAND_EXECUTE: {
1022                         bool const show_it = cmd.argument() != "off";
1023                         d.toolbars_->showCommandBuffer(show_it);
1024                         break;
1025                 }
1026                 case LFUN_DROP_LAYOUTS_CHOICE:
1027                         if (d.layout_)
1028                                 d.layout_->showPopup();
1029                         break;
1030
1031                 case LFUN_MENU_OPEN:
1032                         d.menubar_->openByName(toqstr(cmd.argument()));
1033                         break;
1034
1035                 case LFUN_TOOLBAR_TOGGLE: {
1036                         string const name = cmd.getArg(0);
1037                         bool const allowauto = cmd.getArg(1) == "allowauto";
1038                         // it is possible to get current toolbar status like this,...
1039                         // but I decide to obey the order of ToolbarBackend::flags
1040                         // and disregard real toolbar status.
1041                         // toolbars_->saveToolbarInfo();
1042                         //
1043                         // toggle state on/off/auto
1044                         d.toolbars_->toggleToolbarState(name, allowauto);
1045                         // update toolbar
1046                         updateToolbars();
1047
1048                         ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1049                         if (!tbi) {
1050                                 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1051                                 break;
1052                         }
1053                         docstring state;
1054                         if (tbi->flags & ToolbarInfo::ON)
1055                                 state = _("on");
1056                         else if (tbi->flags & ToolbarInfo::OFF)
1057                                 state = _("off");
1058                         else if (tbi->flags & ToolbarInfo::AUTO)
1059                                 state = _("auto");
1060
1061                         message(bformat(_("Toolbar \"%1$s\" state set to %2$s"), 
1062                                            _(tbi->gui_name), state));
1063                         break;
1064                 }
1065
1066                 case LFUN_DIALOG_UPDATE: {
1067                         string const name = to_utf8(cmd.argument());
1068                         // Can only update a dialog connected to an existing inset
1069                         Inset * inset = getOpenInset(name);
1070                         if (inset) {
1071                                 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1072                                 inset->dispatch(view()->cursor(), fr);
1073                         } else if (name == "paragraph") {
1074                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1075                         } else if (name == "prefs") {
1076                                 updateDialog(name, string());
1077                         }
1078                         break;
1079                 }
1080
1081                 case LFUN_DIALOG_TOGGLE: {
1082                         if (isDialogVisible(cmd.getArg(0)))
1083                                 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1084                         else
1085                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1086                         break;
1087                 }
1088
1089                 case LFUN_DIALOG_DISCONNECT_INSET:
1090                         disconnectDialog(to_utf8(cmd.argument()));
1091                         break;
1092
1093                 case LFUN_DIALOG_HIDE: {
1094                         if (quitting)
1095                                 break;
1096                         guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1097                         break;
1098                 }
1099
1100                 case LFUN_DIALOG_SHOW: {
1101                         string const name = cmd.getArg(0);
1102                         string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1103
1104                         if (name == "character") {
1105                                 data = freefont2string();
1106                                 if (!data.empty())
1107                                         showDialog("character", data);
1108                         } else if (name == "latexlog") {
1109                                 Buffer::LogType type; 
1110                                 string const logfile = buf->logName(&type);
1111                                 switch (type) {
1112                                 case Buffer::latexlog:
1113                                         data = "latex ";
1114                                         break;
1115                                 case Buffer::buildlog:
1116                                         data = "literate ";
1117                                         break;
1118                                 }
1119                                 data += Lexer::quoteString(logfile);
1120                                 showDialog("log", data);
1121                         } else if (name == "vclog") {
1122                                 string const data = "vc " +
1123                                         Lexer::quoteString(buf->lyxvc().getLogFile());
1124                                 showDialog("log", data);
1125                         } else
1126                                 showDialog(name, data);
1127                         break;
1128                 }
1129
1130                 default:
1131                         theLyXFunc().setLyXView(this);
1132                         lyx::dispatch(cmd);
1133         }
1134 }
1135
1136
1137 Buffer const * GuiView::updateInset(Inset const * inset)
1138 {
1139         if (!d.current_work_area_)
1140                 return 0;
1141
1142         if (inset)
1143                 d.current_work_area_->scheduleRedraw();
1144
1145         return &d.current_work_area_->bufferView().buffer();
1146 }
1147
1148
1149 void GuiView::restartCursor()
1150 {
1151         /* When we move around, or type, it's nice to be able to see
1152          * the cursor immediately after the keypress.
1153          */
1154         if (d.current_work_area_)
1155                 d.current_work_area_->startBlinkingCursor();
1156
1157         // Take this occasion to update the toobars and layout list.
1158         updateLayoutList();
1159         updateToolbars();
1160 }
1161
1162 namespace {
1163
1164 // This list should be kept in sync with the list of insets in
1165 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
1166 // dialog should have the same name as the inset.
1167
1168 char const * const dialognames[] = {
1169 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1170 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1171 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1172 "mathdelimiter", "mathmatrix", "note", "paragraph",
1173 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1174
1175 #ifdef HAVE_LIBAIKSAURUS
1176 "thesaurus",
1177 #endif
1178
1179 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1180
1181 char const * const * const end_dialognames =
1182         dialognames + (sizeof(dialognames) / sizeof(char *));
1183
1184 class cmpCStr {
1185 public:
1186         cmpCStr(char const * name) : name_(name) {}
1187         bool operator()(char const * other) {
1188                 return strcmp(other, name_) == 0;
1189         }
1190 private:
1191         char const * name_;
1192 };
1193
1194
1195 bool isValidName(string const & name)
1196 {
1197         return std::find_if(dialognames, end_dialognames,
1198                             cmpCStr(name.c_str())) != end_dialognames;
1199 }
1200
1201 } // namespace anon
1202
1203
1204 void GuiView::resetDialogs()
1205 {
1206         // Make sure that no LFUN uses any LyXView.
1207         theLyXFunc().setLyXView(0);
1208         d.toolbars_->init();
1209         d.menubar_->init();
1210         if (d.layout_)
1211                 d.layout_->updateContents(true);
1212         // Now update controls with current buffer.
1213         theLyXFunc().setLyXView(this);
1214         restartCursor();
1215 }
1216
1217
1218 Dialog * GuiView::find_or_build(string const & name)
1219 {
1220         if (!isValidName(name))
1221                 return 0;
1222
1223         std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1224
1225         if (it != d.dialogs_.end())
1226                 return it->second.get();
1227
1228         d.dialogs_[name].reset(build(name));
1229         return d.dialogs_[name].get();
1230 }
1231
1232
1233 void GuiView::showDialog(string const & name, string const & data,
1234         Inset * inset)
1235 {
1236         if (d.in_show_)
1237                 return;
1238
1239         d.in_show_ = true;
1240         Dialog * dialog = find_or_build(name);
1241         if (dialog) {
1242                 dialog->showData(data);
1243                 if (inset)
1244                         d.open_insets_[name] = inset;
1245         }
1246         d.in_show_ = false;
1247 }
1248
1249
1250 bool GuiView::isDialogVisible(string const & name) const
1251 {
1252         std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1253         if (it == d.dialogs_.end())
1254                 return false;
1255         return it->second.get()->isVisibleView();
1256 }
1257
1258
1259 void GuiView::hideDialog(string const & name, Inset * inset)
1260 {
1261         // Don't send the signal if we are quitting, because on MSVC it is
1262         // destructed before the cut stack in CutAndPaste.cpp, and this method
1263         // is called from some inset destructor if the cut stack is not empty
1264         // on exit.
1265         if (quitting)
1266                 return;
1267
1268         std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1269         if (it == d.dialogs_.end())
1270                 return;
1271
1272         if (inset && inset != getOpenInset(name))
1273                 return;
1274
1275         Dialog * const dialog = it->second.get();
1276         if (dialog->isVisibleView())
1277                 dialog->hide();
1278         d.open_insets_[name] = 0;
1279 }
1280
1281
1282 void GuiView::disconnectDialog(string const & name)
1283 {
1284         if (!isValidName(name))
1285                 return;
1286
1287         if (d.open_insets_.find(name) != d.open_insets_.end())
1288                 d.open_insets_[name] = 0;
1289 }
1290
1291
1292 Inset * GuiView::getOpenInset(string const & name) const
1293 {
1294         if (!isValidName(name))
1295                 return 0;
1296
1297         std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1298         return it == d.open_insets_.end() ? 0 : it->second;
1299 }
1300
1301
1302 void GuiView::hideAll() const
1303 {
1304         std::map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
1305         std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1306
1307         for(; it != end; ++it)
1308                 it->second->hide();
1309 }
1310
1311
1312 void GuiView::hideBufferDependent() const
1313 {
1314         std::map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
1315         std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1316
1317         for(; it != end; ++it) {
1318                 Dialog * dialog = it->second.get();
1319                 if (dialog->isBufferDependent())
1320                         dialog->hide();
1321         }
1322 }
1323
1324
1325 void GuiView::updateBufferDependent(bool switched) const
1326 {
1327         std::map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
1328         std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1329
1330         for(; it != end; ++it) {
1331                 Dialog * dialog = it->second.get();
1332                 if (switched && dialog->isBufferDependent()) {
1333                         if (dialog->isVisibleView() && dialog->initialiseParams(""))
1334                                 dialog->updateView();
1335                         else
1336                                 dialog->hide();
1337                 } else {
1338                         // A bit clunky, but the dialog will request
1339                         // that the kernel provides it with the necessary
1340                         // data.
1341                         dialog->updateDialog(dialog->name());
1342                 }
1343         }
1344 }
1345
1346
1347 void GuiView::checkStatus()
1348 {
1349         std::map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
1350         std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1351
1352         for(; it != end; ++it) {
1353                 Dialog * const dialog = it->second.get();
1354                 if (dialog && dialog->isVisibleView())
1355                         dialog->checkStatus();
1356         }
1357 }
1358
1359
1360
1361 // will be replaced by a proper factory...
1362 Dialog * createGuiAbout(LyXView & lv);
1363 Dialog * createGuiBibitem(LyXView & lv);
1364 Dialog * createGuiBibtex(LyXView & lv);
1365 Dialog * createGuiBox(LyXView & lv);
1366 Dialog * createGuiBranch(LyXView & lv);
1367 Dialog * createGuiChanges(LyXView & lv);
1368 Dialog * createGuiCharacter(LyXView & lv);
1369 Dialog * createGuiCitation(LyXView & lv);
1370 Dialog * createGuiDelimiter(LyXView & lv);
1371 Dialog * createGuiDocument(LyXView & lv);
1372 Dialog * createGuiErrorList(LyXView & lv);
1373 Dialog * createGuiERT(LyXView & lv);
1374 Dialog * createGuiExternal(LyXView & lv);
1375 Dialog * createGuiFloat(LyXView & lv);
1376 Dialog * createGuiGraphics(LyXView & lv);
1377 Dialog * createGuiInclude(LyXView & lv);
1378 Dialog * createGuiIndex(LyXView & lv);
1379 Dialog * createGuiLabel(LyXView & lv);
1380 Dialog * createGuiListings(LyXView & lv);
1381 Dialog * createGuiLog(LyXView & lv);
1382 Dialog * createGuiMathMatrix(LyXView & lv);
1383 Dialog * createGuiNomenclature(LyXView & lv);
1384 Dialog * createGuiNote(LyXView & lv);
1385 Dialog * createGuiParagraph(LyXView & lv);
1386 Dialog * createGuiPreferences(LyXView & lv);
1387 Dialog * createGuiPrint(LyXView & lv);
1388 Dialog * createGuiRef(LyXView & lv);
1389 Dialog * createGuiSearch(LyXView & lv);
1390 Dialog * createGuiSendTo(LyXView & lv);
1391 Dialog * createGuiShowFile(LyXView & lv);
1392 Dialog * createGuiSpellchecker(LyXView & lv);
1393 Dialog * createGuiTabularCreate(LyXView & lv);
1394 Dialog * createGuiTabular(LyXView & lv);
1395 Dialog * createGuiTexInfo(LyXView & lv);
1396 Dialog * createGuiToc(LyXView & lv);
1397 Dialog * createGuiThesaurus(LyXView & lv);
1398 Dialog * createGuiHyperlink(LyXView & lv);
1399 Dialog * createGuiVSpace(LyXView & lv);
1400 Dialog * createGuiViewSource(LyXView & lv);
1401 Dialog * createGuiWrap(LyXView & lv);
1402
1403
1404 Dialog * GuiView::build(string const & name)
1405 {
1406         BOOST_ASSERT(isValidName(name));
1407
1408         if (name == "aboutlyx")
1409                 return createGuiAbout(*this);
1410         if (name == "bibitem")
1411                 return createGuiBibitem(*this);
1412         if (name == "bibtex")
1413                 return createGuiBibtex(*this);
1414         if (name == "box")
1415                 return createGuiBox(*this);
1416         if (name == "branch")
1417                 return createGuiBranch(*this);
1418         if (name == "changes")
1419                 return createGuiChanges(*this);
1420         if (name == "character")
1421                 return createGuiCharacter(*this);
1422         if (name == "citation")
1423                 return createGuiCitation(*this);
1424         if (name == "document")
1425                 return createGuiDocument(*this);
1426         if (name == "errorlist")
1427                 return createGuiErrorList(*this);
1428         if (name == "ert")
1429                 return createGuiERT(*this);
1430         if (name == "external")
1431                 return createGuiExternal(*this);
1432         if (name == "file")
1433                 return createGuiShowFile(*this);
1434         if (name == "findreplace")
1435                 return createGuiSearch(*this);
1436         if (name == "float")
1437                 return createGuiFloat(*this);
1438         if (name == "graphics")
1439                 return createGuiGraphics(*this);
1440         if (name == "include")
1441                 return createGuiInclude(*this);
1442         if (name == "index")
1443                 return createGuiIndex(*this);
1444         if (name == "nomenclature")
1445                 return createGuiNomenclature(*this);
1446         if (name == "label")
1447                 return createGuiLabel(*this);
1448         if (name == "log")
1449                 return createGuiLog(*this);
1450         if (name == "view-source")
1451                 return createGuiViewSource(*this);
1452         if (name == "mathdelimiter")
1453                 return createGuiDelimiter(*this);
1454         if (name == "mathmatrix")
1455                 return createGuiMathMatrix(*this);
1456         if (name == "note")
1457                 return createGuiNote(*this);
1458         if (name == "paragraph")
1459                 return createGuiParagraph(*this);
1460         if (name == "prefs")
1461                 return createGuiPreferences(*this);
1462         if (name == "print")
1463                 return createGuiPrint(*this);
1464         if (name == "ref")
1465                 return createGuiRef(*this);
1466         if (name == "sendto")
1467                 return createGuiSendTo(*this);
1468         if (name == "spellchecker")
1469                 return createGuiSpellchecker(*this);
1470         if (name == "tabular")
1471                 return createGuiTabular(*this);
1472         if (name == "tabularcreate")
1473                 return createGuiTabularCreate(*this);
1474         if (name == "texinfo")
1475                 return createGuiTexInfo(*this);
1476 #ifdef HAVE_LIBAIKSAURUS
1477         if (name == "thesaurus")
1478                 return createGuiThesaurus(*this);
1479 #endif
1480         if (name == "toc")
1481                 return createGuiToc(*this);
1482         if (name == "href")
1483                 return createGuiHyperlink(*this);
1484         if (name == "vspace")
1485                 return createGuiVSpace(*this);
1486         if (name == "wrap")
1487                 return createGuiWrap(*this);
1488         if (name == "listings")
1489                 return createGuiListings(*this);
1490
1491         return 0;
1492 }
1493
1494
1495 } // namespace frontend
1496 } // namespace lyx
1497
1498 #include "GuiView_moc.cpp"