]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiView.cpp
GuiView.cpp, LyXAction.cpp: sort entries
[lyx.git] / src / frontends / qt4 / GuiView.cpp
1 /**
2  * \file GuiView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
18 #include "Dialog.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiCompleter.h"
24 #include "GuiWorkArea.h"
25 #include "GuiKeySymbol.h"
26 #include "GuiToc.h"
27 #include "GuiToolbar.h"
28 #include "Menus.h"
29 #include "TocModel.h"
30
31 #include "qt_helpers.h"
32
33 #include "frontends/alert.h"
34
35 #include "buffer_funcs.h"
36 #include "Buffer.h"
37 #include "BufferList.h"
38 #include "BufferParams.h"
39 #include "BufferView.h"
40 #include "Converter.h"
41 #include "Cursor.h"
42 #include "Encoding.h"
43 #include "ErrorList.h"
44 #include "Format.h"
45 #include "FuncStatus.h"
46 #include "FuncRequest.h"
47 #include "Intl.h"
48 #include "Layout.h"
49 #include "Lexer.h"
50 #include "LyXFunc.h"
51 #include "LyX.h"
52 #include "LyXRC.h"
53 #include "LyXVC.h"
54 #include "Paragraph.h"
55 #include "TextClass.h"
56 #include "Text.h"
57 #include "Toolbars.h"
58 #include "version.h"
59
60 #include "support/convert.h"
61 #include "support/debug.h"
62 #include "support/ExceptionMessage.h"
63 #include "support/FileName.h"
64 #include "support/filetools.h"
65 #include "support/gettext.h"
66 #include "support/ForkedCalls.h"
67 #include "support/lassert.h"
68 #include "support/lstrings.h"
69 #include "support/os.h"
70 #include "support/Package.h"
71 #include "support/Timeout.h"
72
73 #include <QAction>
74 #include <QApplication>
75 #include <QCloseEvent>
76 #include <QDebug>
77 #include <QDesktopWidget>
78 #include <QDragEnterEvent>
79 #include <QDropEvent>
80 #include <QList>
81 #include <QMenu>
82 #include <QMenuBar>
83 #include <QPainter>
84 #include <QPixmap>
85 #include <QPixmapCache>
86 #include <QPoint>
87 #include <QPushButton>
88 #include <QSettings>
89 #include <QShowEvent>
90 #include <QSplitter>
91 #include <QStackedWidget>
92 #include <QStatusBar>
93 #include <QTimer>
94 #include <QToolBar>
95 #include <QUrl>
96 #include <QScrollBar>
97
98 #include <boost/bind.hpp>
99
100 #ifdef HAVE_SYS_TIME_H
101 # include <sys/time.h>
102 #endif
103 #ifdef HAVE_UNISTD_H
104 # include <unistd.h>
105 #endif
106
107 using namespace std;
108 using namespace lyx::support;
109
110 namespace lyx {
111 namespace frontend {
112
113 namespace {
114
115 class BackgroundWidget : public QWidget
116 {
117 public:
118         BackgroundWidget()
119         {
120                 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
121                 /// The text to be written on top of the pixmap
122                 QString const text = lyx_version ?
123                         qt_("version ") + lyx_version : qt_("unknown version");
124                 splash_ = getPixmap("images/", "banner", "png");
125
126                 QPainter pain(&splash_);
127                 pain.setPen(QColor(0, 0, 0));
128                 QFont font;
129                 // The font used to display the version info
130                 font.setStyleHint(QFont::SansSerif);
131                 font.setWeight(QFont::Bold);
132                 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
133                 pain.setFont(font);
134                 pain.drawText(260, 15, text);
135         }
136
137         void paintEvent(QPaintEvent *)
138         {
139                 int x = (width() - splash_.width()) / 2;
140                 int y = (height() - splash_.height()) / 2;
141                 QPainter pain(this);
142                 pain.drawPixmap(x, y, splash_);
143         }
144
145 private:
146         QPixmap splash_;
147 };
148
149
150 /// Toolbar store providing access to individual toolbars by name.
151 typedef std::map<std::string, GuiToolbar *> ToolbarMap;
152
153 typedef boost::shared_ptr<Dialog> DialogPtr;
154
155 } // namespace anon
156
157
158 struct GuiView::GuiViewPrivate
159 {
160         GuiViewPrivate()
161                 : current_work_area_(0), current_main_work_area_(0),
162                 layout_(0), autosave_timeout_(5000),
163                 in_show_(false)
164         {
165                 // hardcode here the platform specific icon size
166                 smallIconSize = 14;  // scaling problems
167                 normalIconSize = 20; // ok, default
168                 bigIconSize = 26;    // better for some math icons
169
170                 splitter_ = new QSplitter;
171                 bg_widget_ = new BackgroundWidget;
172                 stack_widget_ = new QStackedWidget;
173                 stack_widget_->addWidget(bg_widget_);
174                 stack_widget_->addWidget(splitter_);
175                 setBackground();
176         }
177
178         ~GuiViewPrivate()
179         {
180                 delete splitter_;
181                 delete bg_widget_;
182                 delete stack_widget_;
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 setBackground()
224         {
225                 stack_widget_->setCurrentWidget(bg_widget_);
226                 bg_widget_->setUpdatesEnabled(true);
227         }
228
229         TabWorkArea * tabWorkArea(int i)
230         {
231                 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
232         }
233
234         TabWorkArea * currentTabWorkArea()
235         {
236                 if (splitter_->count() == 1)
237                         // The first TabWorkArea is always the first one, if any.
238                         return tabWorkArea(0);
239
240                 for (int i = 0; i != splitter_->count(); ++i) {
241                         TabWorkArea * twa = tabWorkArea(i);
242                         if (current_main_work_area_ == twa->currentWorkArea())
243                                 return twa;
244                 }
245
246                 // None has the focus so we just take the first one.
247                 return tabWorkArea(0);
248         }
249
250 public:
251         GuiWorkArea * current_work_area_;
252         GuiWorkArea * current_main_work_area_;
253         QSplitter * splitter_;
254         QStackedWidget * stack_widget_;
255         BackgroundWidget * bg_widget_;
256         /// view's toolbars
257         ToolbarMap toolbars_;
258         /// The main layout box.
259         /** 
260          * \warning Don't Delete! The layout box is actually owned by
261          * whichever toolbar contains it. All the GuiView class needs is a
262          * means of accessing it.
263          *
264          * FIXME: replace that with a proper model so that we are not limited
265          * to only one dialog.
266          */
267         GuiLayoutBox * layout_;
268
269         ///
270         map<string, Inset *> open_insets_;
271
272         ///
273         map<string, DialogPtr> dialogs_;
274
275         unsigned int smallIconSize;
276         unsigned int normalIconSize;
277         unsigned int bigIconSize;
278         ///
279         QTimer statusbar_timer_;
280         /// auto-saving of buffers
281         Timeout autosave_timeout_;
282         /// flag against a race condition due to multiclicks, see bug #1119
283         bool in_show_;
284
285         ///
286         TocModels toc_models_;
287 };
288
289
290 GuiView::GuiView(int id)
291         : d(*new GuiViewPrivate), id_(id), closing_(false)
292 {
293         // GuiToolbars *must* be initialised before the menu bar.
294         normalSizedIcons(); // at least on Mac the default is 32 otherwise, which is huge
295         constructToolbars();
296
297         // set ourself as the current view. This is needed for the menu bar
298         // filling, at least for the static special menu item on Mac. Otherwise
299         // they are greyed out.
300         theLyXFunc().setLyXView(this);
301         
302         // Fill up the menu bar.
303         guiApp->menus().fillMenuBar(menuBar(), this, true);
304
305         setCentralWidget(d.stack_widget_);
306
307         // Start autosave timer
308         if (lyxrc.autosave) {
309                 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
310                 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
311                 d.autosave_timeout_.start();
312         }
313         connect(&d.statusbar_timer_, SIGNAL(timeout()),
314                 this, SLOT(clearMessage()));
315
316         // We don't want to keep the window in memory if it is closed.
317         setAttribute(Qt::WA_DeleteOnClose, true);
318
319 #if (!defined(Q_WS_WIN) && !defined(Q_WS_MACX))
320         // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
321         // since the icon is provided in the application bundle.
322         setWindowIcon(getPixmap("images/", "lyx", "png"));
323 #endif
324
325         // For Drag&Drop.
326         setAcceptDrops(true);
327
328         statusBar()->setSizeGripEnabled(true);
329
330         // Forbid too small unresizable window because it can happen
331         // with some window manager under X11.
332         setMinimumSize(300, 200);
333
334         if (lyxrc.allow_geometry_session) {
335                 // Now take care of session management.
336                 if (restoreLayout())
337                         return;
338         }
339
340         // no session handling, default to a sane size.
341         setGeometry(50, 50, 690, 510);
342         initToolbars();
343
344         // clear session data if any.
345         QSettings settings;
346         settings.remove("views");
347 }
348
349
350 GuiView::~GuiView()
351 {
352         delete &d;
353 }
354
355
356 void GuiView::saveLayout() const
357 {
358         QSettings settings;
359         settings.beginGroup("views");
360         settings.beginGroup(QString::number(id_));
361 #ifdef Q_WS_X11
362         settings.setValue("pos", pos());
363         settings.setValue("size", size());
364 #else
365         settings.setValue("geometry", saveGeometry());
366 #endif
367         settings.setValue("layout", saveState(0));
368         settings.setValue("icon_size", iconSize());
369 }
370
371
372 bool GuiView::restoreLayout()
373 {
374         QSettings settings;
375         settings.beginGroup("views");
376         settings.beginGroup(QString::number(id_));
377         QString const icon_key = "icon_size";
378         if (!settings.contains(icon_key))
379                 return false;
380
381         setIconSize(settings.value(icon_key).toSize());
382 #ifdef Q_WS_X11
383         QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
384         QSize size = settings.value("size", QSize(690, 510)).toSize();
385         resize(size);
386         move(pos);
387 #else
388         if (!restoreGeometry(settings.value("geometry").toByteArray()))
389                 setGeometry(50, 50, 690, 510);
390 #endif
391         // Make sure layout is correctly oriented.
392         setLayoutDirection(qApp->layoutDirection());
393
394         // Allow the toc and view-source dock widget to be restored if needed.
395         Dialog *d;
396         if ((d = findOrBuild("toc", true)))
397                 // see bug 5082
398                 d->showView();
399         if ((d = findOrBuild("view-source", true)))
400                 d->showView();
401
402         if (!restoreState(settings.value("layout").toByteArray(), 0))
403                 initToolbars();
404         updateDialogs();
405         return true;
406 }
407
408
409 GuiToolbar * GuiView::toolbar(string const & name)
410 {
411         ToolbarMap::iterator it = d.toolbars_.find(name);
412         if (it != d.toolbars_.end())
413                 return it->second;
414
415         LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
416         message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
417         return 0;
418 }
419
420
421 void GuiView::constructToolbars()
422 {
423         ToolbarMap::iterator it = d.toolbars_.begin();
424         for (; it != d.toolbars_.end(); ++it)
425                 delete it->second;
426         d.toolbars_.clear();
427         d.layout_ = 0;
428
429         // extracts the toolbars from the backend
430         Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
431         Toolbars::Infos::iterator end = guiApp->toolbars().end();
432         for (; cit != end; ++cit)
433                 d.toolbars_[cit->name] =  new GuiToolbar(*cit, *this);
434 }
435
436
437 void GuiView::initToolbars()
438 {
439         // extracts the toolbars from the backend
440         Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
441         Toolbars::Infos::iterator end = guiApp->toolbars().end();
442         for (; cit != end; ++cit) {
443                 GuiToolbar * tb = toolbar(cit->name);
444                 if (!tb)
445                         continue;
446                 int const visibility = guiApp->toolbars().defaultVisibility(cit->name);
447                 bool newline = true;
448                 tb->setVisible(false);
449                 tb->setVisibility(visibility);
450
451                 if (visibility & Toolbars::TOP) {
452                         if (newline)
453                                 addToolBarBreak(Qt::TopToolBarArea);
454                         addToolBar(Qt::TopToolBarArea, tb);
455                 }
456
457                 if (visibility & Toolbars::BOTTOM) {
458                         // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
459 #if (QT_VERSION >= 0x040202)
460                         addToolBarBreak(Qt::BottomToolBarArea);
461 #endif
462                         addToolBar(Qt::BottomToolBarArea, tb);
463                 }
464
465                 if (visibility & Toolbars::LEFT) {
466                         // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
467 #if (QT_VERSION >= 0x040202)
468                         addToolBarBreak(Qt::LeftToolBarArea);
469 #endif
470                         addToolBar(Qt::LeftToolBarArea, tb);
471                 }
472
473                 if (visibility & Toolbars::RIGHT) {
474                         // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
475 #if (QT_VERSION >= 0x040202)
476                         addToolBarBreak(Qt::RightToolBarArea);
477 #endif
478                         addToolBar(Qt::RightToolBarArea, tb);
479                 }
480
481                 if (visibility & Toolbars::ON)
482                         tb->setVisible(true);
483         }
484 }
485
486
487 TocModels & GuiView::tocModels()
488 {
489         return d.toc_models_;
490 }
491
492
493 void GuiView::setFocus()
494 {
495         LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
496         // Make sure LyXFunc points to the correct view.
497         guiApp->setCurrentView(this);
498         theLyXFunc().setLyXView(this);
499         QMainWindow::setFocus();
500         if (d.current_work_area_)
501                 d.current_work_area_->setFocus();
502 }
503
504
505 QMenu * GuiView::createPopupMenu()
506 {
507         return d.toolBarPopup(this);
508 }
509
510
511 void GuiView::showEvent(QShowEvent * e)
512 {
513         LYXERR(Debug::GUI, "Passed Geometry "
514                 << size().height() << "x" << size().width()
515                 << "+" << pos().x() << "+" << pos().y());
516
517         if (d.splitter_->count() == 0)
518                 // No work area, switch to the background widget.
519                 d.setBackground();
520
521         QMainWindow::showEvent(e);
522 }
523
524
525 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
526  ** is responsibility of the container (e.g., dialog)
527  **/
528 void GuiView::closeEvent(QCloseEvent * close_event)
529 {
530         LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
531         closing_ = true;
532
533         // it can happen that this event arrives without selecting the view,
534         // e.g. when clicking the close button on a background window.
535         setFocus();
536         GuiWorkArea * active_wa = currentMainWorkArea();
537         setCurrentWorkArea(active_wa);
538
539         int splitter_count = d.splitter_->count();
540         for (; splitter_count; --splitter_count) {
541                 TabWorkArea * twa = d.tabWorkArea(0);
542                                 
543                 int twa_count = twa->count();
544                 for (; twa_count; --twa_count) {
545                         twa->setCurrentIndex(twa_count-1);
546
547                         GuiWorkArea * wa = twa->currentWorkArea();
548                         bool const is_active_wa = active_wa == wa;
549                         Buffer * b = &wa->bufferView().buffer();
550                         if (b->parent()) {
551                                 // This is a child document, just close the tab
552                                 // after saving but keep the file loaded.
553                                 if (!closeBuffer(*b, true, is_active_wa)) {
554                                         closing_ = false;
555                                         close_event->ignore();
556                                         return;
557                                 }
558                                 continue;
559                         }
560
561                         vector<Buffer *> clist = b->getChildren();
562                         for (vector<Buffer *>::const_iterator it = clist.begin();
563                                  it != clist.end(); ++it) {
564                                 if ((*it)->isClean())
565                                         continue;
566                                 Buffer * c = *it;
567                                 // If a child is dirty, do not close
568                                 // without user intervention
569                                 if (!closeBuffer(*c, false)) {
570                                         closing_ = false;
571                                         close_event->ignore();
572                                         return;
573                                 }
574                         }
575
576                         QList<int> const ids = guiApp->viewIds();
577                         for (int i = 0; i != ids.size(); ++i) {
578                                 if (id_ == ids[i])
579                                         continue;
580                                 if (guiApp->view(ids[i]).workArea(*b)) {
581                                         // FIXME 1: should we put an alert box here
582                                         // that the buffer is viewed elsewhere?
583                                         // FIXME 2: should we try to save this buffer in any case?
584                                         //saveBuffer(b);
585
586                                         // This buffer is also opened in another view, so
587                                         // close the associated work area...
588                                         removeWorkArea(wa);
589                                         // ... but don't close the buffer.
590                                         b = 0;
591                                         break;
592                                 }
593                         }
594                         // closeBuffer() needs buffer workArea still alive and
595                         // set as currrent one, and destroys it
596                         if (b && !closeBuffer(*b, true, is_active_wa)) {
597                                 closing_ = false;
598                                 close_event->ignore();
599                                 return;
600                         }
601                 }
602         }
603         // Make sure that nothing will use this close to be closed View.
604         guiApp->unregisterView(this);
605
606         if (isFullScreen()) {
607                 // Switch off fullscreen before closing.
608                 toggleFullScreen();
609                 updateDialogs();
610         }
611
612         // Make sure the timer time out will not trigger a statusbar update.
613         d.statusbar_timer_.stop();
614
615         // Saving fullscreen requires additional tweaks in the toolbar code.
616         // It wouldn't also work under linux natively.
617         if (lyxrc.allow_geometry_session) {
618                 // Save this window geometry and layout.
619                 saveLayout();
620                 // Then the toolbar private states.
621                 ToolbarMap::iterator end = d.toolbars_.end();
622                 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
623                         it->second->saveSession();
624                 // Now take care of all other dialogs:
625                 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
626                 for (; it!= d.dialogs_.end(); ++it)
627                         it->second->saveSession();
628         }
629
630         close_event->accept();
631 }
632
633
634 void GuiView::dragEnterEvent(QDragEnterEvent * event)
635 {
636         if (event->mimeData()->hasUrls())
637                 event->accept();
638         /// \todo Ask lyx-devel is this is enough:
639         /// if (event->mimeData()->hasFormat("text/plain"))
640         ///     event->acceptProposedAction();
641 }
642
643
644 void GuiView::dropEvent(QDropEvent * event)
645 {
646         QList<QUrl> files = event->mimeData()->urls();
647         if (files.isEmpty())
648                 return;
649
650         LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
651         for (int i = 0; i != files.size(); ++i) {
652                 string const file = os::internal_path(fromqstr(
653                         files.at(i).toLocalFile()));
654                 if (!file.empty()) {
655                         // Asynchronously post the event. DropEvent usually come
656                         // from the BufferView. But reloading a file might close
657                         // the BufferView from within its own event handler.
658                         guiApp->dispatchDelayed(FuncRequest(LFUN_FILE_OPEN, file));
659                         event->accept();
660                 }
661         }
662 }
663
664
665 void GuiView::message(docstring const & str)
666 {
667         if (ForkedProcess::iAmAChild())
668                 return;
669
670         statusBar()->showMessage(toqstr(str));
671         d.statusbar_timer_.stop();
672         d.statusbar_timer_.start(3000);
673 }
674
675
676 void GuiView::smallSizedIcons()
677 {
678         setIconSize(QSize(d.smallIconSize, d.smallIconSize));
679 }
680
681
682 void GuiView::normalSizedIcons()
683 {
684         setIconSize(QSize(d.normalIconSize, d.normalIconSize));
685 }
686
687
688 void GuiView::bigSizedIcons()
689 {
690         setIconSize(QSize(d.bigIconSize, d.bigIconSize));
691 }
692
693
694 void GuiView::clearMessage()
695 {
696         if (!hasFocus())
697                 return;
698         theLyXFunc().setLyXView(this);
699         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
700         d.statusbar_timer_.stop();
701 }
702
703
704 void GuiView::updateWindowTitle(GuiWorkArea * wa)
705 {
706         if (wa != d.current_work_area_)
707                 return;
708         setWindowTitle(qt_("LyX: ") + wa->windowTitle());
709         setWindowIconText(wa->windowIconText());
710 }
711
712
713 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
714 {
715         disconnectBuffer();
716         disconnectBufferView();
717         connectBufferView(wa->bufferView());
718         connectBuffer(wa->bufferView().buffer());
719         d.current_work_area_ = wa;
720         QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
721                 this, SLOT(updateWindowTitle(GuiWorkArea *)));
722         updateWindowTitle(wa);
723
724         structureChanged();
725
726         // The document settings needs to be reinitialised.
727         updateDialog("document", "");
728
729         // Buffer-dependent dialogs must be updated. This is done here because
730         // some dialogs require buffer()->text.
731         updateDialogs();
732 }
733
734
735 void GuiView::on_lastWorkAreaRemoved()
736 {
737         if (closing_)
738                 // We already are in a close event. Nothing more to do.
739                 return;
740
741         if (d.splitter_->count() > 1)
742                 // We have a splitter so don't close anything.
743                 return;
744
745         // Reset and updates the dialogs.
746         d.toc_models_.reset(0);
747         updateDialog("document", "");
748         updateDialogs();
749
750         resetWindowTitleAndIconText();
751
752         if (lyxrc.open_buffers_in_tabs)
753                 // Nothing more to do, the window should stay open.
754                 return;
755
756         if (guiApp->viewIds().size() > 1) {
757                 close();
758                 return;
759         }
760
761 #ifdef Q_WS_MACX
762         // On Mac we also close the last window because the application stay
763         // resident in memory. On other platforms we don't close the last
764         // window because this would quit the application.
765         close();
766 #endif
767 }
768
769
770 void GuiView::updateStatusBar()
771 {
772         // let the user see the explicit message
773         if (d.statusbar_timer_.isActive())
774                 return;
775
776         theLyXFunc().setLyXView(this);
777         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
778 }
779
780
781 bool GuiView::hasFocus() const
782 {
783         return qApp->activeWindow() == this;
784 }
785
786
787 bool GuiView::event(QEvent * e)
788 {
789         switch (e->type())
790         {
791         // Useful debug code:
792         //case QEvent::ActivationChange:
793         //case QEvent::WindowDeactivate:
794         //case QEvent::Paint:
795         //case QEvent::Enter:
796         //case QEvent::Leave:
797         //case QEvent::HoverEnter:
798         //case QEvent::HoverLeave:
799         //case QEvent::HoverMove:
800         //case QEvent::StatusTip:
801         //case QEvent::DragEnter:
802         //case QEvent::DragLeave:
803         //case QEvent::Drop:
804         //      break;
805
806         case QEvent::WindowActivate: {
807                 if (this == guiApp->currentView()) {
808                         setFocus();
809                         return QMainWindow::event(e);
810                 }
811                 guiApp->setCurrentView(this);
812                 theLyXFunc().setLyXView(this);
813                 if (d.current_work_area_) {
814                         BufferView & bv = d.current_work_area_->bufferView();
815                         connectBufferView(bv);
816                         connectBuffer(bv.buffer());
817                         // The document structure, name and dialogs might have
818                         // changed in another view.
819                         structureChanged();
820                         // The document settings needs to be reinitialised.
821                         updateDialog("document", "");
822                         updateDialogs();
823                 } else {
824                         resetWindowTitleAndIconText();
825                 }
826                 setFocus();
827                 return QMainWindow::event(e);
828         }
829
830         case QEvent::ShortcutOverride: {
831
832 // See bug 4888
833 #if (!defined Q_WS_X11) || (QT_VERSION >= 0x040500)
834                 if (isFullScreen() && menuBar()->isHidden()) {
835                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
836                         // FIXME: we should also try to detect special LyX shortcut such as
837                         // Alt-P and Alt-M. Right now there is a hack in
838                         // GuiWorkArea::processKeySym() that hides again the menubar for
839                         // those cases.
840                         if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
841                                 menuBar()->show();
842                                 return QMainWindow::event(e);
843                         }
844                 }
845 #endif
846
847                 if (d.current_work_area_)
848                         // Nothing special to do.
849                         return QMainWindow::event(e);
850
851                 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
852                 // Let Qt handle menu access and the Tab keys to navigate keys to navigate
853                 // between controls.
854                 if (ke->modifiers() & Qt::AltModifier || ke->key() == Qt::Key_Tab 
855                         || ke->key() == Qt::Key_Backtab)
856                         return QMainWindow::event(e);
857
858                 // Allow processing of shortcuts that are allowed even when no Buffer
859                 // is viewed.
860                 theLyXFunc().setLyXView(this);
861                 KeySymbol sym;
862                 setKeySymbol(&sym, ke);
863                 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
864                 e->accept();
865                 return true;
866         }
867
868         default:
869                 return QMainWindow::event(e);
870         }
871 }
872
873 void GuiView::resetWindowTitleAndIconText()
874 {
875     setWindowTitle(qt_("LyX"));
876     setWindowIconText(qt_("LyX"));
877 }
878
879 bool GuiView::focusNextPrevChild(bool /*next*/)
880 {
881         setFocus();
882         return true;
883 }
884
885
886 void GuiView::setBusy(bool busy)
887 {
888         if (d.current_work_area_) {
889                 d.current_work_area_->setUpdatesEnabled(!busy);
890                 if (busy)
891                         d.current_work_area_->stopBlinkingCursor();
892                 else
893                         d.current_work_area_->startBlinkingCursor();
894         }
895
896         if (busy)
897                 QApplication::setOverrideCursor(Qt::WaitCursor);
898         else
899                 QApplication::restoreOverrideCursor();
900 }
901
902
903 GuiWorkArea * GuiView::workArea(Buffer & buffer)
904 {
905         if (currentWorkArea()
906             && &currentWorkArea()->bufferView().buffer() == &buffer)
907                 return (GuiWorkArea *) currentWorkArea();
908         if (TabWorkArea * twa = d.currentTabWorkArea())
909                 return twa->workArea(buffer);
910         return 0;
911 }
912
913
914 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
915 {
916         // Automatically create a TabWorkArea if there are none yet.
917         TabWorkArea * tab_widget = d.splitter_->count() 
918                 ? d.currentTabWorkArea() : addTabWorkArea();
919         return tab_widget->addWorkArea(buffer, *this);
920 }
921
922
923 TabWorkArea * GuiView::addTabWorkArea()
924 {
925         TabWorkArea * twa = new TabWorkArea;
926         QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
927                 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
928         QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
929                          this, SLOT(on_lastWorkAreaRemoved()));
930
931         d.splitter_->addWidget(twa);
932         d.stack_widget_->setCurrentWidget(d.splitter_);
933         return twa;
934 }
935
936
937 GuiWorkArea const * GuiView::currentWorkArea() const
938 {
939         return d.current_work_area_;
940 }
941
942
943 GuiWorkArea * GuiView::currentWorkArea()
944 {
945         return d.current_work_area_;
946 }
947
948
949 GuiWorkArea const * GuiView::currentMainWorkArea() const
950 {
951         if (d.currentTabWorkArea() == NULL)
952                 return NULL;
953         return d.currentTabWorkArea()->currentWorkArea();
954 }
955
956
957 GuiWorkArea * GuiView::currentMainWorkArea()
958 {
959         if (d.currentTabWorkArea() == NULL)
960                 return NULL;
961         return d.currentTabWorkArea()->currentWorkArea();
962 }
963
964
965 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
966 {
967         LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
968         if (wa == NULL) {
969                 d.current_work_area_ = NULL;
970                 d.setBackground();
971                 return;
972         }
973         GuiWorkArea * old_gwa = theGuiApp()->currentView()->currentWorkArea();
974         if (old_gwa == wa)
975                 return;
976
977         theGuiApp()->setCurrentView(this);
978         d.current_work_area_ = wa;
979         for (int i = 0; i != d.splitter_->count(); ++i) {
980                 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
981                         //if (d.current_main_work_area_)
982                         //      d.current_main_work_area_->setFrameStyle(QFrame::NoFrame);
983                         d.current_main_work_area_ = wa;
984                         //d.current_main_work_area_->setFrameStyle(QFrame::Box | QFrame::Plain);
985                         //d.current_main_work_area_->setLineWidth(2);
986                         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
987                         return;
988                 }
989         }
990         LYXERR(Debug::DEBUG, "This is not a tabbed wa");
991         on_currentWorkAreaChanged(wa);
992         BufferView & bv = wa->bufferView();
993         bv.cursor().fixIfBroken();
994         bv.updateMetrics();
995         wa->setUpdatesEnabled(true);
996         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
997 }
998
999
1000 void GuiView::removeWorkArea(GuiWorkArea * wa)
1001 {
1002         LASSERT(wa, return);
1003         if (wa == d.current_work_area_) {
1004                 disconnectBuffer();
1005                 disconnectBufferView();
1006                 d.current_work_area_ = 0;
1007                 d.current_main_work_area_ = 0;
1008         }
1009
1010         bool found_twa = false;
1011         for (int i = 0; i != d.splitter_->count(); ++i) {
1012                 TabWorkArea * twa = d.tabWorkArea(i);
1013                 if (twa->removeWorkArea(wa)) {
1014                         // Found in this tab group, and deleted the GuiWorkArea.
1015                         found_twa = true;
1016                         if (twa->count() != 0) {
1017                                 if (d.current_work_area_ == 0)
1018                                         // This means that we are closing the current GuiWorkArea, so
1019                                         // switch to the next GuiWorkArea in the found TabWorkArea.
1020                                         setCurrentWorkArea(twa->currentWorkArea());
1021                         } else {
1022                                 // No more WorkAreas in this tab group, so delete it.
1023                                 delete twa;
1024                         }
1025                         break;
1026                 }
1027         }
1028
1029         // It is not a tabbed work area (i.e., the search work area), so it
1030         // should be deleted by other means.
1031         LASSERT(found_twa, /* */);
1032
1033         if (d.current_work_area_ == 0) {
1034                 if (d.splitter_->count() != 0) {
1035                         TabWorkArea * twa = d.currentTabWorkArea();
1036                         setCurrentWorkArea(twa->currentWorkArea());
1037                 } else {
1038                         // No more work areas, switch to the background widget.
1039                         setCurrentWorkArea(0);
1040                 }
1041         }
1042 }
1043
1044
1045 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
1046 {
1047         d.layout_ = layout;
1048 }
1049
1050
1051 void GuiView::updateLayoutList()
1052 {
1053         if (d.layout_)
1054                 d.layout_->updateContents(false);
1055 }
1056
1057
1058 void GuiView::updateToolbars()
1059 {
1060         ToolbarMap::iterator end = d.toolbars_.end();
1061         if (d.current_work_area_) {
1062                 bool const math =
1063                         d.current_work_area_->bufferView().cursor().inMathed();
1064                 bool const table =
1065                         lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
1066                 bool const review =
1067                         lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
1068                         lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
1069                 bool const mathmacrotemplate =
1070                         lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
1071
1072                 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1073                         it->second->update(math, table, review, mathmacrotemplate);
1074         } else
1075                 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1076                         it->second->update(false, false, false, false);
1077 }
1078
1079
1080 Buffer * GuiView::buffer()
1081 {
1082         if (d.current_work_area_)
1083                 return &d.current_work_area_->bufferView().buffer();
1084         return 0;
1085 }
1086
1087
1088 Buffer const * GuiView::buffer() const
1089 {
1090         if (d.current_work_area_)
1091                 return &d.current_work_area_->bufferView().buffer();
1092         return 0;
1093 }
1094
1095
1096 void GuiView::setBuffer(Buffer * newBuffer)
1097 {
1098         LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << std::endl);
1099         LASSERT(newBuffer, return);
1100         setBusy(true);
1101
1102         GuiWorkArea * wa = workArea(*newBuffer);
1103         if (wa == 0) {
1104                 newBuffer->masterBuffer()->updateLabels();
1105                 wa = addWorkArea(*newBuffer);
1106         } else {
1107                 //Disconnect the old buffer...there's no new one.
1108                 disconnectBuffer();
1109         }
1110         connectBuffer(*newBuffer);
1111         connectBufferView(wa->bufferView());
1112         setCurrentWorkArea(wa);
1113
1114         setBusy(false);
1115 }
1116
1117
1118 void GuiView::connectBuffer(Buffer & buf)
1119 {
1120         buf.setGuiDelegate(this);
1121 }
1122
1123
1124 void GuiView::disconnectBuffer()
1125 {
1126         if (d.current_work_area_)
1127                 d.current_work_area_->bufferView().setGuiDelegate(0);
1128 }
1129
1130
1131 void GuiView::connectBufferView(BufferView & bv)
1132 {
1133         bv.setGuiDelegate(this);
1134 }
1135
1136
1137 void GuiView::disconnectBufferView()
1138 {
1139         if (d.current_work_area_)
1140                 d.current_work_area_->bufferView().setGuiDelegate(0);
1141 }
1142
1143
1144 void GuiView::errors(string const & error_type)
1145 {
1146         ErrorList & el = buffer()->errorList(error_type);
1147         if (!el.empty())
1148                 showDialog("errorlist", error_type);
1149 }
1150
1151
1152 void GuiView::updateTocItem(std::string const & type, DocIterator const & dit)
1153 {
1154         d.toc_models_.updateItem(toqstr(type), dit);
1155 }
1156
1157
1158 void GuiView::structureChanged()
1159 {
1160         d.toc_models_.reset(view());
1161         // Navigator needs more than a simple update in this case. It needs to be
1162         // rebuilt.
1163         updateDialog("toc", "");
1164 }
1165
1166
1167 void GuiView::updateDialog(string const & name, string const & data)
1168 {
1169         if (!isDialogVisible(name))
1170                 return;
1171
1172         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1173         if (it == d.dialogs_.end())
1174                 return;
1175
1176         Dialog * const dialog = it->second.get();
1177         if (dialog->isVisibleView())
1178                 dialog->initialiseParams(data);
1179 }
1180
1181
1182 BufferView * GuiView::view()
1183 {
1184         return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1185 }
1186
1187
1188 void GuiView::autoSave()
1189 {
1190         LYXERR(Debug::INFO, "Running autoSave()");
1191
1192         if (buffer())
1193                 view()->buffer().autoSave();
1194 }
1195
1196
1197 void GuiView::resetAutosaveTimers()
1198 {
1199         if (lyxrc.autosave)
1200                 d.autosave_timeout_.restart();
1201 }
1202
1203
1204 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1205 {
1206         bool enable = true;
1207         Buffer * buf = buffer();
1208
1209         /* In LyX/Mac, when a dialog is open, the menus of the
1210            application can still be accessed without giving focus to
1211            the main window. In this case, we want to disable the menu
1212            entries that are buffer-related.
1213
1214            Note that this code is not perfect, as bug 1941 attests:
1215            http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
1216         */
1217         if (cmd.origin == FuncRequest::MENU && !hasFocus())
1218                 buf = 0;
1219
1220         if (cmd.origin == FuncRequest::TOC) {
1221                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1222                 FuncStatus fs;
1223                 if (toc->getStatus(view()->cursor(), cmd, fs))
1224                         flag |= fs;
1225                 else
1226                         flag.setEnabled(false);
1227                 return true;
1228         }
1229
1230         switch(cmd.action) {
1231         case LFUN_BUFFER_WRITE:
1232                 enable = buf && (buf->isUnnamed() || !buf->isClean());
1233                 break;
1234
1235         case LFUN_BUFFER_WRITE_AS:
1236                 enable = buf;
1237                 break;
1238
1239         case LFUN_SPLIT_VIEW:
1240                 if (cmd.getArg(0) == "vertical")
1241                         enable = buf && (d.splitter_->count() == 1 ||
1242                                          d.splitter_->orientation() == Qt::Vertical);
1243                 else
1244                         enable = buf && (d.splitter_->count() == 1 ||
1245                                          d.splitter_->orientation() == Qt::Horizontal);
1246                 break;
1247
1248         case LFUN_CLOSE_TAB_GROUP:
1249                 enable = d.currentTabWorkArea();
1250                 break;
1251
1252         case LFUN_TOOLBAR_TOGGLE:
1253                 if (GuiToolbar * t = toolbar(cmd.getArg(0)))
1254                         flag.setOnOff(t->isVisible());
1255                 break;
1256
1257         case LFUN_UI_TOGGLE:
1258                 flag.setOnOff(isFullScreen());
1259                 break;
1260
1261         case LFUN_DIALOG_TOGGLE:
1262                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
1263                 // fall through to set "enable"
1264         case LFUN_DIALOG_SHOW: {
1265                 string const name = cmd.getArg(0);
1266                 if (!buf)
1267                         enable = name == "aboutlyx"
1268                                 || name == "file" //FIXME: should be removed.
1269                                 || name == "prefs"
1270                                 || name == "texinfo";
1271                 else if (name == "print")
1272                         enable = buf->isExportable("dvi")
1273                                 && lyxrc.print_command != "none";
1274                 else if (name == "character") {
1275                         if (!view())
1276                                 enable = false;
1277                         else {
1278                                 InsetCode ic = view()->cursor().inset().lyxCode();
1279                                 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1280                         }
1281                 }
1282                 else if (name == "symbols") {
1283                         if (!view() || view()->cursor().inMathed())
1284                                 enable = false;
1285                         else {
1286                                 InsetCode ic = view()->cursor().inset().lyxCode();
1287                                 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1288                         }
1289                 }
1290                 else if (name == "latexlog")
1291                         enable = FileName(buf->logName()).isReadableFile();
1292                 else if (name == "spellchecker")
1293 #if defined (USE_ASPELL)
1294                         enable = !buf->isReadonly();
1295 #else
1296                         enable = false;
1297 #endif
1298                 else if (name == "vclog")
1299                         enable = buf->lyxvc().inUse();
1300                 break;
1301         }
1302
1303         case LFUN_DIALOG_UPDATE: {
1304                 string const name = cmd.getArg(0);
1305                 if (!buf)
1306                         enable = name == "prefs";
1307                 break;
1308         }
1309
1310         case LFUN_INSET_APPLY: {
1311                 string const name = cmd.getArg(0);
1312                 Inset * inset = getOpenInset(name);
1313                 if (inset) {
1314                         FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1315                         FuncStatus fs;
1316                         if (!inset->getStatus(view()->cursor(), fr, fs)) {
1317                                 // Every inset is supposed to handle this
1318                                 LASSERT(false, break);
1319                         }
1320                         flag |= fs;
1321                 } else {
1322                         FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1323                         flag |= lyx::getStatus(fr);
1324                 }
1325                 enable = flag.enabled();
1326                 break;
1327         }
1328
1329         case LFUN_COMPLETION_INLINE:
1330                 if (!d.current_work_area_
1331                     || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1332                     enable = false;
1333                 break;
1334
1335         case LFUN_COMPLETION_POPUP:
1336                 if (!d.current_work_area_
1337                     || !d.current_work_area_->completer().popupPossible(view()->cursor()))
1338                     enable = false;
1339                 break;
1340
1341         case LFUN_COMPLETION_COMPLETE:
1342                 if (!d.current_work_area_
1343                         || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1344                     enable = false;
1345                 break;
1346
1347         case LFUN_COMPLETION_ACCEPT:
1348                 if (!d.current_work_area_
1349                     || (!d.current_work_area_->completer().popupVisible()
1350                         && !d.current_work_area_->completer().inlineVisible()
1351                         && !d.current_work_area_->completer().completionAvailable()))
1352                         enable = false;
1353                 break;
1354
1355         case LFUN_COMPLETION_CANCEL:
1356                 if (!d.current_work_area_
1357                     || (!d.current_work_area_->completer().popupVisible()
1358                         && !d.current_work_area_->completer().inlineVisible()))
1359                         enable = false;
1360                 break;
1361
1362         case LFUN_BUFFER_ZOOM_OUT:
1363                 enable = buf && lyxrc.zoom > 10;
1364                 break;
1365
1366         case LFUN_BUFFER_ZOOM_IN:
1367                 enable = buf;
1368                 break;
1369
1370         default:
1371                 return false;
1372         }
1373
1374         if (!enable)
1375                 flag.setEnabled(false);
1376
1377         return true;
1378 }
1379
1380
1381 static FileName selectTemplateFile()
1382 {
1383         FileDialog dlg(qt_("Select template file"));
1384         dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1385         dlg.setButton1(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1386
1387         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
1388                              QStringList(qt_("LyX Documents (*.lyx)")));
1389
1390         if (result.first == FileDialog::Later)
1391                 return FileName();
1392         if (result.second.isEmpty())
1393                 return FileName();
1394         return FileName(fromqstr(result.second));
1395 }
1396
1397
1398 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
1399 {
1400         setBusy(true);
1401
1402         Buffer * newBuffer = checkAndLoadLyXFile(filename);
1403
1404         if (!newBuffer) {
1405                 message(_("Document not loaded."));
1406                 setBusy(false);
1407                 return 0;
1408         }
1409         
1410         setBuffer(newBuffer);
1411
1412         // scroll to the position when the file was last closed
1413         if (lyxrc.use_lastfilepos) {
1414                 LastFilePosSection::FilePos filepos =
1415                         theSession().lastFilePos().load(filename);
1416                 view()->moveToPosition(filepos.pit, filepos.pos, 0, 0);
1417         }
1418
1419         if (tolastfiles)
1420                 theSession().lastFiles().add(filename);
1421
1422         setBusy(false);
1423         return newBuffer;
1424 }
1425
1426
1427 void GuiView::openDocument(string const & fname)
1428 {
1429         string initpath = lyxrc.document_path;
1430
1431         if (buffer()) {
1432                 string const trypath = buffer()->filePath();
1433                 // If directory is writeable, use this as default.
1434                 if (FileName(trypath).isDirWritable())
1435                         initpath = trypath;
1436         }
1437
1438         string filename;
1439
1440         if (fname.empty()) {
1441                 FileDialog dlg(qt_("Select document to open"), LFUN_FILE_OPEN);
1442                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1443                 dlg.setButton2(qt_("Examples|#E#e"),
1444                                 toqstr(addPath(package().system_support().absFilename(), "examples")));
1445
1446                 QStringList filter(qt_("LyX Documents (*.lyx)"));
1447                 filter << qt_("LyX-1.3.x Documents (*.lyx13)")
1448                         << qt_("LyX-1.4.x Documents (*.lyx14)")
1449                         << qt_("LyX-1.5.x Documents (*.lyx15)")
1450                         << qt_("LyX-1.6.x Documents (*.lyx16)");
1451                 FileDialog::Result result =
1452                         dlg.open(toqstr(initpath), filter);
1453
1454                 if (result.first == FileDialog::Later)
1455                         return;
1456
1457                 filename = fromqstr(result.second);
1458
1459                 // check selected filename
1460                 if (filename.empty()) {
1461                         message(_("Canceled."));
1462                         return;
1463                 }
1464         } else
1465                 filename = fname;
1466
1467         // get absolute path of file and add ".lyx" to the filename if
1468         // necessary. 
1469         FileName const fullname = 
1470                         fileSearch(string(), filename, "lyx", support::may_not_exist);
1471         if (!fullname.empty())
1472                 filename = fullname.absFilename();
1473
1474         if (!fullname.onlyPath().isDirectory()) {
1475                 Alert::warning(_("Invalid filename"),
1476                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
1477                                 from_utf8(fullname.absFilename())));
1478                 return;
1479         }
1480         // if the file doesn't exist, let the user create one
1481         if (!fullname.exists()) {
1482                 // the user specifically chose this name. Believe him.
1483                 Buffer * const b = newFile(filename, string(), true);
1484                 if (b)
1485                         setBuffer(b);
1486                 return;
1487         }
1488
1489         docstring const disp_fn = makeDisplayPath(filename);
1490         message(bformat(_("Opening document %1$s..."), disp_fn));
1491
1492         docstring str2;
1493         Buffer * buf = loadDocument(fullname);
1494         if (buf) {
1495                 buf->updateLabels();
1496                 setBuffer(buf);
1497                 buf->errors("Parse");
1498                 str2 = bformat(_("Document %1$s opened."), disp_fn);
1499                 if (buf->lyxvc().inUse())
1500                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
1501                                 " " + _("Version control detected.");
1502         } else {
1503                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1504         }
1505         message(str2);
1506 }
1507
1508 // FIXME: clean that
1509 static bool import(GuiView * lv, FileName const & filename,
1510         string const & format, ErrorList & errorList)
1511 {
1512         FileName const lyxfile(support::changeExtension(filename.absFilename(), ".lyx"));
1513
1514         string loader_format;
1515         vector<string> loaders = theConverters().loaders();
1516         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
1517                 for (vector<string>::const_iterator it = loaders.begin();
1518                      it != loaders.end(); ++it) {
1519                         if (!theConverters().isReachable(format, *it))
1520                                 continue;
1521
1522                         string const tofile =
1523                                 support::changeExtension(filename.absFilename(),
1524                                 formats.extension(*it));
1525                         if (!theConverters().convert(0, filename, FileName(tofile),
1526                                 filename, format, *it, errorList))
1527                                 return false;
1528                         loader_format = *it;
1529                         break;
1530                 }
1531                 if (loader_format.empty()) {
1532                         frontend::Alert::error(_("Couldn't import file"),
1533                                      bformat(_("No information for importing the format %1$s."),
1534                                          formats.prettyName(format)));
1535                         return false;
1536                 }
1537         } else
1538                 loader_format = format;
1539
1540         if (loader_format == "lyx") {
1541                 Buffer * buf = lv->loadDocument(lyxfile);
1542                 if (!buf)
1543                         return false;
1544                 buf->updateLabels();
1545                 lv->setBuffer(buf);
1546                 buf->errors("Parse");
1547         } else {
1548                 Buffer * const b = newFile(lyxfile.absFilename(), string(), true);
1549                 if (!b)
1550                         return false;
1551                 lv->setBuffer(b);
1552                 bool as_paragraphs = loader_format == "textparagraph";
1553                 string filename2 = (loader_format == format) ? filename.absFilename()
1554                         : support::changeExtension(filename.absFilename(),
1555                                           formats.extension(loader_format));
1556                 lv->view()->insertPlaintextFile(FileName(filename2), as_paragraphs);
1557                 theLyXFunc().setLyXView(lv);
1558                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
1559         }
1560
1561         return true;
1562 }
1563
1564
1565 void GuiView::importDocument(string const & argument)
1566 {
1567         string format;
1568         string filename = split(argument, format, ' ');
1569
1570         LYXERR(Debug::INFO, format << " file: " << filename);
1571
1572         // need user interaction
1573         if (filename.empty()) {
1574                 string initpath = lyxrc.document_path;
1575
1576                 Buffer const * buf = buffer();
1577                 if (buf) {
1578                         string const trypath = buf->filePath();
1579                         // If directory is writeable, use this as default.
1580                         if (FileName(trypath).isDirWritable())
1581                                 initpath = trypath;
1582                 }
1583
1584                 docstring const text = bformat(_("Select %1$s file to import"),
1585                         formats.prettyName(format));
1586
1587                 FileDialog dlg(toqstr(text), LFUN_BUFFER_IMPORT);
1588                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1589                 dlg.setButton2(qt_("Examples|#E#e"),
1590                         toqstr(addPath(package().system_support().absFilename(), "examples")));
1591
1592                 docstring filter = formats.prettyName(format);
1593                 filter += " (*.";
1594                 // FIXME UNICODE
1595                 filter += from_utf8(formats.extension(format));
1596                 filter += ')';
1597
1598                 FileDialog::Result result =
1599                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
1600
1601                 if (result.first == FileDialog::Later)
1602                         return;
1603
1604                 filename = fromqstr(result.second);
1605
1606                 // check selected filename
1607                 if (filename.empty())
1608                         message(_("Canceled."));
1609         }
1610
1611         if (filename.empty())
1612                 return;
1613
1614         // get absolute path of file
1615         FileName const fullname(support::makeAbsPath(filename));
1616
1617         FileName const lyxfile(support::changeExtension(fullname.absFilename(), ".lyx"));
1618
1619         // Check if the document already is open
1620         Buffer * buf = theBufferList().getBuffer(lyxfile);
1621         if (buf) {
1622                 setBuffer(buf);
1623                 if (!closeBuffer()) {
1624                         message(_("Canceled."));
1625                         return;
1626                 }
1627         }
1628
1629         docstring const displaypath = makeDisplayPath(lyxfile.absFilename(), 30);
1630
1631         // if the file exists already, and we didn't do
1632         // -i lyx thefile.lyx, warn
1633         if (lyxfile.exists() && fullname != lyxfile) {
1634
1635                 docstring text = bformat(_("The document %1$s already exists.\n\n"
1636                         "Do you want to overwrite that document?"), displaypath);
1637                 int const ret = Alert::prompt(_("Overwrite document?"),
1638                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
1639
1640                 if (ret == 1) {
1641                         message(_("Canceled."));
1642                         return;
1643                 }
1644         }
1645
1646         message(bformat(_("Importing %1$s..."), displaypath));
1647         ErrorList errorList;
1648         if (import(this, fullname, format, errorList))
1649                 message(_("imported."));
1650         else
1651                 message(_("file not imported!"));
1652
1653         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
1654 }
1655
1656
1657 void GuiView::newDocument(string const & filename, bool from_template)
1658 {
1659         FileName initpath(lyxrc.document_path);
1660         Buffer * buf = buffer();
1661         if (buf) {
1662                 FileName const trypath(buf->filePath());
1663                 // If directory is writeable, use this as default.
1664                 if (trypath.isDirWritable())
1665                         initpath = trypath;
1666         }
1667
1668         string templatefile;
1669         if (from_template) {
1670                 templatefile = selectTemplateFile().absFilename();
1671                 if (templatefile.empty())
1672                         return;
1673         }
1674         
1675         Buffer * b;
1676         if (filename.empty())
1677                 b = newUnnamedFile(templatefile, initpath);
1678         else
1679                 b = newFile(filename, templatefile, true);
1680
1681         if (b)
1682                 setBuffer(b);
1683
1684         // If no new document could be created, it is unsure 
1685         // whether there is a valid BufferView.
1686         if (view())
1687                 // Ensure the cursor is correctly positioned on screen.
1688                 view()->showCursor();
1689 }
1690
1691
1692 void GuiView::insertLyXFile(docstring const & fname)
1693 {
1694         BufferView * bv = view();
1695         if (!bv)
1696                 return;
1697
1698         // FIXME UNICODE
1699         FileName filename(to_utf8(fname));
1700         
1701         if (!filename.empty()) {
1702                 bv->insertLyXFile(filename);
1703                 return;
1704         }
1705
1706         // Launch a file browser
1707         // FIXME UNICODE
1708         string initpath = lyxrc.document_path;
1709         string const trypath = bv->buffer().filePath();
1710         // If directory is writeable, use this as default.
1711         if (FileName(trypath).isDirWritable())
1712                 initpath = trypath;
1713
1714         // FIXME UNICODE
1715         FileDialog dlg(qt_("Select LyX document to insert"), LFUN_FILE_INSERT);
1716         dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1717         dlg.setButton2(qt_("Examples|#E#e"),
1718                 toqstr(addPath(package().system_support().absFilename(),
1719                 "examples")));
1720
1721         FileDialog::Result result = dlg.open(toqstr(initpath),
1722                              QStringList(qt_("LyX Documents (*.lyx)")));
1723
1724         if (result.first == FileDialog::Later)
1725                 return;
1726
1727         // FIXME UNICODE
1728         filename.set(fromqstr(result.second));
1729
1730         // check selected filename
1731         if (filename.empty()) {
1732                 // emit message signal.
1733                 message(_("Canceled."));
1734                 return;
1735         }
1736
1737         bv->insertLyXFile(filename);
1738 }
1739
1740
1741 void GuiView::insertPlaintextFile(docstring const & fname,
1742         bool asParagraph)
1743 {
1744         BufferView * bv = view();
1745         if (!bv)
1746                 return;
1747
1748         if (!fname.empty() && !FileName::isAbsolute(to_utf8(fname))) {
1749                 message(_("Absolute filename expected."));
1750                 return;
1751         }
1752
1753         // FIXME UNICODE
1754         FileName filename(to_utf8(fname));
1755         
1756         if (!filename.empty()) {
1757                 bv->insertPlaintextFile(filename, asParagraph);
1758                 return;
1759         }
1760
1761         FileDialog dlg(qt_("Select file to insert"), (asParagraph ?
1762                 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1763
1764         FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
1765                 QStringList(qt_("All Files (*)")));
1766
1767         if (result.first == FileDialog::Later)
1768                 return;
1769
1770         // FIXME UNICODE
1771         filename.set(fromqstr(result.second));
1772
1773         // check selected filename
1774         if (filename.empty()) {
1775                 // emit message signal.
1776                 message(_("Canceled."));
1777                 return;
1778         }
1779
1780         bv->insertPlaintextFile(filename, asParagraph);
1781 }
1782
1783
1784 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1785 {
1786         FileName fname = b.fileName();
1787         FileName const oldname = fname;
1788
1789         if (!newname.empty()) {
1790                 // FIXME UNICODE
1791                 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1792         } else {
1793                 // Switch to this Buffer.
1794                 setBuffer(&b);
1795
1796                 // No argument? Ask user through dialog.
1797                 // FIXME UNICODE
1798                 FileDialog dlg(qt_("Choose a filename to save document as"),
1799                                    LFUN_BUFFER_WRITE_AS);
1800                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1801                 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1802
1803                 if (!isLyXFilename(fname.absFilename()))
1804                         fname.changeExtension(".lyx");
1805
1806                 FileDialog::Result result =
1807                         dlg.save(toqstr(fname.onlyPath().absFilename()),
1808                                QStringList(qt_("LyX Documents (*.lyx)")),
1809                                      toqstr(fname.onlyFileName()));
1810
1811                 if (result.first == FileDialog::Later)
1812                         return false;
1813
1814                 fname.set(fromqstr(result.second));
1815
1816                 if (fname.empty())
1817                         return false;
1818
1819                 if (!isLyXFilename(fname.absFilename()))
1820                         fname.changeExtension(".lyx");
1821         }
1822
1823         if (FileName(fname).exists()) {
1824                 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1825                 docstring text = bformat(_("The document %1$s already "
1826                                            "exists.\n\nDo you want to "
1827                                            "overwrite that document?"), 
1828                                          file);
1829                 int const ret = Alert::prompt(_("Overwrite document?"),
1830                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1831                 switch (ret) {
1832                 case 0: break;
1833                 case 1: return renameBuffer(b, docstring());
1834                 case 2: return false;
1835                 }
1836         }
1837
1838         FileName oldauto = b.getAutosaveFilename();
1839
1840         // Ok, change the name of the buffer
1841         b.setFileName(fname.absFilename());
1842         b.markDirty();
1843         bool unnamed = b.isUnnamed();
1844         b.setUnnamed(false);
1845         b.saveCheckSum(fname);
1846
1847         // bring the autosave file with us, just in case.
1848         b.moveAutosaveFile(oldauto);
1849         
1850         if (!saveBuffer(b)) {
1851                 oldauto = b.getAutosaveFilename();
1852                 b.setFileName(oldname.absFilename());
1853                 b.setUnnamed(unnamed);
1854                 b.saveCheckSum(oldname);
1855                 b.moveAutosaveFile(oldauto);
1856                 return false;
1857         }
1858
1859         return true;
1860 }
1861
1862
1863 bool GuiView::saveBuffer(Buffer & b)
1864 {
1865         if (workArea(b) && workArea(b)->inDialogMode())
1866                 return true;
1867
1868         if (b.isUnnamed())
1869                 return renameBuffer(b, docstring());
1870
1871         if (b.save()) {
1872                 theSession().lastFiles().add(b.fileName());
1873                 return true;
1874         }
1875
1876         // Switch to this Buffer.
1877         setBuffer(&b);
1878
1879         // FIXME: we don't tell the user *WHY* the save failed !!
1880         docstring const file = makeDisplayPath(b.absFileName(), 30);
1881         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1882                                    "Do you want to rename the document and "
1883                                    "try again?"), file);
1884         int const ret = Alert::prompt(_("Rename and save?"),
1885                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1886         switch (ret) {
1887         case 0:
1888                 if (!renameBuffer(b, docstring()))
1889                         return false;
1890                 break;
1891         case 1:
1892                 break;
1893         case 2:
1894                 return false;
1895         }
1896
1897         return saveBuffer(b);
1898 }
1899
1900
1901 bool GuiView::closeBuffer()
1902 {
1903         Buffer * buf = buffer();
1904         return buf && closeBuffer(*buf);
1905 }
1906
1907
1908 bool GuiView::closeBuffer(Buffer & buf, bool tolastopened, bool mark_active)
1909 {
1910         // goto bookmark to update bookmark pit.
1911         //FIXME: we should update only the bookmarks related to this buffer!
1912         LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
1913         for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
1914                 theLyXFunc().gotoBookmark(i+1, false, false);
1915
1916         if (buf.isClean() || buf.paragraphs().empty()) {
1917                 // save in sessions if requested
1918                 // do not save childs if their master
1919                 // is opened as well
1920                 if (tolastopened)
1921                         theSession().lastOpened().add(buf.fileName(), mark_active);
1922                 if (buf.parent())
1923                         // Don't close child documents.
1924                         removeWorkArea(currentMainWorkArea());
1925                 else
1926                         theBufferList().release(&buf);
1927                 return true;
1928         }
1929         // Switch to this Buffer.
1930         setBuffer(&buf);
1931
1932         docstring file;
1933         // FIXME: Unicode?
1934         if (buf.isUnnamed())
1935                 file = from_utf8(buf.fileName().onlyFileName());
1936         else
1937                 file = buf.fileName().displayName(30);
1938
1939         // Bring this window to top before asking questions.
1940         raise();
1941         activateWindow();
1942
1943         docstring const text = bformat(_("The document %1$s has unsaved changes."
1944                 "\n\nDo you want to save the document or discard the changes?"), file);
1945         int const ret = Alert::prompt(_("Save changed document?"),
1946                 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1947
1948         switch (ret) {
1949         case 0:
1950                 if (!saveBuffer(buf))
1951                         return false;
1952                 break;
1953         case 1:
1954                 // if we crash after this we could
1955                 // have no autosave file but I guess
1956                 // this is really improbable (Jug)
1957                 buf.removeAutosaveFile();
1958                 break;
1959         case 2:
1960                 return false;
1961         }
1962
1963         // save file names to .lyx/session
1964         if (tolastopened)
1965                 theSession().lastOpened().add(buf.fileName(), mark_active);
1966
1967         if (buf.parent())
1968                 // Don't close child documents.
1969                 removeWorkArea(currentMainWorkArea());
1970         else
1971                 theBufferList().release(&buf);
1972
1973         return true;
1974 }
1975
1976
1977 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np)
1978 {
1979         Buffer * const curbuf = buffer();
1980         Buffer * nextbuf = curbuf;
1981         while (true) {
1982                 if (np == NEXTBUFFER)
1983                         nextbuf = theBufferList().next(nextbuf);
1984                 else
1985                         nextbuf = theBufferList().previous(nextbuf);
1986                 if (nextbuf == curbuf)
1987                         break;
1988                 if (nextbuf == 0) {
1989                         nextbuf = curbuf;
1990                         break;
1991                 }
1992                 if (workArea(*nextbuf))
1993                         break;
1994         }
1995         setBuffer(nextbuf);
1996 }
1997
1998
1999 bool GuiView::dispatch(FuncRequest const & cmd)
2000 {
2001         BufferView * bv = view();
2002         // By default we won't need any update.
2003         if (bv)
2004                 bv->cursor().updateFlags(Update::None);
2005         bool dispatched = true;
2006
2007         if (cmd.origin == FuncRequest::TOC) {
2008                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2009                 toc->doDispatch(bv->cursor(), cmd);
2010                 return true;
2011         }
2012
2013         switch(cmd.action) {
2014                 case LFUN_BUFFER_IMPORT:
2015                         importDocument(to_utf8(cmd.argument()));
2016                         break;
2017
2018                 case LFUN_BUFFER_SWITCH:
2019                         if (FileName::isAbsolute(to_utf8(cmd.argument()))) {
2020                                 Buffer * buffer = 
2021                                         theBufferList().getBuffer(FileName(to_utf8(cmd.argument())));
2022                                 if (buffer)
2023                                         setBuffer(buffer);
2024                                 else
2025                                         bv->cursor().message(_("Document not loaded"));
2026                         }
2027                         break;
2028
2029                 case LFUN_BUFFER_NEXT:
2030                         gotoNextOrPreviousBuffer(NEXTBUFFER);
2031                         break;
2032
2033                 case LFUN_BUFFER_PREVIOUS:
2034                         gotoNextOrPreviousBuffer(PREVBUFFER);
2035                         break;
2036
2037                 case LFUN_COMMAND_EXECUTE: {
2038                         bool const show_it = cmd.argument() != "off";
2039                         // FIXME: this is a hack, "minibuffer" should not be
2040                         // hardcoded.
2041                         if (GuiToolbar * t = toolbar("minibuffer")) {
2042                                 t->setVisible(show_it);
2043                                 if (show_it && t->commandBuffer())
2044                                         t->commandBuffer()->setFocus();
2045                         }
2046                         break;
2047                 }
2048                 case LFUN_DROP_LAYOUTS_CHOICE:
2049                         if (d.layout_)
2050                                 d.layout_->showPopup();
2051                         break;
2052
2053                 case LFUN_MENU_OPEN:
2054                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
2055                                 menu->exec(QCursor::pos());
2056                         break;
2057
2058                 case LFUN_FILE_INSERT:
2059                         insertLyXFile(cmd.argument());
2060                         break;
2061                 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2062                         insertPlaintextFile(cmd.argument(), true);
2063                         break;
2064
2065                 case LFUN_FILE_INSERT_PLAINTEXT:
2066                         insertPlaintextFile(cmd.argument(), false);
2067                         break;
2068
2069                 case LFUN_BUFFER_WRITE:
2070                         if (bv)
2071                                 saveBuffer(bv->buffer());
2072                         break;
2073
2074                 case LFUN_BUFFER_WRITE_AS:
2075                         if (bv)
2076                                 renameBuffer(bv->buffer(), cmd.argument());
2077                         break;
2078
2079                 case LFUN_BUFFER_WRITE_ALL: {
2080                         Buffer * first = theBufferList().first();
2081                         if (!first)
2082                                 break;
2083                         message(_("Saving all documents..."));
2084                         // We cannot use a for loop as the buffer list cycles.
2085                         Buffer * b = first;
2086                         do {
2087                                 if (!b->isClean()) {
2088                                         saveBuffer(*b);
2089                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
2090                                 }
2091                                 b = theBufferList().next(b);
2092                         } while (b != first); 
2093                         message(_("All documents saved."));
2094                         break;
2095                 }
2096
2097                 case LFUN_TOOLBAR_TOGGLE: {
2098                         string const name = cmd.getArg(0);
2099                         if (GuiToolbar * t = toolbar(name))
2100                                 t->toggle();
2101                         break;
2102                 }
2103
2104                 case LFUN_DIALOG_UPDATE: {
2105                         string const name = to_utf8(cmd.argument());
2106                         // Can only update a dialog connected to an existing inset
2107                         Inset * inset = getOpenInset(name);
2108                         if (inset) {
2109                                 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
2110                                 inset->dispatch(view()->cursor(), fr);
2111                         } else if (name == "paragraph") {
2112                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
2113                         } else if (name == "prefs" || name == "document") {
2114                                 updateDialog(name, string());
2115                         }
2116                         break;
2117                 }
2118
2119                 case LFUN_DIALOG_TOGGLE: {
2120                         if (isDialogVisible(cmd.getArg(0)))
2121                                 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
2122                         else
2123                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
2124                         break;
2125                 }
2126
2127                 case LFUN_DIALOG_DISCONNECT_INSET:
2128                         disconnectDialog(to_utf8(cmd.argument()));
2129                         break;
2130
2131                 case LFUN_DIALOG_HIDE: {
2132                         guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
2133                         break;
2134                 }
2135
2136                 case LFUN_DIALOG_SHOW: {
2137                         string const name = cmd.getArg(0);
2138                         string data = trim(to_utf8(cmd.argument()).substr(name.size()));
2139
2140                         if (name == "character") {
2141                                 data = freefont2string();
2142                                 if (!data.empty())
2143                                         showDialog("character", data);
2144                         } else if (name == "latexlog") {
2145                                 Buffer::LogType type; 
2146                                 string const logfile = buffer()->logName(&type);
2147                                 switch (type) {
2148                                 case Buffer::latexlog:
2149                                         data = "latex ";
2150                                         break;
2151                                 case Buffer::buildlog:
2152                                         data = "literate ";
2153                                         break;
2154                                 }
2155                                 data += Lexer::quoteString(logfile);
2156                                 showDialog("log", data);
2157                         } else if (name == "vclog") {
2158                                 string const data = "vc " +
2159                                         Lexer::quoteString(buffer()->lyxvc().getLogFile());
2160                                 showDialog("log", data);
2161                         } else if (name == "symbols") {
2162                                 data = bv->cursor().getEncoding()->name();
2163                                 if (!data.empty())
2164                                         showDialog("symbols", data);
2165                         // bug 5274
2166                         } else if (name == "prefs" && isFullScreen()) {
2167                                 FuncRequest fr(LFUN_INSET_INSERT, "fullscreen");
2168                                 lfunUiToggle(fr);
2169                                 showDialog("prefs", data);
2170                         } else
2171                                 showDialog(name, data);
2172                         break;
2173                 }
2174
2175                 case LFUN_INSET_APPLY: {
2176                         string const name = cmd.getArg(0);
2177                         Inset * inset = getOpenInset(name);
2178                         if (inset) {
2179                                 // put cursor in front of inset.
2180                                 if (!view()->setCursorFromInset(inset)) {
2181                                         LASSERT(false, break);
2182                                 }
2183                                 
2184                                 // useful if we are called from a dialog.
2185                                 view()->cursor().beginUndoGroup();
2186                                 view()->cursor().recordUndo();
2187                                 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
2188                                 inset->dispatch(view()->cursor(), fr);
2189                                 view()->cursor().endUndoGroup();
2190                         } else {
2191                                 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
2192                                 lyx::dispatch(fr);
2193                         }
2194                         break;
2195                 }
2196
2197                 case LFUN_UI_TOGGLE:
2198                         lfunUiToggle(cmd);
2199                         // Make sure the keyboard focus stays in the work area.
2200                         setFocus();
2201                         break;
2202
2203                 case LFUN_SPLIT_VIEW:
2204                         if (Buffer * buf = buffer()) {
2205                                 string const orientation = cmd.getArg(0);
2206                                 d.splitter_->setOrientation(orientation == "vertical"
2207                                         ? Qt::Vertical : Qt::Horizontal);
2208                                 TabWorkArea * twa = addTabWorkArea();
2209                                 GuiWorkArea * wa = twa->addWorkArea(*buf, *this);
2210                                 setCurrentWorkArea(wa);
2211                         }
2212                         break;
2213
2214                 case LFUN_CLOSE_TAB_GROUP:
2215                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
2216                                 delete twa;
2217                                 twa = d.currentTabWorkArea();
2218                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
2219                                 if (twa) {
2220                                         // Make sure the work area is up to date.
2221                                         setCurrentWorkArea(twa->currentWorkArea());
2222                                 } else {
2223                                         setCurrentWorkArea(0);
2224                                 }
2225                         }
2226                         break;
2227                         
2228                 case LFUN_COMPLETION_INLINE:
2229                         if (d.current_work_area_)
2230                                 d.current_work_area_->completer().showInline();
2231                         break;
2232
2233                 case LFUN_COMPLETION_POPUP:
2234                         if (d.current_work_area_)
2235                                 d.current_work_area_->completer().showPopup();
2236                         break;
2237
2238
2239                 case LFUN_COMPLETION_COMPLETE:
2240                         if (d.current_work_area_)
2241                                 d.current_work_area_->completer().tab();
2242                         break;
2243
2244                 case LFUN_COMPLETION_CANCEL:
2245                         if (d.current_work_area_) {
2246                                 if (d.current_work_area_->completer().popupVisible())
2247                                         d.current_work_area_->completer().hidePopup();
2248                                 else
2249                                         d.current_work_area_->completer().hideInline();
2250                         }
2251                         break;
2252
2253                 case LFUN_COMPLETION_ACCEPT:
2254                         if (d.current_work_area_)
2255                                 d.current_work_area_->completer().activate();
2256                         break;
2257
2258                 case LFUN_BUFFER_ZOOM_IN:
2259                 case LFUN_BUFFER_ZOOM_OUT:
2260                         if (cmd.argument().empty()) {
2261                                 if (cmd.action == LFUN_BUFFER_ZOOM_IN)
2262                                         lyxrc.zoom += 20;
2263                                 else
2264                                         lyxrc.zoom -= 20;
2265                         } else
2266                                 lyxrc.zoom += convert<int>(cmd.argument());
2267
2268                         if (lyxrc.zoom < 10)
2269                                 lyxrc.zoom = 10;
2270                                 
2271                         // The global QPixmapCache is used in GuiPainter to cache text
2272                         // painting so we must reset it.
2273                         QPixmapCache::clear();
2274                         guiApp->fontLoader().update();
2275                         lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
2276                         break;
2277
2278                 default:
2279                         dispatched = false;
2280                         break;
2281         }
2282
2283         // Part of automatic menu appearance feature.
2284         if (isFullScreen()) {
2285                 if (menuBar()->isVisible())
2286                         menuBar()->hide();
2287                 if (statusBar()->isVisible())
2288                         statusBar()->hide();
2289         }
2290
2291         return dispatched;
2292 }
2293
2294
2295 void GuiView::lfunUiToggle(FuncRequest const & cmd)
2296 {
2297         string const arg = cmd.getArg(0);
2298         if (arg == "scrollbar") {
2299                 // hide() is of no help
2300                 if (d.current_work_area_->verticalScrollBarPolicy() ==
2301                         Qt::ScrollBarAlwaysOff)
2302
2303                         d.current_work_area_->setVerticalScrollBarPolicy(
2304                                 Qt::ScrollBarAsNeeded);
2305                 else
2306                         d.current_work_area_->setVerticalScrollBarPolicy(
2307                                 Qt::ScrollBarAlwaysOff);
2308                 return;
2309         }
2310         if (arg == "statusbar") {
2311                 statusBar()->setVisible(!statusBar()->isVisible());
2312                 return;
2313         }
2314         if (arg == "menubar") {
2315                 menuBar()->setVisible(!menuBar()->isVisible());
2316                 return;
2317         }
2318 #if QT_VERSION >= 0x040300
2319         if (arg == "frame") {
2320                 int l, t, r, b;
2321                 getContentsMargins(&l, &t, &r, &b);
2322                 //are the frames in default state?
2323                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
2324                 if (l == 0) {
2325                         setContentsMargins(-2, -2, -2, -2);
2326                 } else {
2327                         setContentsMargins(0, 0, 0, 0);
2328                 }
2329                 return;
2330         }
2331 #endif
2332         if (arg == "fullscreen") {
2333                 toggleFullScreen();
2334                 return;
2335         }
2336
2337         message(bformat("LFUN_UI_TOGGLE " + _("%1$s unknown command!"), from_utf8(arg)));
2338 }
2339
2340
2341 void GuiView::toggleFullScreen()
2342 {
2343         if (isFullScreen()) {
2344                 for (int i = 0; i != d.splitter_->count(); ++i)
2345                         d.tabWorkArea(i)->setFullScreen(false);
2346 #if QT_VERSION >= 0x040300
2347                 setContentsMargins(0, 0, 0, 0);
2348 #endif
2349                 setWindowState(windowState() ^ Qt::WindowFullScreen);
2350                 restoreLayout();
2351                 menuBar()->show();
2352                 statusBar()->show();
2353         } else {
2354                 // bug 5274
2355                 hideDialogs("prefs", 0);
2356                 for (int i = 0; i != d.splitter_->count(); ++i)
2357                         d.tabWorkArea(i)->setFullScreen(true);
2358 #if QT_VERSION >= 0x040300
2359                 setContentsMargins(-2, -2, -2, -2);
2360 #endif
2361                 saveLayout();
2362                 setWindowState(windowState() ^ Qt::WindowFullScreen);
2363                 statusBar()->hide();
2364                 menuBar()->hide();
2365                 if (lyxrc.full_screen_toolbars) {
2366                         ToolbarMap::iterator end = d.toolbars_.end();
2367                         for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
2368                                 it->second->hide();
2369                 }
2370         }
2371
2372         // give dialogs like the TOC a chance to adapt
2373         updateDialogs();
2374 }
2375
2376
2377 Buffer const * GuiView::updateInset(Inset const * inset)
2378 {
2379         if (!d.current_work_area_)
2380                 return 0;
2381
2382         if (inset)
2383                 d.current_work_area_->scheduleRedraw();
2384
2385         return &d.current_work_area_->bufferView().buffer();
2386 }
2387
2388
2389 void GuiView::restartCursor()
2390 {
2391         /* When we move around, or type, it's nice to be able to see
2392          * the cursor immediately after the keypress.
2393          */
2394         if (d.current_work_area_)
2395                 d.current_work_area_->startBlinkingCursor();
2396
2397         // Take this occasion to update the other GUI elements.
2398         updateDialogs();
2399         updateStatusBar();
2400 }
2401
2402
2403 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
2404 {
2405         if (d.current_work_area_)
2406                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
2407 }
2408
2409 namespace {
2410
2411 // This list should be kept in sync with the list of insets in
2412 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
2413 // dialog should have the same name as the inset.
2414 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
2415 // docs in LyXAction.cpp.
2416
2417 char const * const dialognames[] = {
2418 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
2419 "citation", "document", "errorlist", "ert", "external", "file", "findreplace",
2420 "findreplaceadv", "float", "graphics", "href", "include", "index",
2421 "index_print", "info", "listings", "label", "log", "mathdelimiter",
2422 "mathmatrix", "mathspace", "nomenclature", "note", "paragraph", "phantom",
2423 "prefs", "print", "ref", "sendto", "space", "spellchecker", "symbols",
2424 "tabular", "tabularcreate", "thesaurus", "texinfo", "toc", "view-source",
2425 "vspace", "wrap",  " };
2426
2427 char const * const * const end_dialognames =
2428         dialognames + (sizeof(dialognames) / sizeof(char *));
2429
2430 class cmpCStr {
2431 public:
2432         cmpCStr(char const * name) : name_(name) {}
2433         bool operator()(char const * other) {
2434                 return strcmp(other, name_) == 0;
2435         }
2436 private:
2437         char const * name_;
2438 };
2439
2440
2441 bool isValidName(string const & name)
2442 {
2443         return find_if(dialognames, end_dialognames,
2444                             cmpCStr(name.c_str())) != end_dialognames;
2445 }
2446
2447 } // namespace anon
2448
2449
2450 void GuiView::resetDialogs()
2451 {
2452         // Make sure that no LFUN uses any LyXView.
2453         theLyXFunc().setLyXView(0);
2454         saveLayout();
2455         menuBar()->clear();
2456         constructToolbars();
2457         guiApp->menus().fillMenuBar(menuBar(), this, false);
2458         if (d.layout_)
2459                 d.layout_->updateContents(true);
2460         // Now update controls with current buffer.
2461         theLyXFunc().setLyXView(this);
2462         restoreLayout();
2463         restartCursor();
2464 }
2465
2466
2467 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
2468 {
2469         if (!isValidName(name))
2470                 return 0;
2471
2472         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
2473
2474         if (it != d.dialogs_.end()) {
2475                 if (hide_it)
2476                         it->second->hideView();
2477                 return it->second.get();
2478         }
2479
2480         Dialog * dialog = build(name);
2481         d.dialogs_[name].reset(dialog);
2482         if (lyxrc.allow_geometry_session)
2483                 dialog->restoreSession();
2484         if (hide_it)
2485                 dialog->hideView();
2486         return dialog;
2487 }
2488
2489
2490 void GuiView::showDialog(string const & name, string const & data,
2491         Inset * inset)
2492 {
2493         if (d.in_show_)
2494                 return;
2495
2496         d.in_show_ = true;
2497         try {
2498                 Dialog * dialog = findOrBuild(name, false);
2499                 if (dialog) {
2500                         dialog->showData(data);
2501                         if (inset)
2502                                 d.open_insets_[name] = inset;
2503                 }
2504         }
2505         catch (ExceptionMessage const & ex) {
2506                 d.in_show_ = false;
2507                 throw ex;
2508         }
2509         d.in_show_ = false;
2510 }
2511
2512
2513 bool GuiView::isDialogVisible(string const & name) const
2514 {
2515         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2516         if (it == d.dialogs_.end())
2517                 return false;
2518         return it->second.get()->isVisibleView();
2519 }
2520
2521
2522 void GuiView::hideDialog(string const & name, Inset * inset)
2523 {
2524         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2525         if (it == d.dialogs_.end())
2526                 return;
2527
2528         if (inset && inset != getOpenInset(name))
2529                 return;
2530
2531         Dialog * const dialog = it->second.get();
2532         if (dialog->isVisibleView())
2533                 dialog->hideView();
2534         d.open_insets_[name] = 0;
2535 }
2536
2537
2538 void GuiView::disconnectDialog(string const & name)
2539 {
2540         if (!isValidName(name))
2541                 return;
2542
2543         if (d.open_insets_.find(name) != d.open_insets_.end())
2544                 d.open_insets_[name] = 0;
2545 }
2546
2547
2548 Inset * GuiView::getOpenInset(string const & name) const
2549 {
2550         if (!isValidName(name))
2551                 return 0;
2552
2553         map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
2554         return it == d.open_insets_.end() ? 0 : it->second;
2555 }
2556
2557
2558 void GuiView::hideAll() const
2559 {
2560         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
2561         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2562
2563         for(; it != end; ++it)
2564                 it->second->hideView();
2565 }
2566
2567
2568 void GuiView::updateDialogs()
2569 {
2570         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
2571         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2572
2573         for(; it != end; ++it) {
2574                 Dialog * dialog = it->second.get();
2575                 if (dialog && dialog->isVisibleView())
2576                         dialog->checkStatus();
2577         }
2578         updateToolbars();
2579         updateLayoutList();
2580 }
2581
2582
2583 // will be replaced by a proper factory...
2584 Dialog * createGuiAbout(GuiView & lv);
2585 Dialog * createGuiBibitem(GuiView & lv);
2586 Dialog * createGuiBibtex(GuiView & lv);
2587 Dialog * createGuiBox(GuiView & lv);
2588 Dialog * createGuiBranch(GuiView & lv);
2589 Dialog * createGuiChanges(GuiView & lv);
2590 Dialog * createGuiCharacter(GuiView & lv);
2591 Dialog * createGuiCitation(GuiView & lv);
2592 Dialog * createGuiDelimiter(GuiView & lv);
2593 Dialog * createGuiDocument(GuiView & lv);
2594 Dialog * createGuiErrorList(GuiView & lv);
2595 Dialog * createGuiERT(GuiView & lv);
2596 Dialog * createGuiExternal(GuiView & lv);
2597 Dialog * createGuiFloat(GuiView & lv);
2598 Dialog * createGuiGraphics(GuiView & lv);
2599 Dialog * createGuiInclude(GuiView & lv);
2600 Dialog * createGuiIndex(GuiView & lv);
2601 Dialog * createGuiInfo(GuiView & lv);
2602 Dialog * createGuiLabel(GuiView & lv);
2603 Dialog * createGuiListings(GuiView & lv);
2604 Dialog * createGuiLog(GuiView & lv);
2605 Dialog * createGuiMathHSpace(GuiView & lv);
2606 Dialog * createGuiMathMatrix(GuiView & lv);
2607 Dialog * createGuiNomenclature(GuiView & lv);
2608 Dialog * createGuiNote(GuiView & lv);
2609 Dialog * createGuiParagraph(GuiView & lv);
2610 Dialog * createGuiPhantom(GuiView & lv);
2611 Dialog * createGuiPreferences(GuiView & lv);
2612 Dialog * createGuiPrint(GuiView & lv);
2613 Dialog * createGuiPrintindex(GuiView & lv);
2614 Dialog * createGuiRef(GuiView & lv);
2615 Dialog * createGuiSearch(GuiView & lv);
2616 Dialog * createGuiSearchAdv(GuiView & lv);
2617 Dialog * createGuiSendTo(GuiView & lv);
2618 Dialog * createGuiShowFile(GuiView & lv);
2619 Dialog * createGuiSpellchecker(GuiView & lv);
2620 Dialog * createGuiSymbols(GuiView & lv);
2621 Dialog * createGuiTabularCreate(GuiView & lv);
2622 Dialog * createGuiTabular(GuiView & lv);
2623 Dialog * createGuiTexInfo(GuiView & lv);
2624 Dialog * createGuiTextHSpace(GuiView & lv);
2625 Dialog * createGuiToc(GuiView & lv);
2626 Dialog * createGuiThesaurus(GuiView & lv);
2627 Dialog * createGuiHyperlink(GuiView & lv);
2628 Dialog * createGuiVSpace(GuiView & lv);
2629 Dialog * createGuiViewSource(GuiView & lv);
2630 Dialog * createGuiWrap(GuiView & lv);
2631
2632
2633 Dialog * GuiView::build(string const & name)
2634 {
2635         LASSERT(isValidName(name), return 0);
2636
2637         if (name == "aboutlyx")
2638                 return createGuiAbout(*this);
2639         if (name == "bibitem")
2640                 return createGuiBibitem(*this);
2641         if (name == "bibtex")
2642                 return createGuiBibtex(*this);
2643         if (name == "box")
2644                 return createGuiBox(*this);
2645         if (name == "branch")
2646                 return createGuiBranch(*this);
2647         if (name == "changes")
2648                 return createGuiChanges(*this);
2649         if (name == "character")
2650                 return createGuiCharacter(*this);
2651         if (name == "citation")
2652                 return createGuiCitation(*this);
2653         if (name == "document")
2654                 return createGuiDocument(*this);
2655         if (name == "errorlist")
2656                 return createGuiErrorList(*this);
2657         if (name == "ert")
2658                 return createGuiERT(*this);
2659         if (name == "external")
2660                 return createGuiExternal(*this);
2661         if (name == "file")
2662                 return createGuiShowFile(*this);
2663         if (name == "findreplace")
2664                 return createGuiSearch(*this);
2665         if (name == "findreplaceadv")
2666                 return createGuiSearchAdv(*this);
2667         if (name == "float")
2668                 return createGuiFloat(*this);
2669         if (name == "graphics")
2670                 return createGuiGraphics(*this);
2671         if (name == "include")
2672                 return createGuiInclude(*this);
2673         if (name == "index")
2674                 return createGuiIndex(*this);
2675         if (name == "info")
2676                 return createGuiInfo(*this);
2677         if (name == "nomenclature")
2678                 return createGuiNomenclature(*this);
2679         if (name == "label")
2680                 return createGuiLabel(*this);
2681         if (name == "log")
2682                 return createGuiLog(*this);
2683         if (name == "mathdelimiter")
2684                 return createGuiDelimiter(*this);
2685         if (name == "mathspace")
2686                 return createGuiMathHSpace(*this);
2687         if (name == "mathmatrix")
2688                 return createGuiMathMatrix(*this);
2689         if (name == "note")
2690                 return createGuiNote(*this);
2691         if (name == "paragraph")
2692                 return createGuiParagraph(*this);
2693         if (name == "phantom")
2694                 return createGuiPhantom(*this);
2695         if (name == "prefs")
2696                 return createGuiPreferences(*this);
2697         if (name == "print")
2698                 return createGuiPrint(*this);
2699         if (name == "ref")
2700                 return createGuiRef(*this);
2701         if (name == "sendto")
2702                 return createGuiSendTo(*this);
2703         if (name == "space")
2704                 return createGuiTextHSpace(*this);
2705         if (name == "spellchecker")
2706                 return createGuiSpellchecker(*this);
2707         if (name == "symbols")
2708                 return createGuiSymbols(*this);
2709         if (name == "tabular")
2710                 return createGuiTabular(*this);
2711         if (name == "tabularcreate")
2712                 return createGuiTabularCreate(*this);
2713         if (name == "texinfo")
2714                 return createGuiTexInfo(*this);
2715         if (name == "view-source")
2716                 return createGuiViewSource(*this);
2717         if (name == "thesaurus")
2718                 return createGuiThesaurus(*this);
2719         if (name == "href")
2720                 return createGuiHyperlink(*this);
2721         if (name == "index_print")
2722                 return createGuiPrintindex(*this);
2723         if (name == "listings")
2724                 return createGuiListings(*this);
2725         if (name == "toc")
2726                 return createGuiToc(*this);
2727         if (name == "vspace")
2728                 return createGuiVSpace(*this);
2729         if (name == "wrap")
2730                 return createGuiWrap(*this);
2731
2732         return 0;
2733 }
2734
2735
2736 } // namespace frontend
2737 } // namespace lyx
2738
2739 #include "moc_GuiView.cpp"