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 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1211 if (toc->getStatus(view()->cursor(), cmd, fs))
1214 flag.setEnabled(false);
1218 switch(cmd.action) {
1219 case LFUN_BUFFER_WRITE:
1220 enable = buf && (buf->isUnnamed() || !buf->isClean());
1223 case LFUN_BUFFER_WRITE_AS:
1227 case LFUN_SPLIT_VIEW:
1228 if (cmd.getArg(0) == "vertical")
1229 enable = buf && (d.splitter_->count() == 1 ||
1230 d.splitter_->orientation() == Qt::Vertical);
1232 enable = buf && (d.splitter_->count() == 1 ||
1233 d.splitter_->orientation() == Qt::Horizontal);
1236 case LFUN_CLOSE_TAB_GROUP:
1237 enable = d.currentTabWorkArea();
1240 case LFUN_TOOLBAR_TOGGLE:
1241 if (GuiToolbar * t = toolbar(cmd.getArg(0)))
1242 flag.setOnOff(t->isVisible());
1245 case LFUN_UI_TOGGLE:
1246 flag.setOnOff(isFullScreen());
1249 case LFUN_DIALOG_TOGGLE:
1250 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
1251 // fall through to set "enable"
1252 case LFUN_DIALOG_SHOW: {
1253 string const name = cmd.getArg(0);
1255 enable = name == "aboutlyx"
1256 || name == "file" //FIXME: should be removed.
1258 || name == "texinfo";
1259 else if (name == "print")
1260 enable = buf->isExportable("dvi")
1261 && lyxrc.print_command != "none";
1262 else if (name == "character") {
1266 InsetCode ic = view()->cursor().inset().lyxCode();
1267 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1270 else if (name == "symbols") {
1271 if (!view() || view()->cursor().inMathed())
1274 InsetCode ic = view()->cursor().inset().lyxCode();
1275 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1278 else if (name == "latexlog")
1279 enable = FileName(buf->logName()).isReadableFile();
1280 else if (name == "spellchecker")
1281 #if defined (USE_ASPELL)
1282 enable = !buf->isReadonly();
1286 else if (name == "vclog")
1287 enable = buf->lyxvc().inUse();
1291 case LFUN_DIALOG_UPDATE: {
1292 string const name = cmd.getArg(0);
1294 enable = name == "prefs";
1298 case LFUN_INSET_APPLY: {
1299 string const name = cmd.getArg(0);
1300 Inset * inset = getOpenInset(name);
1302 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1304 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1305 // Every inset is supposed to handle this
1306 LASSERT(false, break);
1310 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1311 flag |= lyx::getStatus(fr);
1313 enable = flag.enabled();
1317 case LFUN_COMPLETION_INLINE:
1318 if (!d.current_work_area_
1319 || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1323 case LFUN_COMPLETION_POPUP:
1324 if (!d.current_work_area_
1325 || !d.current_work_area_->completer().popupPossible(view()->cursor()))
1329 case LFUN_COMPLETION_COMPLETE:
1330 if (!d.current_work_area_
1331 || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1335 case LFUN_COMPLETION_ACCEPT:
1336 if (!d.current_work_area_
1337 || (!d.current_work_area_->completer().popupVisible()
1338 && !d.current_work_area_->completer().inlineVisible()
1339 && !d.current_work_area_->completer().completionAvailable()))
1343 case LFUN_COMPLETION_CANCEL:
1344 if (!d.current_work_area_
1345 || (!d.current_work_area_->completer().popupVisible()
1346 && !d.current_work_area_->completer().inlineVisible()))
1350 case LFUN_BUFFER_ZOOM_OUT:
1351 enable = buf && lyxrc.zoom > 10;
1354 case LFUN_BUFFER_ZOOM_IN:
1363 flag.setEnabled(false);
1369 static FileName selectTemplateFile()
1371 FileDialog dlg(qt_("Select template file"));
1372 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1373 dlg.setButton1(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1375 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
1376 QStringList(qt_("LyX Documents (*.lyx)")));
1378 if (result.first == FileDialog::Later)
1380 if (result.second.isEmpty())
1382 return FileName(fromqstr(result.second));
1386 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
1390 Buffer * newBuffer = checkAndLoadLyXFile(filename);
1393 message(_("Document not loaded."));
1398 setBuffer(newBuffer);
1400 // scroll to the position when the file was last closed
1401 if (lyxrc.use_lastfilepos) {
1402 LastFilePosSection::FilePos filepos =
1403 theSession().lastFilePos().load(filename);
1404 view()->moveToPosition(filepos.pit, filepos.pos, 0, 0);
1408 theSession().lastFiles().add(filename);
1415 void GuiView::openDocument(string const & fname)
1417 string initpath = lyxrc.document_path;
1420 string const trypath = buffer()->filePath();
1421 // If directory is writeable, use this as default.
1422 if (FileName(trypath).isDirWritable())
1428 if (fname.empty()) {
1429 FileDialog dlg(qt_("Select document to open"), LFUN_FILE_OPEN);
1430 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1431 dlg.setButton2(qt_("Examples|#E#e"),
1432 toqstr(addPath(package().system_support().absFilename(), "examples")));
1434 QStringList filter(qt_("LyX Documents (*.lyx)"));
1435 filter << qt_("LyX-1.3.x Documents (*.lyx13)")
1436 << qt_("LyX-1.4.x Documents (*.lyx14)")
1437 << qt_("LyX-1.5.x Documents (*.lyx15)")
1438 << qt_("LyX-1.6.x Documents (*.lyx16)");
1439 FileDialog::Result result =
1440 dlg.open(toqstr(initpath), filter);
1442 if (result.first == FileDialog::Later)
1445 filename = fromqstr(result.second);
1447 // check selected filename
1448 if (filename.empty()) {
1449 message(_("Canceled."));
1455 // get absolute path of file and add ".lyx" to the filename if
1457 FileName const fullname =
1458 fileSearch(string(), filename, "lyx", support::may_not_exist);
1459 if (!fullname.empty())
1460 filename = fullname.absFilename();
1462 if (!fullname.onlyPath().isDirectory()) {
1463 Alert::warning(_("Invalid filename"),
1464 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
1465 from_utf8(fullname.absFilename())));
1468 // if the file doesn't exist, let the user create one
1469 if (!fullname.exists()) {
1470 // the user specifically chose this name. Believe him.
1471 Buffer * const b = newFile(filename, string(), true);
1477 docstring const disp_fn = makeDisplayPath(filename);
1478 message(bformat(_("Opening document %1$s..."), disp_fn));
1481 Buffer * buf = loadDocument(fullname);
1483 buf->updateLabels();
1485 buf->errors("Parse");
1486 str2 = bformat(_("Document %1$s opened."), disp_fn);
1487 if (buf->lyxvc().inUse())
1488 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
1489 " " + _("Version control detected.");
1491 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1496 // FIXME: clean that
1497 static bool import(GuiView * lv, FileName const & filename,
1498 string const & format, ErrorList & errorList)
1500 FileName const lyxfile(support::changeExtension(filename.absFilename(), ".lyx"));
1502 string loader_format;
1503 vector<string> loaders = theConverters().loaders();
1504 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
1505 for (vector<string>::const_iterator it = loaders.begin();
1506 it != loaders.end(); ++it) {
1507 if (!theConverters().isReachable(format, *it))
1510 string const tofile =
1511 support::changeExtension(filename.absFilename(),
1512 formats.extension(*it));
1513 if (!theConverters().convert(0, filename, FileName(tofile),
1514 filename, format, *it, errorList))
1516 loader_format = *it;
1519 if (loader_format.empty()) {
1520 frontend::Alert::error(_("Couldn't import file"),
1521 bformat(_("No information for importing the format %1$s."),
1522 formats.prettyName(format)));
1526 loader_format = format;
1528 if (loader_format == "lyx") {
1529 Buffer * buf = lv->loadDocument(lyxfile);
1532 buf->updateLabels();
1534 buf->errors("Parse");
1536 Buffer * const b = newFile(lyxfile.absFilename(), string(), true);
1540 bool as_paragraphs = loader_format == "textparagraph";
1541 string filename2 = (loader_format == format) ? filename.absFilename()
1542 : support::changeExtension(filename.absFilename(),
1543 formats.extension(loader_format));
1544 lv->view()->insertPlaintextFile(FileName(filename2), as_paragraphs);
1545 theLyXFunc().setLyXView(lv);
1546 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
1553 void GuiView::importDocument(string const & argument)
1556 string filename = split(argument, format, ' ');
1558 LYXERR(Debug::INFO, format << " file: " << filename);
1560 // need user interaction
1561 if (filename.empty()) {
1562 string initpath = lyxrc.document_path;
1564 Buffer const * buf = buffer();
1566 string const trypath = buf->filePath();
1567 // If directory is writeable, use this as default.
1568 if (FileName(trypath).isDirWritable())
1572 docstring const text = bformat(_("Select %1$s file to import"),
1573 formats.prettyName(format));
1575 FileDialog dlg(toqstr(text), LFUN_BUFFER_IMPORT);
1576 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1577 dlg.setButton2(qt_("Examples|#E#e"),
1578 toqstr(addPath(package().system_support().absFilename(), "examples")));
1580 docstring filter = formats.prettyName(format);
1583 filter += from_utf8(formats.extension(format));
1586 FileDialog::Result result =
1587 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
1589 if (result.first == FileDialog::Later)
1592 filename = fromqstr(result.second);
1594 // check selected filename
1595 if (filename.empty())
1596 message(_("Canceled."));
1599 if (filename.empty())
1602 // get absolute path of file
1603 FileName const fullname(support::makeAbsPath(filename));
1605 FileName const lyxfile(support::changeExtension(fullname.absFilename(), ".lyx"));
1607 // Check if the document already is open
1608 Buffer * buf = theBufferList().getBuffer(lyxfile);
1611 if (!closeBuffer()) {
1612 message(_("Canceled."));
1617 docstring const displaypath = makeDisplayPath(lyxfile.absFilename(), 30);
1619 // if the file exists already, and we didn't do
1620 // -i lyx thefile.lyx, warn
1621 if (lyxfile.exists() && fullname != lyxfile) {
1623 docstring text = bformat(_("The document %1$s already exists.\n\n"
1624 "Do you want to overwrite that document?"), displaypath);
1625 int const ret = Alert::prompt(_("Overwrite document?"),
1626 text, 0, 1, _("&Overwrite"), _("&Cancel"));
1629 message(_("Canceled."));
1634 message(bformat(_("Importing %1$s..."), displaypath));
1635 ErrorList errorList;
1636 if (import(this, fullname, format, errorList))
1637 message(_("imported."));
1639 message(_("file not imported!"));
1641 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
1645 void GuiView::newDocument(string const & filename, bool from_template)
1647 FileName initpath(lyxrc.document_path);
1648 Buffer * buf = buffer();
1650 FileName const trypath(buf->filePath());
1651 // If directory is writeable, use this as default.
1652 if (trypath.isDirWritable())
1656 string templatefile;
1657 if (from_template) {
1658 templatefile = selectTemplateFile().absFilename();
1659 if (templatefile.empty())
1664 if (filename.empty())
1665 b = newUnnamedFile(templatefile, initpath);
1667 b = newFile(filename, templatefile, true);
1672 // If no new document could be created, it is unsure
1673 // whether there is a valid BufferView.
1675 // Ensure the cursor is correctly positioned on screen.
1676 view()->showCursor();
1680 void GuiView::insertLyXFile(docstring const & fname)
1682 BufferView * bv = view();
1687 FileName filename(to_utf8(fname));
1689 if (!filename.empty()) {
1690 bv->insertLyXFile(filename);
1694 // Launch a file browser
1696 string initpath = lyxrc.document_path;
1697 string const trypath = bv->buffer().filePath();
1698 // If directory is writeable, use this as default.
1699 if (FileName(trypath).isDirWritable())
1703 FileDialog dlg(qt_("Select LyX document to insert"), LFUN_FILE_INSERT);
1704 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1705 dlg.setButton2(qt_("Examples|#E#e"),
1706 toqstr(addPath(package().system_support().absFilename(),
1709 FileDialog::Result result = dlg.open(toqstr(initpath),
1710 QStringList(qt_("LyX Documents (*.lyx)")));
1712 if (result.first == FileDialog::Later)
1716 filename.set(fromqstr(result.second));
1718 // check selected filename
1719 if (filename.empty()) {
1720 // emit message signal.
1721 message(_("Canceled."));
1725 bv->insertLyXFile(filename);
1729 void GuiView::insertPlaintextFile(docstring const & fname,
1732 BufferView * bv = view();
1737 FileName filename(to_utf8(fname));
1739 if (!filename.empty()) {
1740 bv->insertPlaintextFile(filename, asParagraph);
1744 FileDialog dlg(qt_("Select file to insert"), (asParagraph ?
1745 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1747 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
1748 QStringList(qt_("All Files (*)")));
1750 if (result.first == FileDialog::Later)
1754 filename.set(fromqstr(result.second));
1756 // check selected filename
1757 if (filename.empty()) {
1758 // emit message signal.
1759 message(_("Canceled."));
1763 bv->insertPlaintextFile(filename, asParagraph);
1767 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1769 FileName fname = b.fileName();
1770 FileName const oldname = fname;
1772 if (!newname.empty()) {
1774 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1776 // Switch to this Buffer.
1779 // No argument? Ask user through dialog.
1781 FileDialog dlg(qt_("Choose a filename to save document as"),
1782 LFUN_BUFFER_WRITE_AS);
1783 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1784 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1786 if (!isLyXFilename(fname.absFilename()))
1787 fname.changeExtension(".lyx");
1789 FileDialog::Result result =
1790 dlg.save(toqstr(fname.onlyPath().absFilename()),
1791 QStringList(qt_("LyX Documents (*.lyx)")),
1792 toqstr(fname.onlyFileName()));
1794 if (result.first == FileDialog::Later)
1797 fname.set(fromqstr(result.second));
1802 if (!isLyXFilename(fname.absFilename()))
1803 fname.changeExtension(".lyx");
1806 if (FileName(fname).exists()) {
1807 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1808 docstring text = bformat(_("The document %1$s already "
1809 "exists.\n\nDo you want to "
1810 "overwrite that document?"),
1812 int const ret = Alert::prompt(_("Overwrite document?"),
1813 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1816 case 1: return renameBuffer(b, docstring());
1817 case 2: return false;
1821 // Ok, change the name of the buffer
1822 b.setFileName(fname.absFilename());
1824 bool unnamed = b.isUnnamed();
1825 b.setUnnamed(false);
1826 b.saveCheckSum(fname);
1828 if (!saveBuffer(b)) {
1829 b.setFileName(oldname.absFilename());
1830 b.setUnnamed(unnamed);
1831 b.saveCheckSum(oldname);
1839 bool GuiView::saveBuffer(Buffer & b)
1841 if (workArea(b) && workArea(b)->inDialogMode())
1845 return renameBuffer(b, docstring());
1848 theSession().lastFiles().add(b.fileName());
1852 // Switch to this Buffer.
1855 // FIXME: we don't tell the user *WHY* the save failed !!
1856 docstring const file = makeDisplayPath(b.absFileName(), 30);
1857 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1858 "Do you want to rename the document and "
1859 "try again?"), file);
1860 int const ret = Alert::prompt(_("Rename and save?"),
1861 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1864 if (!renameBuffer(b, docstring()))
1873 return saveBuffer(b);
1877 bool GuiView::closeBuffer()
1879 Buffer * buf = buffer();
1880 return buf && closeBuffer(*buf);
1884 bool GuiView::closeBuffer(Buffer & buf, bool tolastopened)
1886 // goto bookmark to update bookmark pit.
1887 //FIXME: we should update only the bookmarks related to this buffer!
1888 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
1889 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
1890 theLyXFunc().gotoBookmark(i+1, false, false);
1892 if (buf.isClean() || buf.paragraphs().empty()) {
1893 // save in sessions if requested
1894 // do not save childs if their master
1895 // is opened as well
1897 theSession().lastOpened().add(buf.fileName());
1899 // Don't close child documents.
1900 removeWorkArea(currentMainWorkArea());
1902 theBufferList().release(&buf);
1905 // Switch to this Buffer.
1910 if (buf.isUnnamed())
1911 file = from_utf8(buf.fileName().onlyFileName());
1913 file = buf.fileName().displayName(30);
1915 // Bring this window to top before asking questions.
1919 docstring const text = bformat(_("The document %1$s has unsaved changes."
1920 "\n\nDo you want to save the document or discard the changes?"), file);
1921 int const ret = Alert::prompt(_("Save changed document?"),
1922 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1926 if (!saveBuffer(buf))
1930 // if we crash after this we could
1931 // have no autosave file but I guess
1932 // this is really improbable (Jug)
1933 buf.removeAutosaveFile();
1939 // save file names to .lyx/session
1941 theSession().lastOpened().add(buf.fileName());
1944 // Don't close child documents.
1945 removeWorkArea(currentMainWorkArea());
1947 theBufferList().release(&buf);
1953 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np)
1955 Buffer * const curbuf = buffer();
1956 Buffer * nextbuf = curbuf;
1958 if (np == NEXTBUFFER)
1959 nextbuf = theBufferList().next(nextbuf);
1961 nextbuf = theBufferList().previous(nextbuf);
1962 if (nextbuf == curbuf)
1968 if (workArea(*nextbuf))
1975 bool GuiView::dispatch(FuncRequest const & cmd)
1977 BufferView * bv = view();
1978 // By default we won't need any update.
1980 bv->cursor().updateFlags(Update::None);
1981 bool dispatched = true;
1983 if (cmd.origin == FuncRequest::TOC) {
1984 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1985 toc->doDispatch(bv->cursor(), cmd);
1989 switch(cmd.action) {
1990 case LFUN_BUFFER_IMPORT:
1991 importDocument(to_utf8(cmd.argument()));
1994 case LFUN_BUFFER_SWITCH: {
1996 theBufferList().getBuffer(FileName(to_utf8(cmd.argument())));
2000 bv->cursor().message(_("Document not loaded"));
2004 case LFUN_BUFFER_NEXT:
2005 gotoNextOrPreviousBuffer(NEXTBUFFER);
2008 case LFUN_BUFFER_PREVIOUS:
2009 gotoNextOrPreviousBuffer(PREVBUFFER);
2012 case LFUN_COMMAND_EXECUTE: {
2013 bool const show_it = cmd.argument() != "off";
2014 // FIXME: this is a hack, "minibuffer" should not be
2016 if (GuiToolbar * t = toolbar("minibuffer")) {
2017 t->setVisible(show_it);
2018 if (show_it && t->commandBuffer())
2019 t->commandBuffer()->setFocus();
2023 case LFUN_DROP_LAYOUTS_CHOICE:
2025 d.layout_->showPopup();
2028 case LFUN_MENU_OPEN:
2029 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
2030 menu->exec(QCursor::pos());
2033 case LFUN_FILE_INSERT:
2034 insertLyXFile(cmd.argument());
2036 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2037 insertPlaintextFile(cmd.argument(), true);
2040 case LFUN_FILE_INSERT_PLAINTEXT:
2041 insertPlaintextFile(cmd.argument(), false);
2044 case LFUN_BUFFER_WRITE:
2046 saveBuffer(bv->buffer());
2049 case LFUN_BUFFER_WRITE_AS:
2051 renameBuffer(bv->buffer(), cmd.argument());
2054 case LFUN_BUFFER_WRITE_ALL: {
2055 Buffer * first = theBufferList().first();
2058 message(_("Saving all documents..."));
2059 // We cannot use a for loop as the buffer list cycles.
2062 if (!b->isClean()) {
2064 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
2066 b = theBufferList().next(b);
2067 } while (b != first);
2068 message(_("All documents saved."));
2072 case LFUN_TOOLBAR_TOGGLE: {
2073 string const name = cmd.getArg(0);
2074 if (GuiToolbar * t = toolbar(name))
2079 case LFUN_DIALOG_UPDATE: {
2080 string const name = to_utf8(cmd.argument());
2081 // Can only update a dialog connected to an existing inset
2082 Inset * inset = getOpenInset(name);
2084 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
2085 inset->dispatch(view()->cursor(), fr);
2086 } else if (name == "paragraph") {
2087 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
2088 } else if (name == "prefs" || name == "document") {
2089 updateDialog(name, string());
2094 case LFUN_DIALOG_TOGGLE: {
2095 if (isDialogVisible(cmd.getArg(0)))
2096 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
2098 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
2102 case LFUN_DIALOG_DISCONNECT_INSET:
2103 disconnectDialog(to_utf8(cmd.argument()));
2106 case LFUN_DIALOG_HIDE: {
2107 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
2111 case LFUN_DIALOG_SHOW: {
2112 string const name = cmd.getArg(0);
2113 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
2115 if (name == "character") {
2116 data = freefont2string();
2118 showDialog("character", data);
2119 } else if (name == "latexlog") {
2120 Buffer::LogType type;
2121 string const logfile = buffer()->logName(&type);
2123 case Buffer::latexlog:
2126 case Buffer::buildlog:
2130 data += Lexer::quoteString(logfile);
2131 showDialog("log", data);
2132 } else if (name == "vclog") {
2133 string const data = "vc " +
2134 Lexer::quoteString(buffer()->lyxvc().getLogFile());
2135 showDialog("log", data);
2136 } else if (name == "symbols") {
2137 data = bv->cursor().getEncoding()->name();
2139 showDialog("symbols", data);
2141 } else if (name == "prefs" && isFullScreen()) {
2142 FuncRequest fr(LFUN_INSET_INSERT, "fullscreen");
2144 showDialog("prefs", data);
2146 showDialog(name, data);
2150 case LFUN_INSET_APPLY: {
2151 string const name = cmd.getArg(0);
2152 Inset * inset = getOpenInset(name);
2154 // put cursor in front of inset.
2155 if (!view()->setCursorFromInset(inset)) {
2156 LASSERT(false, break);
2159 // useful if we are called from a dialog.
2160 view()->cursor().beginUndoGroup();
2161 view()->cursor().recordUndo();
2162 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
2163 inset->dispatch(view()->cursor(), fr);
2164 view()->cursor().endUndoGroup();
2166 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
2172 case LFUN_UI_TOGGLE:
2174 // Make sure the keyboard focus stays in the work area.
2178 case LFUN_SPLIT_VIEW:
2179 if (Buffer * buf = buffer()) {
2180 string const orientation = cmd.getArg(0);
2181 d.splitter_->setOrientation(orientation == "vertical"
2182 ? Qt::Vertical : Qt::Horizontal);
2183 TabWorkArea * twa = addTabWorkArea();
2184 GuiWorkArea * wa = twa->addWorkArea(*buf, *this);
2185 setCurrentWorkArea(wa);
2189 case LFUN_CLOSE_TAB_GROUP:
2190 if (TabWorkArea * twa = d.currentTabWorkArea()) {
2192 twa = d.currentTabWorkArea();
2193 // Switch to the next GuiWorkArea in the found TabWorkArea.
2195 // Make sure the work area is up to date.
2196 setCurrentWorkArea(twa->currentWorkArea());
2198 setCurrentWorkArea(0);
2203 case LFUN_COMPLETION_INLINE:
2204 if (d.current_work_area_)
2205 d.current_work_area_->completer().showInline();
2208 case LFUN_COMPLETION_POPUP:
2209 if (d.current_work_area_)
2210 d.current_work_area_->completer().showPopup();
2214 case LFUN_COMPLETION_COMPLETE:
2215 if (d.current_work_area_)
2216 d.current_work_area_->completer().tab();
2219 case LFUN_COMPLETION_CANCEL:
2220 if (d.current_work_area_) {
2221 if (d.current_work_area_->completer().popupVisible())
2222 d.current_work_area_->completer().hidePopup();
2224 d.current_work_area_->completer().hideInline();
2228 case LFUN_COMPLETION_ACCEPT:
2229 if (d.current_work_area_)
2230 d.current_work_area_->completer().activate();
2233 case LFUN_BUFFER_ZOOM_IN:
2234 case LFUN_BUFFER_ZOOM_OUT:
2235 if (cmd.argument().empty()) {
2236 if (cmd.action == LFUN_BUFFER_ZOOM_IN)
2241 lyxrc.zoom += convert<int>(cmd.argument());
2243 if (lyxrc.zoom < 10)
2246 // The global QPixmapCache is used in GuiPainter to cache text
2247 // painting so we must reset it.
2248 QPixmapCache::clear();
2249 guiApp->fontLoader().update();
2250 lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
2258 // Part of automatic menu appearance feature.
2259 if (isFullScreen()) {
2260 if (menuBar()->isVisible())
2262 if (statusBar()->isVisible())
2263 statusBar()->hide();
2270 void GuiView::lfunUiToggle(FuncRequest const & cmd)
2272 string const arg = cmd.getArg(0);
2273 if (arg == "scrollbar") {
2274 // hide() is of no help
2275 if (d.current_work_area_->verticalScrollBarPolicy() ==
2276 Qt::ScrollBarAlwaysOff)
2278 d.current_work_area_->setVerticalScrollBarPolicy(
2279 Qt::ScrollBarAsNeeded);
2281 d.current_work_area_->setVerticalScrollBarPolicy(
2282 Qt::ScrollBarAlwaysOff);
2285 if (arg == "statusbar") {
2286 statusBar()->setVisible(!statusBar()->isVisible());
2289 if (arg == "menubar") {
2290 menuBar()->setVisible(!menuBar()->isVisible());
2293 #if QT_VERSION >= 0x040300
2294 if (arg == "frame") {
2296 getContentsMargins(&l, &t, &r, &b);
2297 //are the frames in default state?
2298 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
2300 setContentsMargins(-2, -2, -2, -2);
2302 setContentsMargins(0, 0, 0, 0);
2307 if (arg == "fullscreen") {
2312 message(bformat("LFUN_UI_TOGGLE " + _("%1$s unknown command!"), from_utf8(arg)));
2316 void GuiView::toggleFullScreen()
2318 if (isFullScreen()) {
2319 for (int i = 0; i != d.splitter_->count(); ++i)
2320 d.tabWorkArea(i)->setFullScreen(false);
2321 #if QT_VERSION >= 0x040300
2322 setContentsMargins(0, 0, 0, 0);
2324 setWindowState(windowState() ^ Qt::WindowFullScreen);
2327 statusBar()->show();
2330 hideDialogs("prefs", 0);
2331 for (int i = 0; i != d.splitter_->count(); ++i)
2332 d.tabWorkArea(i)->setFullScreen(true);
2333 #if QT_VERSION >= 0x040300
2334 setContentsMargins(-2, -2, -2, -2);
2337 setWindowState(windowState() ^ Qt::WindowFullScreen);
2338 statusBar()->hide();
2340 if (lyxrc.full_screen_toolbars) {
2341 ToolbarMap::iterator end = d.toolbars_.end();
2342 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
2347 // give dialogs like the TOC a chance to adapt
2352 Buffer const * GuiView::updateInset(Inset const * inset)
2354 if (!d.current_work_area_)
2358 d.current_work_area_->scheduleRedraw();
2360 return &d.current_work_area_->bufferView().buffer();
2364 void GuiView::restartCursor()
2366 /* When we move around, or type, it's nice to be able to see
2367 * the cursor immediately after the keypress.
2369 if (d.current_work_area_)
2370 d.current_work_area_->startBlinkingCursor();
2372 // Take this occasion to update the other GUI elements.
2378 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
2380 if (d.current_work_area_)
2381 d.current_work_area_->completer().updateVisibility(cur, start, keep);
2386 // This list should be kept in sync with the list of insets in
2387 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
2388 // dialog should have the same name as the inset.
2389 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
2390 // docs in LyXAction.cpp.
2392 char const * const dialognames[] = {
2393 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
2394 "citation", "document", "errorlist", "ert", "external", "file", "findreplace",
2395 "float", "graphics", "include", "index", "index_print", "info", "nomenclature",
2396 "label", "log", "mathdelimiter", "mathmatrix", "mathspace", "note", "paragraph",
2397 "phantom", "prefs", "print", "ref", "sendto", "space", "spellchecker",
2398 "symbols", "tabular", "tabularcreate",
2400 #ifdef HAVE_LIBAIKSAURUS
2404 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings", "findreplaceadv" };
2406 char const * const * const end_dialognames =
2407 dialognames + (sizeof(dialognames) / sizeof(char *));
2411 cmpCStr(char const * name) : name_(name) {}
2412 bool operator()(char const * other) {
2413 return strcmp(other, name_) == 0;
2420 bool isValidName(string const & name)
2422 return find_if(dialognames, end_dialognames,
2423 cmpCStr(name.c_str())) != end_dialognames;
2429 void GuiView::resetDialogs()
2431 // Make sure that no LFUN uses any LyXView.
2432 theLyXFunc().setLyXView(0);
2435 constructToolbars();
2436 guiApp->menus().fillMenuBar(menuBar(), this, false);
2438 d.layout_->updateContents(true);
2439 // Now update controls with current buffer.
2440 theLyXFunc().setLyXView(this);
2446 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
2448 if (!isValidName(name))
2451 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
2453 if (it != d.dialogs_.end()) {
2455 it->second->hideView();
2456 return it->second.get();
2459 Dialog * dialog = build(name);
2460 d.dialogs_[name].reset(dialog);
2461 if (lyxrc.allow_geometry_session)
2462 dialog->restoreSession();
2469 void GuiView::showDialog(string const & name, string const & data,
2477 Dialog * dialog = findOrBuild(name, false);
2479 dialog->showData(data);
2481 d.open_insets_[name] = inset;
2484 catch (ExceptionMessage const & ex) {
2492 bool GuiView::isDialogVisible(string const & name) const
2494 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2495 if (it == d.dialogs_.end())
2497 return it->second.get()->isVisibleView();
2501 void GuiView::hideDialog(string const & name, Inset * inset)
2503 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2504 if (it == d.dialogs_.end())
2507 if (inset && inset != getOpenInset(name))
2510 Dialog * const dialog = it->second.get();
2511 if (dialog->isVisibleView())
2513 d.open_insets_[name] = 0;
2517 void GuiView::disconnectDialog(string const & name)
2519 if (!isValidName(name))
2522 if (d.open_insets_.find(name) != d.open_insets_.end())
2523 d.open_insets_[name] = 0;
2527 Inset * GuiView::getOpenInset(string const & name) const
2529 if (!isValidName(name))
2532 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
2533 return it == d.open_insets_.end() ? 0 : it->second;
2537 void GuiView::hideAll() const
2539 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
2540 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2542 for(; it != end; ++it)
2543 it->second->hideView();
2547 void GuiView::updateDialogs()
2549 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
2550 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2552 for(; it != end; ++it) {
2553 Dialog * dialog = it->second.get();
2554 if (dialog && dialog->isVisibleView())
2555 dialog->checkStatus();
2562 // will be replaced by a proper factory...
2563 Dialog * createGuiAbout(GuiView & lv);
2564 Dialog * createGuiBibitem(GuiView & lv);
2565 Dialog * createGuiBibtex(GuiView & lv);
2566 Dialog * createGuiBox(GuiView & lv);
2567 Dialog * createGuiBranch(GuiView & lv);
2568 Dialog * createGuiChanges(GuiView & lv);
2569 Dialog * createGuiCharacter(GuiView & lv);
2570 Dialog * createGuiCitation(GuiView & lv);
2571 Dialog * createGuiDelimiter(GuiView & lv);
2572 Dialog * createGuiDocument(GuiView & lv);
2573 Dialog * createGuiErrorList(GuiView & lv);
2574 Dialog * createGuiERT(GuiView & lv);
2575 Dialog * createGuiExternal(GuiView & lv);
2576 Dialog * createGuiFloat(GuiView & lv);
2577 Dialog * createGuiGraphics(GuiView & lv);
2578 Dialog * createGuiInclude(GuiView & lv);
2579 Dialog * createGuiIndex(GuiView & lv);
2580 Dialog * createGuiInfo(GuiView & lv);
2581 Dialog * createGuiLabel(GuiView & lv);
2582 Dialog * createGuiListings(GuiView & lv);
2583 Dialog * createGuiLog(GuiView & lv);
2584 Dialog * createGuiMathHSpace(GuiView & lv);
2585 Dialog * createGuiMathMatrix(GuiView & lv);
2586 Dialog * createGuiNomenclature(GuiView & lv);
2587 Dialog * createGuiNote(GuiView & lv);
2588 Dialog * createGuiParagraph(GuiView & lv);
2589 Dialog * createGuiPhantom(GuiView & lv);
2590 Dialog * createGuiPreferences(GuiView & lv);
2591 Dialog * createGuiPrint(GuiView & lv);
2592 Dialog * createGuiPrintindex(GuiView & lv);
2593 Dialog * createGuiRef(GuiView & lv);
2594 Dialog * createGuiSearch(GuiView & lv);
2595 Dialog * createGuiSearchAdv(GuiView & lv);
2596 Dialog * createGuiSendTo(GuiView & lv);
2597 Dialog * createGuiShowFile(GuiView & lv);
2598 Dialog * createGuiSpellchecker(GuiView & lv);
2599 Dialog * createGuiSymbols(GuiView & lv);
2600 Dialog * createGuiTabularCreate(GuiView & lv);
2601 Dialog * createGuiTabular(GuiView & lv);
2602 Dialog * createGuiTexInfo(GuiView & lv);
2603 Dialog * createGuiTextHSpace(GuiView & lv);
2604 Dialog * createGuiToc(GuiView & lv);
2605 Dialog * createGuiThesaurus(GuiView & lv);
2606 Dialog * createGuiHyperlink(GuiView & lv);
2607 Dialog * createGuiVSpace(GuiView & lv);
2608 Dialog * createGuiViewSource(GuiView & lv);
2609 Dialog * createGuiWrap(GuiView & lv);
2612 Dialog * GuiView::build(string const & name)
2614 LASSERT(isValidName(name), return 0);
2616 if (name == "aboutlyx")
2617 return createGuiAbout(*this);
2618 if (name == "bibitem")
2619 return createGuiBibitem(*this);
2620 if (name == "bibtex")
2621 return createGuiBibtex(*this);
2623 return createGuiBox(*this);
2624 if (name == "branch")
2625 return createGuiBranch(*this);
2626 if (name == "changes")
2627 return createGuiChanges(*this);
2628 if (name == "character")
2629 return createGuiCharacter(*this);
2630 if (name == "citation")
2631 return createGuiCitation(*this);
2632 if (name == "document")
2633 return createGuiDocument(*this);
2634 if (name == "errorlist")
2635 return createGuiErrorList(*this);
2637 return createGuiERT(*this);
2638 if (name == "external")
2639 return createGuiExternal(*this);
2641 return createGuiShowFile(*this);
2642 if (name == "findreplace")
2643 return createGuiSearch(*this);
2644 if (name == "findreplaceadv")
2645 return createGuiSearchAdv(*this);
2646 if (name == "float")
2647 return createGuiFloat(*this);
2648 if (name == "graphics")
2649 return createGuiGraphics(*this);
2650 if (name == "include")
2651 return createGuiInclude(*this);
2652 if (name == "index")
2653 return createGuiIndex(*this);
2655 return createGuiInfo(*this);
2656 if (name == "nomenclature")
2657 return createGuiNomenclature(*this);
2658 if (name == "label")
2659 return createGuiLabel(*this);
2661 return createGuiLog(*this);
2662 if (name == "mathdelimiter")
2663 return createGuiDelimiter(*this);
2664 if (name == "mathspace")
2665 return createGuiMathHSpace(*this);
2666 if (name == "mathmatrix")
2667 return createGuiMathMatrix(*this);
2669 return createGuiNote(*this);
2670 if (name == "paragraph")
2671 return createGuiParagraph(*this);
2672 if (name == "phantom")
2673 return createGuiPhantom(*this);
2674 if (name == "prefs")
2675 return createGuiPreferences(*this);
2676 if (name == "print")
2677 return createGuiPrint(*this);
2679 return createGuiRef(*this);
2680 if (name == "sendto")
2681 return createGuiSendTo(*this);
2682 if (name == "space")
2683 return createGuiTextHSpace(*this);
2684 if (name == "spellchecker")
2685 return createGuiSpellchecker(*this);
2686 if (name == "symbols")
2687 return createGuiSymbols(*this);
2688 if (name == "tabular")
2689 return createGuiTabular(*this);
2690 if (name == "tabularcreate")
2691 return createGuiTabularCreate(*this);
2692 if (name == "texinfo")
2693 return createGuiTexInfo(*this);
2694 if (name == "view-source")
2695 return createGuiViewSource(*this);
2696 #ifdef HAVE_LIBAIKSAURUS
2697 if (name == "thesaurus")
2698 return createGuiThesaurus(*this);
2701 return createGuiHyperlink(*this);
2702 if (name == "index_print")
2703 return createGuiPrintindex(*this);
2704 if (name == "listings")
2705 return createGuiListings(*this);
2707 return createGuiToc(*this);
2708 if (name == "vspace")
2709 return createGuiVSpace(*this);
2711 return createGuiWrap(*this);
2717 } // namespace frontend
2720 #include "moc_GuiView.cpp"