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