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