3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
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"
27 #include "GuiToolbar.h"
31 #include "qt_helpers.h"
33 #include "frontends/alert.h"
35 #include "buffer_funcs.h"
37 #include "BufferList.h"
38 #include "BufferParams.h"
39 #include "BufferView.h"
40 #include "Converter.h"
43 #include "ErrorList.h"
45 #include "FuncStatus.h"
46 #include "FuncRequest.h"
54 #include "Paragraph.h"
55 #include "TextClass.h"
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"
74 #include <QApplication>
75 #include <QCloseEvent>
77 #include <QDesktopWidget>
78 #include <QDragEnterEvent>
85 #include <QPixmapCache>
87 #include <QPushButton>
91 #include <QStackedWidget>
98 #include <boost/bind.hpp>
100 #ifdef HAVE_SYS_TIME_H
101 # include <sys/time.h>
108 using namespace lyx::support;
115 class BackgroundWidget : public QWidget
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");
126 QPainter pain(&splash_);
127 pain.setPen(QColor(0, 0, 0));
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()));
134 pain.drawText(260, 15, text);
137 void paintEvent(QPaintEvent *)
139 int x = (width() - splash_.width()) / 2;
140 int y = (height() - splash_.height()) / 2;
142 pain.drawPixmap(x, y, splash_);
150 /// Toolbar store providing access to individual toolbars by name.
151 typedef std::map<std::string, GuiToolbar *> ToolbarMap;
153 typedef boost::shared_ptr<Dialog> DialogPtr;
158 struct GuiView::GuiViewPrivate
161 : current_work_area_(0), current_main_work_area_(0),
162 layout_(0), autosave_timeout_(5000),
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
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_);
182 delete stack_widget_;
185 QMenu * toolBarPopup(GuiView * parent)
187 // FIXME: translation
188 QMenu * menu = new QMenu(parent);
189 QActionGroup * iconSizeGroup = new QActionGroup(parent);
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);
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);
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);
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);
225 stack_widget_->setCurrentWidget(bg_widget_);
226 bg_widget_->setUpdatesEnabled(true);
229 TabWorkArea * tabWorkArea(int i)
231 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
234 TabWorkArea * currentTabWorkArea()
236 if (splitter_->count() == 1)
237 // The first TabWorkArea is always the first one, if any.
238 return tabWorkArea(0);
240 for (int i = 0; i != splitter_->count(); ++i) {
241 TabWorkArea * twa = tabWorkArea(i);
242 if (current_main_work_area_ == twa->currentWorkArea())
246 // None has the focus so we just take the first one.
247 return tabWorkArea(0);
251 GuiWorkArea * current_work_area_;
252 GuiWorkArea * current_main_work_area_;
253 QSplitter * splitter_;
254 QStackedWidget * stack_widget_;
255 BackgroundWidget * bg_widget_;
257 ToolbarMap toolbars_;
258 /// The main layout box.
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.
264 * FIXME: replace that with a proper model so that we are not limited
265 * to only one dialog.
267 GuiLayoutBox * layout_;
270 map<string, Inset *> open_insets_;
273 map<string, DialogPtr> dialogs_;
275 unsigned int smallIconSize;
276 unsigned int normalIconSize;
277 unsigned int bigIconSize;
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
286 TocModels toc_models_;
290 GuiView::GuiView(int id)
291 : d(*new GuiViewPrivate), id_(id), closing_(false)
293 // GuiToolbars *must* be initialised before the menu bar.
294 normalSizedIcons(); // at least on Mac the default is 32 otherwise, which is huge
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);
302 // Fill up the menu bar.
303 guiApp->menus().fillMenuBar(menuBar(), this, true);
305 setCentralWidget(d.stack_widget_);
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();
313 connect(&d.statusbar_timer_, SIGNAL(timeout()),
314 this, SLOT(clearMessage()));
316 // We don't want to keep the window in memory if it is closed.
317 setAttribute(Qt::WA_DeleteOnClose, true);
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"));
326 setAcceptDrops(true);
328 statusBar()->setSizeGripEnabled(true);
330 // Forbid too small unresizable window because it can happen
331 // with some window manager under X11.
332 setMinimumSize(300, 200);
334 if (lyxrc.allow_geometry_session) {
335 // Now take care of session management.
340 // no session handling, default to a sane size.
341 setGeometry(50, 50, 690, 510);
344 // clear session data if any.
346 settings.remove("views");
356 void GuiView::saveLayout() const
359 settings.beginGroup("views");
360 settings.beginGroup(QString::number(id_));
362 settings.setValue("pos", pos());
363 settings.setValue("size", size());
365 settings.setValue("geometry", saveGeometry());
367 settings.setValue("layout", saveState(0));
368 settings.setValue("icon_size", iconSize());
372 bool GuiView::restoreLayout()
375 settings.beginGroup("views");
376 settings.beginGroup(QString::number(id_));
377 QString const icon_key = "icon_size";
378 if (!settings.contains(icon_key))
381 setIconSize(settings.value(icon_key).toSize());
383 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
384 QSize size = settings.value("size", QSize(690, 510)).toSize();
388 if (!restoreGeometry(settings.value("geometry").toByteArray()))
389 setGeometry(50, 50, 690, 510);
391 // Make sure layout is correctly oriented.
392 setLayoutDirection(qApp->layoutDirection());
394 // Allow the toc and view-source dock widget to be restored if needed.
396 if ((d = findOrBuild("toc", true)))
399 if ((d = findOrBuild("view-source", true)))
402 if (!restoreState(settings.value("layout").toByteArray(), 0))
409 GuiToolbar * GuiView::toolbar(string const & name)
411 ToolbarMap::iterator it = d.toolbars_.find(name);
412 if (it != d.toolbars_.end())
415 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
416 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
421 void GuiView::constructToolbars()
423 ToolbarMap::iterator it = d.toolbars_.begin();
424 for (; it != d.toolbars_.end(); ++it)
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);
437 void GuiView::initToolbars()
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);
446 int const visibility = guiApp->toolbars().defaultVisibility(cit->name);
448 tb->setVisible(false);
449 tb->setVisibility(visibility);
451 if (visibility & Toolbars::TOP) {
453 addToolBarBreak(Qt::TopToolBarArea);
454 addToolBar(Qt::TopToolBarArea, tb);
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);
462 addToolBar(Qt::BottomToolBarArea, tb);
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);
470 addToolBar(Qt::LeftToolBarArea, tb);
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);
478 addToolBar(Qt::RightToolBarArea, tb);
481 if (visibility & Toolbars::ON)
482 tb->setVisible(true);
487 TocModels & GuiView::tocModels()
489 return d.toc_models_;
493 void GuiView::setFocus()
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();
505 QMenu * GuiView::createPopupMenu()
507 return d.toolBarPopup(this);
511 void GuiView::showEvent(QShowEvent * e)
513 LYXERR(Debug::GUI, "Passed Geometry "
514 << size().height() << "x" << size().width()
515 << "+" << pos().x() << "+" << pos().y());
517 if (d.splitter_->count() == 0)
518 // No work area, switch to the background widget.
521 QMainWindow::showEvent(e);
525 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
526 ** is responsibility of the container (e.g., dialog)
528 void GuiView::closeEvent(QCloseEvent * close_event)
530 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
533 // it can happen that this event arrives without selecting the view,
534 // e.g. when clicking the close button on a background window.
536 setCurrentWorkArea(currentMainWorkArea());
537 while (GuiWorkArea * wa = currentMainWorkArea()) {
538 Buffer * b = &wa->bufferView().buffer();
540 // This is a child document, just close the tab
541 // after saving but keep the file loaded.
542 if (!closeBuffer(*b, true)) {
544 close_event->ignore();
550 vector<Buffer *> clist = b->getChildren();
551 for (vector<Buffer *>::const_iterator it = clist.begin();
552 it != clist.end(); ++it) {
553 if ((*it)->isClean())
556 // If a child is dirty, do not close
557 // without user intervention
558 if (!closeBuffer(*c, false)) {
560 close_event->ignore();
565 QList<int> const ids = guiApp->viewIds();
566 for (int i = 0; i != ids.size(); ++i) {
569 if (guiApp->view(ids[i]).workArea(*b)) {
570 // FIXME 1: should we put an alert box here that the buffer
571 // is viewed elsewhere?
572 // FIXME 2: should we try to save this buffer in any case?
575 // This buffer is also opened in another view, so
576 // close the associated work area...
578 // ... but don't close the buffer.
583 // closeBuffer() needs buffer workArea still alive and set as currrent one, and destroys it
584 if (b && !closeBuffer(*b, true)) {
586 close_event->ignore();
591 // Make sure that nothing will use this close to be closed View.
592 guiApp->unregisterView(this);
594 if (isFullScreen()) {
595 // Switch off fullscreen before closing.
600 // Make sure the timer time out will not trigger a statusbar update.
601 d.statusbar_timer_.stop();
603 // Saving fullscreen requires additional tweaks in the toolbar code.
604 // It wouldn't also work under linux natively.
605 if (lyxrc.allow_geometry_session) {
606 // Save this window geometry and layout.
608 // Then the toolbar private states.
609 ToolbarMap::iterator end = d.toolbars_.end();
610 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
611 it->second->saveSession();
612 // Now take care of all other dialogs:
613 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
614 for (; it!= d.dialogs_.end(); ++it)
615 it->second->saveSession();
618 close_event->accept();
622 void GuiView::dragEnterEvent(QDragEnterEvent * event)
624 if (event->mimeData()->hasUrls())
626 /// \todo Ask lyx-devel is this is enough:
627 /// if (event->mimeData()->hasFormat("text/plain"))
628 /// event->acceptProposedAction();
632 void GuiView::dropEvent(QDropEvent * event)
634 QList<QUrl> files = event->mimeData()->urls();
638 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
639 for (int i = 0; i != files.size(); ++i) {
640 string const file = os::internal_path(fromqstr(
641 files.at(i).toLocalFile()));
643 // Asynchronously post the event. DropEvent usually come
644 // from the BufferView. But reloading a file might close
645 // the BufferView from within its own event handler.
646 guiApp->dispatchDelayed(FuncRequest(LFUN_FILE_OPEN, file));
653 void GuiView::message(docstring const & str)
655 if (ForkedProcess::iAmAChild())
658 statusBar()->showMessage(toqstr(str));
659 d.statusbar_timer_.stop();
660 d.statusbar_timer_.start(3000);
664 void GuiView::smallSizedIcons()
666 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
670 void GuiView::normalSizedIcons()
672 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
676 void GuiView::bigSizedIcons()
678 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
682 void GuiView::clearMessage()
686 theLyXFunc().setLyXView(this);
687 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
688 d.statusbar_timer_.stop();
692 void GuiView::updateWindowTitle(GuiWorkArea * wa)
694 if (wa != d.current_work_area_)
696 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
697 setWindowIconText(wa->windowIconText());
701 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
704 disconnectBufferView();
705 connectBufferView(wa->bufferView());
706 connectBuffer(wa->bufferView().buffer());
707 d.current_work_area_ = wa;
708 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
709 this, SLOT(updateWindowTitle(GuiWorkArea *)));
710 updateWindowTitle(wa);
714 // The document settings needs to be reinitialised.
715 updateDialog("document", "");
717 // Buffer-dependent dialogs must be updated. This is done here because
718 // some dialogs require buffer()->text.
723 void GuiView::on_lastWorkAreaRemoved()
726 // We already are in a close event. Nothing more to do.
729 if (d.splitter_->count() > 1)
730 // We have a splitter so don't close anything.
733 // Reset and updates the dialogs.
734 d.toc_models_.reset(0);
735 updateDialog("document", "");
738 resetWindowTitleAndIconText();
740 if (lyxrc.open_buffers_in_tabs)
741 // Nothing more to do, the window should stay open.
744 if (guiApp->viewIds().size() > 1) {
750 // On Mac we also close the last window because the application stay
751 // resident in memory. On other platforms we don't close the last
752 // window because this would quit the application.
758 void GuiView::updateStatusBar()
760 // let the user see the explicit message
761 if (d.statusbar_timer_.isActive())
764 theLyXFunc().setLyXView(this);
765 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
769 bool GuiView::hasFocus() const
771 return qApp->activeWindow() == this;
775 bool GuiView::event(QEvent * e)
779 // Useful debug code:
780 //case QEvent::ActivationChange:
781 //case QEvent::WindowDeactivate:
782 //case QEvent::Paint:
783 //case QEvent::Enter:
784 //case QEvent::Leave:
785 //case QEvent::HoverEnter:
786 //case QEvent::HoverLeave:
787 //case QEvent::HoverMove:
788 //case QEvent::StatusTip:
789 //case QEvent::DragEnter:
790 //case QEvent::DragLeave:
794 case QEvent::WindowActivate: {
795 if (this == guiApp->currentView()) {
797 return QMainWindow::event(e);
799 guiApp->setCurrentView(this);
800 theLyXFunc().setLyXView(this);
801 if (d.current_work_area_) {
802 BufferView & bv = d.current_work_area_->bufferView();
803 connectBufferView(bv);
804 connectBuffer(bv.buffer());
805 // The document structure, name and dialogs might have
806 // changed in another view.
808 // The document settings needs to be reinitialised.
809 updateDialog("document", "");
812 resetWindowTitleAndIconText();
815 return QMainWindow::event(e);
818 case QEvent::ShortcutOverride: {
822 if (isFullScreen() && menuBar()->isHidden()) {
823 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
824 // FIXME: we should also try to detect special LyX shortcut such as
825 // Alt-P and Alt-M. Right now there is a hack in
826 // GuiWorkArea::processKeySym() that hides again the menubar for
828 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
830 return QMainWindow::event(e);
835 if (d.current_work_area_)
836 // Nothing special to do.
837 return QMainWindow::event(e);
839 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
840 // Let Qt handle menu access and the Tab keys to navigate keys to navigate
842 if (ke->modifiers() & Qt::AltModifier || ke->key() == Qt::Key_Tab
843 || ke->key() == Qt::Key_Backtab)
844 return QMainWindow::event(e);
846 // Allow processing of shortcuts that are allowed even when no Buffer
848 theLyXFunc().setLyXView(this);
850 setKeySymbol(&sym, ke);
851 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
857 return QMainWindow::event(e);
861 void GuiView::resetWindowTitleAndIconText()
863 setWindowTitle(qt_("LyX"));
864 setWindowIconText(qt_("LyX"));
867 bool GuiView::focusNextPrevChild(bool /*next*/)
874 void GuiView::setBusy(bool busy)
876 if (d.current_work_area_) {
877 d.current_work_area_->setUpdatesEnabled(!busy);
879 d.current_work_area_->stopBlinkingCursor();
881 d.current_work_area_->startBlinkingCursor();
885 QApplication::setOverrideCursor(Qt::WaitCursor);
887 QApplication::restoreOverrideCursor();
891 GuiWorkArea * GuiView::workArea(Buffer & buffer)
893 if (currentWorkArea()
894 && ¤tWorkArea()->bufferView().buffer() == &buffer)
895 return (GuiWorkArea *) currentWorkArea();
896 if (TabWorkArea * twa = d.currentTabWorkArea())
897 return twa->workArea(buffer);
902 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
904 // Automatically create a TabWorkArea if there are none yet.
905 TabWorkArea * tab_widget = d.splitter_->count()
906 ? d.currentTabWorkArea() : addTabWorkArea();
907 return tab_widget->addWorkArea(buffer, *this);
911 TabWorkArea * GuiView::addTabWorkArea()
913 TabWorkArea * twa = new TabWorkArea;
914 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
915 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
916 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
917 this, SLOT(on_lastWorkAreaRemoved()));
919 d.splitter_->addWidget(twa);
920 d.stack_widget_->setCurrentWidget(d.splitter_);
925 GuiWorkArea const * GuiView::currentWorkArea() const
927 return d.current_work_area_;
931 GuiWorkArea * GuiView::currentWorkArea()
933 return d.current_work_area_;
937 GuiWorkArea const * GuiView::currentMainWorkArea() const
939 if (d.currentTabWorkArea() == NULL)
941 return d.currentTabWorkArea()->currentWorkArea();
945 GuiWorkArea * GuiView::currentMainWorkArea()
947 if (d.currentTabWorkArea() == NULL)
949 return d.currentTabWorkArea()->currentWorkArea();
953 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
955 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
957 d.current_work_area_ = NULL;
961 GuiWorkArea * old_gwa = theGuiApp()->currentView()->currentWorkArea();
965 theGuiApp()->setCurrentView(this);
966 d.current_work_area_ = wa;
967 for (int i = 0; i != d.splitter_->count(); ++i) {
968 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
969 //if (d.current_main_work_area_)
970 // d.current_main_work_area_->setFrameStyle(QFrame::NoFrame);
971 d.current_main_work_area_ = wa;
972 //d.current_main_work_area_->setFrameStyle(QFrame::Box | QFrame::Plain);
973 //d.current_main_work_area_->setLineWidth(2);
974 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
978 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
979 on_currentWorkAreaChanged(wa);
980 BufferView & bv = wa->bufferView();
981 bv.cursor().fixIfBroken();
983 wa->setUpdatesEnabled(true);
984 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
988 void GuiView::removeWorkArea(GuiWorkArea * wa)
991 if (wa == d.current_work_area_) {
993 disconnectBufferView();
994 d.current_work_area_ = 0;
995 d.current_main_work_area_ = 0;
998 bool found_twa = false;
999 for (int i = 0; i != d.splitter_->count(); ++i) {
1000 TabWorkArea * twa = d.tabWorkArea(i);
1001 if (twa->removeWorkArea(wa)) {
1002 // Found in this tab group, and deleted the GuiWorkArea.
1004 if (twa->count() != 0) {
1005 if (d.current_work_area_ == 0)
1006 // This means that we are closing the current GuiWorkArea, so
1007 // switch to the next GuiWorkArea in the found TabWorkArea.
1008 setCurrentWorkArea(twa->currentWorkArea());
1010 // No more WorkAreas in this tab group, so delete it.
1017 // It is not a tabbed work area (i.e., the search work area), so it
1018 // should be deleted by other means.
1019 LASSERT(found_twa, /* */);
1021 if (d.current_work_area_ == 0) {
1022 if (d.splitter_->count() != 0) {
1023 TabWorkArea * twa = d.currentTabWorkArea();
1024 setCurrentWorkArea(twa->currentWorkArea());
1026 // No more work areas, switch to the background widget.
1027 setCurrentWorkArea(0);
1033 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
1039 void GuiView::updateLayoutList()
1042 d.layout_->updateContents(false);
1046 void GuiView::updateToolbars()
1048 ToolbarMap::iterator end = d.toolbars_.end();
1049 if (d.current_work_area_) {
1051 d.current_work_area_->bufferView().cursor().inMathed();
1053 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
1055 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
1056 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
1057 bool const mathmacrotemplate =
1058 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
1060 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1061 it->second->update(math, table, review, mathmacrotemplate);
1063 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1064 it->second->update(false, false, false, false);
1068 Buffer * GuiView::buffer()
1070 if (d.current_work_area_)
1071 return &d.current_work_area_->bufferView().buffer();
1076 Buffer const * GuiView::buffer() const
1078 if (d.current_work_area_)
1079 return &d.current_work_area_->bufferView().buffer();
1084 void GuiView::setBuffer(Buffer * newBuffer)
1086 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << std::endl);
1087 LASSERT(newBuffer, return);
1090 GuiWorkArea * wa = workArea(*newBuffer);
1092 newBuffer->masterBuffer()->updateLabels();
1093 wa = addWorkArea(*newBuffer);
1095 //Disconnect the old buffer...there's no new one.
1098 connectBuffer(*newBuffer);
1099 connectBufferView(wa->bufferView());
1100 setCurrentWorkArea(wa);
1106 void GuiView::connectBuffer(Buffer & buf)
1108 buf.setGuiDelegate(this);
1112 void GuiView::disconnectBuffer()
1114 if (d.current_work_area_)
1115 d.current_work_area_->bufferView().setGuiDelegate(0);
1119 void GuiView::connectBufferView(BufferView & bv)
1121 bv.setGuiDelegate(this);
1125 void GuiView::disconnectBufferView()
1127 if (d.current_work_area_)
1128 d.current_work_area_->bufferView().setGuiDelegate(0);
1132 void GuiView::errors(string const & error_type)
1134 ErrorList & el = buffer()->errorList(error_type);
1136 showDialog("errorlist", error_type);
1140 void GuiView::updateTocItem(std::string const & type, DocIterator const & dit)
1142 d.toc_models_.updateItem(toqstr(type), dit);
1146 void GuiView::structureChanged()
1148 d.toc_models_.reset(view());
1149 // Navigator needs more than a simple update in this case. It needs to be
1151 updateDialog("toc", "");
1155 void GuiView::updateDialog(string const & name, string const & data)
1157 if (!isDialogVisible(name))
1160 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1161 if (it == d.dialogs_.end())
1164 Dialog * const dialog = it->second.get();
1165 if (dialog->isVisibleView())
1166 dialog->initialiseParams(data);
1170 BufferView * GuiView::view()
1172 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1176 void GuiView::autoSave()
1178 LYXERR(Debug::INFO, "Running autoSave()");
1181 view()->buffer().autoSave();
1185 void GuiView::resetAutosaveTimers()
1188 d.autosave_timeout_.restart();
1192 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1195 Buffer * buf = buffer();
1197 /* In LyX/Mac, when a dialog is open, the menus of the
1198 application can still be accessed without giving focus to
1199 the main window. In this case, we want to disable the menu
1200 entries that are buffer-related.
1202 Note that this code is not perfect, as bug 1941 attests:
1203 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
1205 if (cmd.origin == FuncRequest::MENU && !hasFocus())
1208 if (cmd.origin == FuncRequest::TOC) {
1209 //FIXME: dispatch this to the toc
1213 switch(cmd.action) {
1214 case LFUN_BUFFER_WRITE:
1215 enable = buf && (buf->isUnnamed() || !buf->isClean());
1218 case LFUN_BUFFER_WRITE_AS:
1222 case LFUN_SPLIT_VIEW:
1223 if (cmd.getArg(0) == "vertical")
1224 enable = buf && (d.splitter_->count() == 1 ||
1225 d.splitter_->orientation() == Qt::Vertical);
1227 enable = buf && (d.splitter_->count() == 1 ||
1228 d.splitter_->orientation() == Qt::Horizontal);
1231 case LFUN_CLOSE_TAB_GROUP:
1232 enable = d.currentTabWorkArea();
1235 case LFUN_TOOLBAR_TOGGLE:
1236 if (GuiToolbar * t = toolbar(cmd.getArg(0)))
1237 flag.setOnOff(t->isVisible());
1240 case LFUN_UI_TOGGLE:
1241 flag.setOnOff(isFullScreen());
1244 case LFUN_DIALOG_TOGGLE:
1245 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
1246 // fall through to set "enable"
1247 case LFUN_DIALOG_SHOW: {
1248 string const name = cmd.getArg(0);
1250 enable = name == "aboutlyx"
1251 || name == "file" //FIXME: should be removed.
1253 || name == "texinfo";
1254 else if (name == "print")
1255 enable = buf->isExportable("dvi")
1256 && lyxrc.print_command != "none";
1257 else if (name == "character") {
1261 InsetCode ic = view()->cursor().inset().lyxCode();
1262 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1265 else if (name == "symbols") {
1266 if (!view() || view()->cursor().inMathed())
1269 InsetCode ic = view()->cursor().inset().lyxCode();
1270 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1273 else if (name == "latexlog")
1274 enable = FileName(buf->logName()).isReadableFile();
1275 else if (name == "spellchecker")
1276 #if defined (USE_ASPELL)
1277 enable = !buf->isReadonly();
1281 else if (name == "vclog")
1282 enable = buf->lyxvc().inUse();
1286 case LFUN_DIALOG_UPDATE: {
1287 string const name = cmd.getArg(0);
1289 enable = name == "prefs";
1293 case LFUN_INSET_APPLY: {
1294 string const name = cmd.getArg(0);
1295 Inset * inset = getOpenInset(name);
1297 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1299 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1300 // Every inset is supposed to handle this
1301 LASSERT(false, break);
1305 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1306 flag |= lyx::getStatus(fr);
1308 enable = flag.enabled();
1312 case LFUN_COMPLETION_INLINE:
1313 if (!d.current_work_area_
1314 || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1318 case LFUN_COMPLETION_POPUP:
1319 if (!d.current_work_area_
1320 || !d.current_work_area_->completer().popupPossible(view()->cursor()))
1324 case LFUN_COMPLETION_COMPLETE:
1325 if (!d.current_work_area_
1326 || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1330 case LFUN_COMPLETION_ACCEPT:
1331 if (!d.current_work_area_
1332 || (!d.current_work_area_->completer().popupVisible()
1333 && !d.current_work_area_->completer().inlineVisible()
1334 && !d.current_work_area_->completer().completionAvailable()))
1338 case LFUN_COMPLETION_CANCEL:
1339 if (!d.current_work_area_
1340 || (!d.current_work_area_->completer().popupVisible()
1341 && !d.current_work_area_->completer().inlineVisible()))
1345 case LFUN_BUFFER_ZOOM_OUT:
1346 enable = buf && lyxrc.zoom > 10;
1349 case LFUN_BUFFER_ZOOM_IN:
1358 flag.setEnabled(false);
1364 static FileName selectTemplateFile()
1366 FileDialog dlg(qt_("Select template file"));
1367 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1368 dlg.setButton1(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1370 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
1371 QStringList(qt_("LyX Documents (*.lyx)")));
1373 if (result.first == FileDialog::Later)
1375 if (result.second.isEmpty())
1377 return FileName(fromqstr(result.second));
1381 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
1385 Buffer * newBuffer = checkAndLoadLyXFile(filename);
1388 message(_("Document not loaded."));
1393 setBuffer(newBuffer);
1395 // scroll to the position when the file was last closed
1396 if (lyxrc.use_lastfilepos) {
1397 LastFilePosSection::FilePos filepos =
1398 theSession().lastFilePos().load(filename);
1399 view()->moveToPosition(filepos.pit, filepos.pos, 0, 0);
1403 theSession().lastFiles().add(filename);
1410 void GuiView::openDocument(string const & fname)
1412 string initpath = lyxrc.document_path;
1415 string const trypath = buffer()->filePath();
1416 // If directory is writeable, use this as default.
1417 if (FileName(trypath).isDirWritable())
1423 if (fname.empty()) {
1424 FileDialog dlg(qt_("Select document to open"), LFUN_FILE_OPEN);
1425 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1426 dlg.setButton2(qt_("Examples|#E#e"),
1427 toqstr(addPath(package().system_support().absFilename(), "examples")));
1429 QStringList filter(qt_("LyX Documents (*.lyx)"));
1430 filter << qt_("LyX-1.3.x Documents (*.lyx13)")
1431 << qt_("LyX-1.4.x Documents (*.lyx14)")
1432 << qt_("LyX-1.5.x Documents (*.lyx15)")
1433 << qt_("LyX-1.6.x Documents (*.lyx16)");
1434 FileDialog::Result result =
1435 dlg.open(toqstr(initpath), filter);
1437 if (result.first == FileDialog::Later)
1440 filename = fromqstr(result.second);
1442 // check selected filename
1443 if (filename.empty()) {
1444 message(_("Canceled."));
1450 // get absolute path of file and add ".lyx" to the filename if
1452 FileName const fullname =
1453 fileSearch(string(), filename, "lyx", support::may_not_exist);
1454 if (!fullname.empty())
1455 filename = fullname.absFilename();
1457 if (!fullname.onlyPath().isDirectory()) {
1458 Alert::warning(_("Invalid filename"),
1459 bformat(_("The directory in the given path\n%1$s\ndoes not exists."),
1460 from_utf8(fullname.absFilename())));
1463 // if the file doesn't exist, let the user create one
1464 if (!fullname.exists()) {
1465 // the user specifically chose this name. Believe him.
1466 Buffer * const b = newFile(filename, string(), true);
1472 docstring const disp_fn = makeDisplayPath(filename);
1473 message(bformat(_("Opening document %1$s..."), disp_fn));
1476 Buffer * buf = loadDocument(fullname);
1478 buf->updateLabels();
1480 buf->errors("Parse");
1481 str2 = bformat(_("Document %1$s opened."), disp_fn);
1482 if (buf->lyxvc().inUse())
1483 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
1484 " " + _("Version control detected.");
1486 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1491 // FIXME: clean that
1492 static bool import(GuiView * lv, FileName const & filename,
1493 string const & format, ErrorList & errorList)
1495 FileName const lyxfile(support::changeExtension(filename.absFilename(), ".lyx"));
1497 string loader_format;
1498 vector<string> loaders = theConverters().loaders();
1499 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
1500 for (vector<string>::const_iterator it = loaders.begin();
1501 it != loaders.end(); ++it) {
1502 if (!theConverters().isReachable(format, *it))
1505 string const tofile =
1506 support::changeExtension(filename.absFilename(),
1507 formats.extension(*it));
1508 if (!theConverters().convert(0, filename, FileName(tofile),
1509 filename, format, *it, errorList))
1511 loader_format = *it;
1514 if (loader_format.empty()) {
1515 frontend::Alert::error(_("Couldn't import file"),
1516 bformat(_("No information for importing the format %1$s."),
1517 formats.prettyName(format)));
1521 loader_format = format;
1523 if (loader_format == "lyx") {
1524 Buffer * buf = lv->loadDocument(lyxfile);
1527 buf->updateLabels();
1529 buf->errors("Parse");
1531 Buffer * const b = newFile(lyxfile.absFilename(), string(), true);
1535 bool as_paragraphs = loader_format == "textparagraph";
1536 string filename2 = (loader_format == format) ? filename.absFilename()
1537 : support::changeExtension(filename.absFilename(),
1538 formats.extension(loader_format));
1539 lv->view()->insertPlaintextFile(FileName(filename2), as_paragraphs);
1540 theLyXFunc().setLyXView(lv);
1541 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
1548 void GuiView::importDocument(string const & argument)
1551 string filename = split(argument, format, ' ');
1553 LYXERR(Debug::INFO, format << " file: " << filename);
1555 // need user interaction
1556 if (filename.empty()) {
1557 string initpath = lyxrc.document_path;
1559 Buffer const * buf = buffer();
1561 string const trypath = buf->filePath();
1562 // If directory is writeable, use this as default.
1563 if (FileName(trypath).isDirWritable())
1567 docstring const text = bformat(_("Select %1$s file to import"),
1568 formats.prettyName(format));
1570 FileDialog dlg(toqstr(text), LFUN_BUFFER_IMPORT);
1571 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1572 dlg.setButton2(qt_("Examples|#E#e"),
1573 toqstr(addPath(package().system_support().absFilename(), "examples")));
1575 docstring filter = formats.prettyName(format);
1578 filter += from_utf8(formats.extension(format));
1581 FileDialog::Result result =
1582 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
1584 if (result.first == FileDialog::Later)
1587 filename = fromqstr(result.second);
1589 // check selected filename
1590 if (filename.empty())
1591 message(_("Canceled."));
1594 if (filename.empty())
1597 // get absolute path of file
1598 FileName const fullname(support::makeAbsPath(filename));
1600 FileName const lyxfile(support::changeExtension(fullname.absFilename(), ".lyx"));
1602 // Check if the document already is open
1603 Buffer * buf = theBufferList().getBuffer(lyxfile);
1606 if (!closeBuffer()) {
1607 message(_("Canceled."));
1612 docstring const displaypath = makeDisplayPath(lyxfile.absFilename(), 30);
1614 // if the file exists already, and we didn't do
1615 // -i lyx thefile.lyx, warn
1616 if (lyxfile.exists() && fullname != lyxfile) {
1618 docstring text = bformat(_("The document %1$s already exists.\n\n"
1619 "Do you want to overwrite that document?"), displaypath);
1620 int const ret = Alert::prompt(_("Overwrite document?"),
1621 text, 0, 1, _("&Overwrite"), _("&Cancel"));
1624 message(_("Canceled."));
1629 message(bformat(_("Importing %1$s..."), displaypath));
1630 ErrorList errorList;
1631 if (import(this, fullname, format, errorList))
1632 message(_("imported."));
1634 message(_("file not imported!"));
1636 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
1640 void GuiView::newDocument(string const & filename, bool from_template)
1642 FileName initpath(lyxrc.document_path);
1643 Buffer * buf = buffer();
1645 FileName const trypath(buf->filePath());
1646 // If directory is writeable, use this as default.
1647 if (trypath.isDirWritable())
1651 string templatefile;
1652 if (from_template) {
1653 templatefile = selectTemplateFile().absFilename();
1654 if (templatefile.empty())
1659 if (filename.empty())
1660 b = newUnnamedFile(templatefile, initpath);
1662 b = newFile(filename, templatefile, true);
1667 // If no new document could be created, it is unsure
1668 // whether there is a valid BufferView.
1670 // Ensure the cursor is correctly positioned on screen.
1671 view()->showCursor();
1675 void GuiView::insertLyXFile(docstring const & fname)
1677 BufferView * bv = view();
1682 FileName filename(to_utf8(fname));
1684 if (!filename.empty()) {
1685 bv->insertLyXFile(filename);
1689 // Launch a file browser
1691 string initpath = lyxrc.document_path;
1692 string const trypath = bv->buffer().filePath();
1693 // If directory is writeable, use this as default.
1694 if (FileName(trypath).isDirWritable())
1698 FileDialog dlg(qt_("Select LyX document to insert"), LFUN_FILE_INSERT);
1699 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1700 dlg.setButton2(qt_("Examples|#E#e"),
1701 toqstr(addPath(package().system_support().absFilename(),
1704 FileDialog::Result result = dlg.open(toqstr(initpath),
1705 QStringList(qt_("LyX Documents (*.lyx)")));
1707 if (result.first == FileDialog::Later)
1711 filename.set(fromqstr(result.second));
1713 // check selected filename
1714 if (filename.empty()) {
1715 // emit message signal.
1716 message(_("Canceled."));
1720 bv->insertLyXFile(filename);
1724 void GuiView::insertPlaintextFile(docstring const & fname,
1727 BufferView * bv = view();
1732 FileName filename(to_utf8(fname));
1734 if (!filename.empty()) {
1735 bv->insertPlaintextFile(filename, asParagraph);
1739 FileDialog dlg(qt_("Select file to insert"), (asParagraph ?
1740 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1742 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
1743 QStringList(qt_("All Files (*)")));
1745 if (result.first == FileDialog::Later)
1749 filename.set(fromqstr(result.second));
1751 // check selected filename
1752 if (filename.empty()) {
1753 // emit message signal.
1754 message(_("Canceled."));
1758 bv->insertPlaintextFile(filename, asParagraph);
1762 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1764 FileName fname = b.fileName();
1765 FileName const oldname = fname;
1767 if (!newname.empty()) {
1769 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1771 // Switch to this Buffer.
1774 // No argument? Ask user through dialog.
1776 FileDialog dlg(qt_("Choose a filename to save document as"),
1777 LFUN_BUFFER_WRITE_AS);
1778 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1779 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1781 if (!isLyXFilename(fname.absFilename()))
1782 fname.changeExtension(".lyx");
1784 FileDialog::Result result =
1785 dlg.save(toqstr(fname.onlyPath().absFilename()),
1786 QStringList(qt_("LyX Documents (*.lyx)")),
1787 toqstr(fname.onlyFileName()));
1789 if (result.first == FileDialog::Later)
1792 fname.set(fromqstr(result.second));
1797 if (!isLyXFilename(fname.absFilename()))
1798 fname.changeExtension(".lyx");
1801 if (FileName(fname).exists()) {
1802 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1803 docstring text = bformat(_("The document %1$s already "
1804 "exists.\n\nDo you want to "
1805 "overwrite that document?"),
1807 int const ret = Alert::prompt(_("Overwrite document?"),
1808 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1811 case 1: return renameBuffer(b, docstring());
1812 case 2: return false;
1816 // Ok, change the name of the buffer
1817 b.setFileName(fname.absFilename());
1819 bool unnamed = b.isUnnamed();
1820 b.setUnnamed(false);
1821 b.saveCheckSum(fname);
1823 if (!saveBuffer(b)) {
1824 b.setFileName(oldname.absFilename());
1825 b.setUnnamed(unnamed);
1826 b.saveCheckSum(oldname);
1834 bool GuiView::saveBuffer(Buffer & b)
1836 if (workArea(b) && workArea(b)->inDialogMode())
1840 return renameBuffer(b, docstring());
1843 theSession().lastFiles().add(b.fileName());
1847 // Switch to this Buffer.
1850 // FIXME: we don't tell the user *WHY* the save failed !!
1851 docstring const file = makeDisplayPath(b.absFileName(), 30);
1852 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1853 "Do you want to rename the document and "
1854 "try again?"), file);
1855 int const ret = Alert::prompt(_("Rename and save?"),
1856 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1859 if (!renameBuffer(b, docstring()))
1868 return saveBuffer(b);
1872 bool GuiView::closeBuffer()
1874 Buffer * buf = buffer();
1875 return buf && closeBuffer(*buf);
1879 bool GuiView::closeBuffer(Buffer & buf, bool tolastopened)
1881 // goto bookmark to update bookmark pit.
1882 //FIXME: we should update only the bookmarks related to this buffer!
1883 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
1884 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
1885 theLyXFunc().gotoBookmark(i+1, false, false);
1887 if (buf.isClean() || buf.paragraphs().empty()) {
1888 // save in sessions if requested
1889 // do not save childs if their master
1890 // is opened as well
1892 theSession().lastOpened().add(buf.fileName());
1894 // Don't close child documents.
1895 removeWorkArea(currentMainWorkArea());
1897 theBufferList().release(&buf);
1900 // Switch to this Buffer.
1905 if (buf.isUnnamed())
1906 file = from_utf8(buf.fileName().onlyFileName());
1908 file = buf.fileName().displayName(30);
1910 // Bring this window to top before asking questions.
1914 docstring const text = bformat(_("The document %1$s has unsaved changes."
1915 "\n\nDo you want to save the document or discard the changes?"), file);
1916 int const ret = Alert::prompt(_("Save changed document?"),
1917 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1921 if (!saveBuffer(buf))
1925 // if we crash after this we could
1926 // have no autosave file but I guess
1927 // this is really improbable (Jug)
1928 buf.removeAutosaveFile();
1934 // save file names to .lyx/session
1936 theSession().lastOpened().add(buf.fileName());
1939 // Don't close child documents.
1940 removeWorkArea(currentMainWorkArea());
1942 theBufferList().release(&buf);
1948 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np)
1950 Buffer * const curbuf = buffer();
1951 Buffer * nextbuf = curbuf;
1953 if (np == NEXTBUFFER)
1954 nextbuf = theBufferList().next(nextbuf);
1956 nextbuf = theBufferList().previous(nextbuf);
1957 if (nextbuf == curbuf)
1963 if (workArea(*nextbuf))
1970 bool GuiView::dispatch(FuncRequest const & cmd)
1972 BufferView * bv = view();
1973 // By default we won't need any update.
1975 bv->cursor().updateFlags(Update::None);
1976 bool dispatched = true;
1978 if (cmd.origin == FuncRequest::TOC) {
1979 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1980 toc->doDispatch(bv->cursor(), cmd);
1984 switch(cmd.action) {
1985 case LFUN_BUFFER_IMPORT:
1986 importDocument(to_utf8(cmd.argument()));
1989 case LFUN_BUFFER_SWITCH: {
1991 theBufferList().getBuffer(FileName(to_utf8(cmd.argument())));
1995 bv->cursor().message(_("Document not loaded"));
1999 case LFUN_BUFFER_NEXT:
2000 gotoNextOrPreviousBuffer(NEXTBUFFER);
2003 case LFUN_BUFFER_PREVIOUS:
2004 gotoNextOrPreviousBuffer(PREVBUFFER);
2007 case LFUN_COMMAND_EXECUTE: {
2008 bool const show_it = cmd.argument() != "off";
2009 // FIXME: this is a hack, "minibuffer" should not be
2011 if (GuiToolbar * t = toolbar("minibuffer")) {
2012 t->setVisible(show_it);
2013 if (show_it && t->commandBuffer())
2014 t->commandBuffer()->setFocus();
2018 case LFUN_DROP_LAYOUTS_CHOICE:
2020 d.layout_->showPopup();
2023 case LFUN_MENU_OPEN:
2024 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
2025 menu->exec(QCursor::pos());
2028 case LFUN_FILE_INSERT:
2029 insertLyXFile(cmd.argument());
2031 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2032 insertPlaintextFile(cmd.argument(), true);
2035 case LFUN_FILE_INSERT_PLAINTEXT:
2036 insertPlaintextFile(cmd.argument(), false);
2039 case LFUN_BUFFER_WRITE:
2041 saveBuffer(bv->buffer());
2044 case LFUN_BUFFER_WRITE_AS:
2046 renameBuffer(bv->buffer(), cmd.argument());
2049 case LFUN_BUFFER_WRITE_ALL: {
2050 Buffer * first = theBufferList().first();
2053 message(_("Saving all documents..."));
2054 // We cannot use a for loop as the buffer list cycles.
2057 if (!b->isClean()) {
2059 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
2061 b = theBufferList().next(b);
2062 } while (b != first);
2063 message(_("All documents saved."));
2067 case LFUN_TOOLBAR_TOGGLE: {
2068 string const name = cmd.getArg(0);
2069 if (GuiToolbar * t = toolbar(name))
2074 case LFUN_DIALOG_UPDATE: {
2075 string const name = to_utf8(cmd.argument());
2076 // Can only update a dialog connected to an existing inset
2077 Inset * inset = getOpenInset(name);
2079 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
2080 inset->dispatch(view()->cursor(), fr);
2081 } else if (name == "paragraph") {
2082 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
2083 } else if (name == "prefs" || name == "document") {
2084 updateDialog(name, string());
2089 case LFUN_DIALOG_TOGGLE: {
2090 if (isDialogVisible(cmd.getArg(0)))
2091 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
2093 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
2097 case LFUN_DIALOG_DISCONNECT_INSET:
2098 disconnectDialog(to_utf8(cmd.argument()));
2101 case LFUN_DIALOG_HIDE: {
2102 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
2106 case LFUN_DIALOG_SHOW: {
2107 string const name = cmd.getArg(0);
2108 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
2110 if (name == "character") {
2111 data = freefont2string();
2113 showDialog("character", data);
2114 } else if (name == "latexlog") {
2115 Buffer::LogType type;
2116 string const logfile = buffer()->logName(&type);
2118 case Buffer::latexlog:
2121 case Buffer::buildlog:
2125 data += Lexer::quoteString(logfile);
2126 showDialog("log", data);
2127 } else if (name == "vclog") {
2128 string const data = "vc " +
2129 Lexer::quoteString(buffer()->lyxvc().getLogFile());
2130 showDialog("log", data);
2131 } else if (name == "symbols") {
2132 data = bv->cursor().getEncoding()->name();
2134 showDialog("symbols", data);
2136 } else if (name == "prefs" && isFullScreen()) {
2137 FuncRequest fr(LFUN_INSET_INSERT, "fullscreen");
2139 showDialog("prefs", data);
2141 showDialog(name, data);
2145 case LFUN_INSET_APPLY: {
2146 string const name = cmd.getArg(0);
2147 Inset * inset = getOpenInset(name);
2149 // put cursor in front of inset.
2150 if (!view()->setCursorFromInset(inset)) {
2151 LASSERT(false, break);
2154 // useful if we are called from a dialog.
2155 view()->cursor().beginUndoGroup();
2156 view()->cursor().recordUndo();
2157 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
2158 inset->dispatch(view()->cursor(), fr);
2159 view()->cursor().endUndoGroup();
2161 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
2167 case LFUN_UI_TOGGLE:
2169 // Make sure the keyboard focus stays in the work area.
2173 case LFUN_SPLIT_VIEW:
2174 if (Buffer * buf = buffer()) {
2175 string const orientation = cmd.getArg(0);
2176 d.splitter_->setOrientation(orientation == "vertical"
2177 ? Qt::Vertical : Qt::Horizontal);
2178 TabWorkArea * twa = addTabWorkArea();
2179 GuiWorkArea * wa = twa->addWorkArea(*buf, *this);
2180 setCurrentWorkArea(wa);
2184 case LFUN_CLOSE_TAB_GROUP:
2185 if (TabWorkArea * twa = d.currentTabWorkArea()) {
2187 twa = d.currentTabWorkArea();
2188 // Switch to the next GuiWorkArea in the found TabWorkArea.
2190 // Make sure the work area is up to date.
2191 setCurrentWorkArea(twa->currentWorkArea());
2193 setCurrentWorkArea(0);
2198 case LFUN_COMPLETION_INLINE:
2199 if (d.current_work_area_)
2200 d.current_work_area_->completer().showInline();
2203 case LFUN_COMPLETION_POPUP:
2204 if (d.current_work_area_)
2205 d.current_work_area_->completer().showPopup();
2209 case LFUN_COMPLETION_COMPLETE:
2210 if (d.current_work_area_)
2211 d.current_work_area_->completer().tab();
2214 case LFUN_COMPLETION_CANCEL:
2215 if (d.current_work_area_) {
2216 if (d.current_work_area_->completer().popupVisible())
2217 d.current_work_area_->completer().hidePopup();
2219 d.current_work_area_->completer().hideInline();
2223 case LFUN_COMPLETION_ACCEPT:
2224 if (d.current_work_area_)
2225 d.current_work_area_->completer().activate();
2228 case LFUN_BUFFER_ZOOM_IN:
2229 case LFUN_BUFFER_ZOOM_OUT:
2230 if (cmd.argument().empty()) {
2231 if (cmd.action == LFUN_BUFFER_ZOOM_IN)
2236 lyxrc.zoom += convert<int>(cmd.argument());
2238 if (lyxrc.zoom < 10)
2241 // The global QPixmapCache is used in GuiPainter to cache text
2242 // painting so we must reset it.
2243 QPixmapCache::clear();
2244 guiApp->fontLoader().update();
2245 lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
2253 // Part of automatic menu appearance feature.
2254 if (isFullScreen()) {
2255 if (menuBar()->isVisible())
2257 if (statusBar()->isVisible())
2258 statusBar()->hide();
2265 void GuiView::lfunUiToggle(FuncRequest const & cmd)
2267 string const arg = cmd.getArg(0);
2268 if (arg == "scrollbar") {
2269 // hide() is of no help
2270 if (d.current_work_area_->verticalScrollBarPolicy() ==
2271 Qt::ScrollBarAlwaysOff)
2273 d.current_work_area_->setVerticalScrollBarPolicy(
2274 Qt::ScrollBarAsNeeded);
2276 d.current_work_area_->setVerticalScrollBarPolicy(
2277 Qt::ScrollBarAlwaysOff);
2280 if (arg == "statusbar") {
2281 statusBar()->setVisible(!statusBar()->isVisible());
2284 if (arg == "menubar") {
2285 menuBar()->setVisible(!menuBar()->isVisible());
2288 #if QT_VERSION >= 0x040300
2289 if (arg == "frame") {
2291 getContentsMargins(&l, &t, &r, &b);
2292 //are the frames in default state?
2293 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
2295 setContentsMargins(-2, -2, -2, -2);
2297 setContentsMargins(0, 0, 0, 0);
2302 if (arg == "fullscreen") {
2307 message(bformat("LFUN_UI_TOGGLE " + _("%1$s unknown command!"), from_utf8(arg)));
2311 void GuiView::toggleFullScreen()
2313 if (isFullScreen()) {
2314 for (int i = 0; i != d.splitter_->count(); ++i)
2315 d.tabWorkArea(i)->setFullScreen(false);
2316 #if QT_VERSION >= 0x040300
2317 setContentsMargins(0, 0, 0, 0);
2319 setWindowState(windowState() ^ Qt::WindowFullScreen);
2322 statusBar()->show();
2325 hideDialogs("prefs", 0);
2326 for (int i = 0; i != d.splitter_->count(); ++i)
2327 d.tabWorkArea(i)->setFullScreen(true);
2328 #if QT_VERSION >= 0x040300
2329 setContentsMargins(-2, -2, -2, -2);
2332 setWindowState(windowState() ^ Qt::WindowFullScreen);
2333 statusBar()->hide();
2335 if (lyxrc.full_screen_toolbars) {
2336 ToolbarMap::iterator end = d.toolbars_.end();
2337 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
2342 // give dialogs like the TOC a chance to adapt
2347 Buffer const * GuiView::updateInset(Inset const * inset)
2349 if (!d.current_work_area_)
2353 d.current_work_area_->scheduleRedraw();
2355 return &d.current_work_area_->bufferView().buffer();
2359 void GuiView::restartCursor()
2361 /* When we move around, or type, it's nice to be able to see
2362 * the cursor immediately after the keypress.
2364 if (d.current_work_area_)
2365 d.current_work_area_->startBlinkingCursor();
2367 // Take this occasion to update the other GUI elements.
2373 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
2375 if (d.current_work_area_)
2376 d.current_work_area_->completer().updateVisibility(cur, start, keep);
2381 // This list should be kept in sync with the list of insets in
2382 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
2383 // dialog should have the same name as the inset.
2384 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
2385 // docs in LyXAction.cpp.
2387 char const * const dialognames[] = {
2388 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
2389 "citation", "document", "errorlist", "ert", "external", "file", "findreplace",
2390 "float", "graphics", "include", "index", "info", "nomenclature", "label",
2391 "log", "mathdelimiter", "mathmatrix", "mathspace", "note", "paragraph",
2392 "phantom", "prefs", "print", "ref", "sendto", "space", "spellchecker",
2393 "symbols", "tabular", "tabularcreate",
2395 #ifdef HAVE_LIBAIKSAURUS
2399 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings", "findreplaceadv" };
2401 char const * const * const end_dialognames =
2402 dialognames + (sizeof(dialognames) / sizeof(char *));
2406 cmpCStr(char const * name) : name_(name) {}
2407 bool operator()(char const * other) {
2408 return strcmp(other, name_) == 0;
2415 bool isValidName(string const & name)
2417 return find_if(dialognames, end_dialognames,
2418 cmpCStr(name.c_str())) != end_dialognames;
2424 void GuiView::resetDialogs()
2426 // Make sure that no LFUN uses any LyXView.
2427 theLyXFunc().setLyXView(0);
2430 constructToolbars();
2431 guiApp->menus().fillMenuBar(menuBar(), this, false);
2433 d.layout_->updateContents(true);
2434 // Now update controls with current buffer.
2435 theLyXFunc().setLyXView(this);
2441 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
2443 if (!isValidName(name))
2446 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
2448 if (it != d.dialogs_.end()) {
2450 it->second->hideView();
2451 return it->second.get();
2454 Dialog * dialog = build(name);
2455 d.dialogs_[name].reset(dialog);
2456 if (lyxrc.allow_geometry_session)
2457 dialog->restoreSession();
2464 void GuiView::showDialog(string const & name, string const & data,
2472 Dialog * dialog = findOrBuild(name, false);
2474 dialog->showData(data);
2476 d.open_insets_[name] = inset;
2479 catch (ExceptionMessage const & ex) {
2487 bool GuiView::isDialogVisible(string const & name) const
2489 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2490 if (it == d.dialogs_.end())
2492 return it->second.get()->isVisibleView();
2496 void GuiView::hideDialog(string const & name, Inset * inset)
2498 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2499 if (it == d.dialogs_.end())
2502 if (inset && inset != getOpenInset(name))
2505 Dialog * const dialog = it->second.get();
2506 if (dialog->isVisibleView())
2508 d.open_insets_[name] = 0;
2512 void GuiView::disconnectDialog(string const & name)
2514 if (!isValidName(name))
2517 if (d.open_insets_.find(name) != d.open_insets_.end())
2518 d.open_insets_[name] = 0;
2522 Inset * GuiView::getOpenInset(string const & name) const
2524 if (!isValidName(name))
2527 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
2528 return it == d.open_insets_.end() ? 0 : it->second;
2532 void GuiView::hideAll() const
2534 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
2535 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2537 for(; it != end; ++it)
2538 it->second->hideView();
2542 void GuiView::updateDialogs()
2544 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
2545 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2547 for(; it != end; ++it) {
2548 Dialog * dialog = it->second.get();
2549 if (dialog && dialog->isVisibleView())
2550 dialog->checkStatus();
2557 // will be replaced by a proper factory...
2558 Dialog * createGuiAbout(GuiView & lv);
2559 Dialog * createGuiBibitem(GuiView & lv);
2560 Dialog * createGuiBibtex(GuiView & lv);
2561 Dialog * createGuiBox(GuiView & lv);
2562 Dialog * createGuiBranch(GuiView & lv);
2563 Dialog * createGuiChanges(GuiView & lv);
2564 Dialog * createGuiCharacter(GuiView & lv);
2565 Dialog * createGuiCitation(GuiView & lv);
2566 Dialog * createGuiDelimiter(GuiView & lv);
2567 Dialog * createGuiDocument(GuiView & lv);
2568 Dialog * createGuiErrorList(GuiView & lv);
2569 Dialog * createGuiERT(GuiView & lv);
2570 Dialog * createGuiExternal(GuiView & lv);
2571 Dialog * createGuiFloat(GuiView & lv);
2572 Dialog * createGuiGraphics(GuiView & lv);
2573 Dialog * createGuiInclude(GuiView & lv);
2574 Dialog * createGuiInfo(GuiView & lv);
2575 Dialog * createGuiLabel(GuiView & lv);
2576 Dialog * createGuiListings(GuiView & lv);
2577 Dialog * createGuiLog(GuiView & lv);
2578 Dialog * createGuiMathHSpace(GuiView & lv);
2579 Dialog * createGuiMathMatrix(GuiView & lv);
2580 Dialog * createGuiNomenclature(GuiView & lv);
2581 Dialog * createGuiNote(GuiView & lv);
2582 Dialog * createGuiParagraph(GuiView & lv);
2583 Dialog * createGuiPhantom(GuiView & lv);
2584 Dialog * createGuiPreferences(GuiView & lv);
2585 Dialog * createGuiPrint(GuiView & lv);
2586 Dialog * createGuiRef(GuiView & lv);
2587 Dialog * createGuiSearch(GuiView & lv);
2588 Dialog * createGuiSearchAdv(GuiView & lv);
2589 Dialog * createGuiSendTo(GuiView & lv);
2590 Dialog * createGuiShowFile(GuiView & lv);
2591 Dialog * createGuiSpellchecker(GuiView & lv);
2592 Dialog * createGuiSymbols(GuiView & lv);
2593 Dialog * createGuiTabularCreate(GuiView & lv);
2594 Dialog * createGuiTabular(GuiView & lv);
2595 Dialog * createGuiTexInfo(GuiView & lv);
2596 Dialog * createGuiTextHSpace(GuiView & lv);
2597 Dialog * createGuiToc(GuiView & lv);
2598 Dialog * createGuiThesaurus(GuiView & lv);
2599 Dialog * createGuiHyperlink(GuiView & lv);
2600 Dialog * createGuiVSpace(GuiView & lv);
2601 Dialog * createGuiViewSource(GuiView & lv);
2602 Dialog * createGuiWrap(GuiView & lv);
2605 Dialog * GuiView::build(string const & name)
2607 LASSERT(isValidName(name), return 0);
2609 if (name == "aboutlyx")
2610 return createGuiAbout(*this);
2611 if (name == "bibitem")
2612 return createGuiBibitem(*this);
2613 if (name == "bibtex")
2614 return createGuiBibtex(*this);
2616 return createGuiBox(*this);
2617 if (name == "branch")
2618 return createGuiBranch(*this);
2619 if (name == "changes")
2620 return createGuiChanges(*this);
2621 if (name == "character")
2622 return createGuiCharacter(*this);
2623 if (name == "citation")
2624 return createGuiCitation(*this);
2625 if (name == "document")
2626 return createGuiDocument(*this);
2627 if (name == "errorlist")
2628 return createGuiErrorList(*this);
2630 return createGuiERT(*this);
2631 if (name == "external")
2632 return createGuiExternal(*this);
2634 return createGuiShowFile(*this);
2635 if (name == "findreplace")
2636 return createGuiSearch(*this);
2637 if (name == "findreplaceadv")
2638 return createGuiSearchAdv(*this);
2639 if (name == "float")
2640 return createGuiFloat(*this);
2641 if (name == "graphics")
2642 return createGuiGraphics(*this);
2643 if (name == "include")
2644 return createGuiInclude(*this);
2646 return createGuiInfo(*this);
2647 if (name == "nomenclature")
2648 return createGuiNomenclature(*this);
2649 if (name == "label")
2650 return createGuiLabel(*this);
2652 return createGuiLog(*this);
2653 if (name == "mathdelimiter")
2654 return createGuiDelimiter(*this);
2655 if (name == "mathspace")
2656 return createGuiMathHSpace(*this);
2657 if (name == "mathmatrix")
2658 return createGuiMathMatrix(*this);
2660 return createGuiNote(*this);
2661 if (name == "paragraph")
2662 return createGuiParagraph(*this);
2663 if (name == "phantom")
2664 return createGuiPhantom(*this);
2665 if (name == "prefs")
2666 return createGuiPreferences(*this);
2667 if (name == "print")
2668 return createGuiPrint(*this);
2670 return createGuiRef(*this);
2671 if (name == "sendto")
2672 return createGuiSendTo(*this);
2673 if (name == "space")
2674 return createGuiTextHSpace(*this);
2675 if (name == "spellchecker")
2676 return createGuiSpellchecker(*this);
2677 if (name == "symbols")
2678 return createGuiSymbols(*this);
2679 if (name == "tabular")
2680 return createGuiTabular(*this);
2681 if (name == "tabularcreate")
2682 return createGuiTabularCreate(*this);
2683 if (name == "texinfo")
2684 return createGuiTexInfo(*this);
2685 if (name == "view-source")
2686 return createGuiViewSource(*this);
2687 #ifdef HAVE_LIBAIKSAURUS
2688 if (name == "thesaurus")
2689 return createGuiThesaurus(*this);
2692 return createGuiHyperlink(*this);
2693 if (name == "listings")
2694 return createGuiListings(*this);
2696 return createGuiToc(*this);
2697 if (name == "vspace")
2698 return createGuiVSpace(*this);
2700 return createGuiWrap(*this);
2706 } // namespace frontend
2709 #include "moc_GuiView.cpp"