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 //code below is skipped when when ~/.config/LyX is (re)created
382 setIconSize(settings.value(icon_key).toSize());
384 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
385 QSize size = settings.value("size", QSize(690, 510)).toSize();
389 if (!restoreGeometry(settings.value("geometry").toByteArray()))
390 setGeometry(50, 50, 690, 510);
392 // Make sure layout is correctly oriented.
393 setLayoutDirection(qApp->layoutDirection());
395 // Allow the toc and view-source dock widget to be restored if needed.
397 if ((dialog = findOrBuild("toc", true)))
398 // see bug 5082. At least setup title and enabled state.
399 // Visibility will be adjusted by restoreState below.
400 dialog->prepareView();
401 if ((dialog = findOrBuild("view-source", true)))
402 dialog->prepareView();
404 if (!restoreState(settings.value("layout").toByteArray(), 0))
411 GuiToolbar * GuiView::toolbar(string const & name)
413 ToolbarMap::iterator it = d.toolbars_.find(name);
414 if (it != d.toolbars_.end())
417 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
418 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
423 void GuiView::constructToolbars()
425 ToolbarMap::iterator it = d.toolbars_.begin();
426 for (; it != d.toolbars_.end(); ++it)
431 // extracts the toolbars from the backend
432 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
433 Toolbars::Infos::iterator end = guiApp->toolbars().end();
434 for (; cit != end; ++cit)
435 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
439 void GuiView::initToolbars()
441 // extracts the toolbars from the backend
442 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
443 Toolbars::Infos::iterator end = guiApp->toolbars().end();
444 for (; cit != end; ++cit) {
445 GuiToolbar * tb = toolbar(cit->name);
448 int const visibility = guiApp->toolbars().defaultVisibility(cit->name);
450 tb->setVisible(false);
451 tb->setVisibility(visibility);
453 if (visibility & Toolbars::TOP) {
455 addToolBarBreak(Qt::TopToolBarArea);
456 addToolBar(Qt::TopToolBarArea, tb);
459 if (visibility & Toolbars::BOTTOM) {
460 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
461 #if (QT_VERSION >= 0x040202)
462 addToolBarBreak(Qt::BottomToolBarArea);
464 addToolBar(Qt::BottomToolBarArea, tb);
467 if (visibility & Toolbars::LEFT) {
468 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
469 #if (QT_VERSION >= 0x040202)
470 addToolBarBreak(Qt::LeftToolBarArea);
472 addToolBar(Qt::LeftToolBarArea, tb);
475 if (visibility & Toolbars::RIGHT) {
476 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
477 #if (QT_VERSION >= 0x040202)
478 addToolBarBreak(Qt::RightToolBarArea);
480 addToolBar(Qt::RightToolBarArea, tb);
483 if (visibility & Toolbars::ON)
484 tb->setVisible(true);
489 TocModels & GuiView::tocModels()
491 return d.toc_models_;
495 void GuiView::setFocus()
497 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
498 // Make sure LyXFunc points to the correct view.
499 guiApp->setCurrentView(this);
500 theLyXFunc().setLyXView(this);
501 QMainWindow::setFocus();
502 if (d.current_work_area_)
503 d.current_work_area_->setFocus();
507 QMenu * GuiView::createPopupMenu()
509 return d.toolBarPopup(this);
513 void GuiView::showEvent(QShowEvent * e)
515 LYXERR(Debug::GUI, "Passed Geometry "
516 << size().height() << "x" << size().width()
517 << "+" << pos().x() << "+" << pos().y());
519 if (d.splitter_->count() == 0)
520 // No work area, switch to the background widget.
523 QMainWindow::showEvent(e);
527 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
528 ** is responsibility of the container (e.g., dialog)
530 void GuiView::closeEvent(QCloseEvent * close_event)
532 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
535 // it can happen that this event arrives without selecting the view,
536 // e.g. when clicking the close button on a background window.
538 GuiWorkArea * active_wa = currentMainWorkArea();
539 setCurrentWorkArea(active_wa);
541 // We have to call count() each time, because it can happen that
542 // more than one splitter will disappear in one iteration (bug 5998).
543 for (; d.splitter_->count(); ) {
544 TabWorkArea * twa = d.tabWorkArea(0);
546 int twa_count = twa->count();
547 for (; twa_count; --twa_count) {
548 twa->setCurrentIndex(twa_count-1);
550 GuiWorkArea * wa = twa->currentWorkArea();
551 bool const is_active_wa = active_wa == wa;
552 Buffer * b = &wa->bufferView().buffer();
554 // This is a child document, just close the tab
555 // after saving but keep the file loaded.
556 if (!closeBuffer(*b, true, is_active_wa)) {
558 close_event->ignore();
564 vector<Buffer *> clist = b->getChildren();
565 for (vector<Buffer *>::const_iterator it = clist.begin();
566 it != clist.end(); ++it) {
567 if ((*it)->isClean())
570 // If a child is dirty, do not close
571 // without user intervention
572 if (!closeBuffer(*c, false)) {
574 close_event->ignore();
579 QList<int> const ids = guiApp->viewIds();
580 for (int i = 0; i != ids.size(); ++i) {
583 if (guiApp->view(ids[i]).workArea(*b)) {
584 // FIXME 1: should we put an alert box here
585 // that the buffer is viewed elsewhere?
586 // FIXME 2: should we try to save this buffer in any case?
589 // This buffer is also opened in another view, so
590 // close the associated work area...
592 // ... but don't close the buffer.
597 // closeBuffer() needs buffer workArea still alive and
598 // set as currrent one, and destroys it
599 if (b && !closeBuffer(*b, true, is_active_wa)) {
601 close_event->ignore();
606 // Make sure that nothing will use this close to be closed View.
607 guiApp->unregisterView(this);
609 if (isFullScreen()) {
610 // Switch off fullscreen before closing.
615 // Make sure the timer time out will not trigger a statusbar update.
616 d.statusbar_timer_.stop();
618 // Saving fullscreen requires additional tweaks in the toolbar code.
619 // It wouldn't also work under linux natively.
620 if (lyxrc.allow_geometry_session) {
621 // Save this window geometry and layout.
623 // Then the toolbar private states.
624 ToolbarMap::iterator end = d.toolbars_.end();
625 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
626 it->second->saveSession();
627 // Now take care of all other dialogs:
628 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
629 for (; it!= d.dialogs_.end(); ++it)
630 it->second->saveSession();
633 close_event->accept();
637 void GuiView::dragEnterEvent(QDragEnterEvent * event)
639 if (event->mimeData()->hasUrls())
641 /// \todo Ask lyx-devel is this is enough:
642 /// if (event->mimeData()->hasFormat("text/plain"))
643 /// event->acceptProposedAction();
647 void GuiView::dropEvent(QDropEvent * event)
649 QList<QUrl> files = event->mimeData()->urls();
653 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
654 for (int i = 0; i != files.size(); ++i) {
655 string const file = os::internal_path(fromqstr(
656 files.at(i).toLocalFile()));
658 // Asynchronously post the event. DropEvent usually come
659 // from the BufferView. But reloading a file might close
660 // the BufferView from within its own event handler.
661 guiApp->dispatchDelayed(FuncRequest(LFUN_FILE_OPEN, file));
668 void GuiView::message(docstring const & str)
670 if (ForkedProcess::iAmAChild())
673 statusBar()->showMessage(toqstr(str));
674 d.statusbar_timer_.stop();
675 d.statusbar_timer_.start(3000);
679 void GuiView::smallSizedIcons()
681 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
685 void GuiView::normalSizedIcons()
687 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
691 void GuiView::bigSizedIcons()
693 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
697 void GuiView::clearMessage()
701 theLyXFunc().setLyXView(this);
702 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
703 d.statusbar_timer_.stop();
707 void GuiView::updateWindowTitle(GuiWorkArea * wa)
709 if (wa != d.current_work_area_)
711 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
712 setWindowIconText(wa->windowIconText());
716 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
719 disconnectBufferView();
720 connectBufferView(wa->bufferView());
721 connectBuffer(wa->bufferView().buffer());
722 d.current_work_area_ = wa;
723 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
724 this, SLOT(updateWindowTitle(GuiWorkArea *)));
725 updateWindowTitle(wa);
729 // The document settings needs to be reinitialised.
730 updateDialog("document", "");
732 // Buffer-dependent dialogs must be updated. This is done here because
733 // some dialogs require buffer()->text.
738 void GuiView::on_lastWorkAreaRemoved()
741 // We already are in a close event. Nothing more to do.
744 if (d.splitter_->count() > 1)
745 // We have a splitter so don't close anything.
748 // Reset and updates the dialogs.
749 d.toc_models_.reset(0);
750 updateDialog("document", "");
753 resetWindowTitleAndIconText();
755 if (lyxrc.open_buffers_in_tabs)
756 // Nothing more to do, the window should stay open.
759 if (guiApp->viewIds().size() > 1) {
765 // On Mac we also close the last window because the application stay
766 // resident in memory. On other platforms we don't close the last
767 // window because this would quit the application.
773 void GuiView::updateStatusBar()
775 // let the user see the explicit message
776 if (d.statusbar_timer_.isActive())
779 theLyXFunc().setLyXView(this);
780 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
784 bool GuiView::hasFocus() const
786 return qApp->activeWindow() == this;
790 bool GuiView::event(QEvent * e)
794 // Useful debug code:
795 //case QEvent::ActivationChange:
796 //case QEvent::WindowDeactivate:
797 //case QEvent::Paint:
798 //case QEvent::Enter:
799 //case QEvent::Leave:
800 //case QEvent::HoverEnter:
801 //case QEvent::HoverLeave:
802 //case QEvent::HoverMove:
803 //case QEvent::StatusTip:
804 //case QEvent::DragEnter:
805 //case QEvent::DragLeave:
809 case QEvent::WindowActivate: {
810 if (this == guiApp->currentView()) {
812 return QMainWindow::event(e);
814 guiApp->setCurrentView(this);
815 theLyXFunc().setLyXView(this);
816 if (d.current_work_area_) {
817 BufferView & bv = d.current_work_area_->bufferView();
818 connectBufferView(bv);
819 connectBuffer(bv.buffer());
820 // The document structure, name and dialogs might have
821 // changed in another view.
823 // The document settings needs to be reinitialised.
824 updateDialog("document", "");
827 resetWindowTitleAndIconText();
830 return QMainWindow::event(e);
833 case QEvent::ShortcutOverride: {
836 #if (!defined Q_WS_X11) || (QT_VERSION >= 0x040500)
837 if (isFullScreen() && menuBar()->isHidden()) {
838 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
839 // FIXME: we should also try to detect special LyX shortcut such as
840 // Alt-P and Alt-M. Right now there is a hack in
841 // GuiWorkArea::processKeySym() that hides again the menubar for
843 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
845 return QMainWindow::event(e);
850 if (d.current_work_area_)
851 // Nothing special to do.
852 return QMainWindow::event(e);
854 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
855 // Let Qt handle menu access and the Tab keys to navigate keys to navigate
857 if (ke->modifiers() & Qt::AltModifier || ke->key() == Qt::Key_Tab
858 || ke->key() == Qt::Key_Backtab)
859 return QMainWindow::event(e);
861 // Allow processing of shortcuts that are allowed even when no Buffer
863 theLyXFunc().setLyXView(this);
865 setKeySymbol(&sym, ke);
866 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
872 return QMainWindow::event(e);
876 void GuiView::resetWindowTitleAndIconText()
878 setWindowTitle(qt_("LyX"));
879 setWindowIconText(qt_("LyX"));
882 bool GuiView::focusNextPrevChild(bool /*next*/)
889 void GuiView::setBusy(bool busy)
891 if (d.current_work_area_) {
892 d.current_work_area_->setUpdatesEnabled(!busy);
894 d.current_work_area_->stopBlinkingCursor();
896 d.current_work_area_->startBlinkingCursor();
900 QApplication::setOverrideCursor(Qt::WaitCursor);
902 QApplication::restoreOverrideCursor();
906 GuiWorkArea * GuiView::workArea(Buffer & buffer)
908 if (currentWorkArea()
909 && ¤tWorkArea()->bufferView().buffer() == &buffer)
910 return (GuiWorkArea *) currentWorkArea();
911 if (TabWorkArea * twa = d.currentTabWorkArea())
912 return twa->workArea(buffer);
917 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
919 // Automatically create a TabWorkArea if there are none yet.
920 TabWorkArea * tab_widget = d.splitter_->count()
921 ? d.currentTabWorkArea() : addTabWorkArea();
922 return tab_widget->addWorkArea(buffer, *this);
926 TabWorkArea * GuiView::addTabWorkArea()
928 TabWorkArea * twa = new TabWorkArea;
929 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
930 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
931 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
932 this, SLOT(on_lastWorkAreaRemoved()));
934 d.splitter_->addWidget(twa);
935 d.stack_widget_->setCurrentWidget(d.splitter_);
940 GuiWorkArea const * GuiView::currentWorkArea() const
942 return d.current_work_area_;
946 GuiWorkArea * GuiView::currentWorkArea()
948 return d.current_work_area_;
952 GuiWorkArea const * GuiView::currentMainWorkArea() const
954 if (d.currentTabWorkArea() == NULL)
956 return d.currentTabWorkArea()->currentWorkArea();
960 GuiWorkArea * GuiView::currentMainWorkArea()
962 if (d.currentTabWorkArea() == NULL)
964 return d.currentTabWorkArea()->currentWorkArea();
968 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
970 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
972 d.current_work_area_ = NULL;
976 GuiWorkArea * old_gwa = theGuiApp()->currentView()->currentWorkArea();
980 theGuiApp()->setCurrentView(this);
981 d.current_work_area_ = wa;
982 for (int i = 0; i != d.splitter_->count(); ++i) {
983 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
984 //if (d.current_main_work_area_)
985 // d.current_main_work_area_->setFrameStyle(QFrame::NoFrame);
986 d.current_main_work_area_ = wa;
987 //d.current_main_work_area_->setFrameStyle(QFrame::Box | QFrame::Plain);
988 //d.current_main_work_area_->setLineWidth(2);
989 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
993 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
994 on_currentWorkAreaChanged(wa);
995 BufferView & bv = wa->bufferView();
996 bv.cursor().fixIfBroken();
998 wa->setUpdatesEnabled(true);
999 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1003 void GuiView::removeWorkArea(GuiWorkArea * wa)
1005 LASSERT(wa, return);
1006 if (wa == d.current_work_area_) {
1008 disconnectBufferView();
1009 d.current_work_area_ = 0;
1010 d.current_main_work_area_ = 0;
1013 bool found_twa = false;
1014 for (int i = 0; i != d.splitter_->count(); ++i) {
1015 TabWorkArea * twa = d.tabWorkArea(i);
1016 if (twa->removeWorkArea(wa)) {
1017 // Found in this tab group, and deleted the GuiWorkArea.
1019 if (twa->count() != 0) {
1020 if (d.current_work_area_ == 0)
1021 // This means that we are closing the current GuiWorkArea, so
1022 // switch to the next GuiWorkArea in the found TabWorkArea.
1023 setCurrentWorkArea(twa->currentWorkArea());
1025 // No more WorkAreas in this tab group, so delete it.
1032 // It is not a tabbed work area (i.e., the search work area), so it
1033 // should be deleted by other means.
1034 LASSERT(found_twa, /* */);
1036 if (d.current_work_area_ == 0) {
1037 if (d.splitter_->count() != 0) {
1038 TabWorkArea * twa = d.currentTabWorkArea();
1039 setCurrentWorkArea(twa->currentWorkArea());
1041 // No more work areas, switch to the background widget.
1042 setCurrentWorkArea(0);
1048 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
1054 void GuiView::updateLayoutList()
1057 d.layout_->updateContents(false);
1061 void GuiView::updateToolbars()
1063 ToolbarMap::iterator end = d.toolbars_.end();
1064 if (d.current_work_area_) {
1066 d.current_work_area_->bufferView().cursor().inMathed();
1068 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
1070 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
1071 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
1072 bool const mathmacrotemplate =
1073 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
1075 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1076 it->second->update(math, table, review, mathmacrotemplate);
1078 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1079 it->second->update(false, false, false, false);
1083 Buffer * GuiView::buffer()
1085 if (d.current_work_area_)
1086 return &d.current_work_area_->bufferView().buffer();
1091 Buffer const * GuiView::buffer() const
1093 if (d.current_work_area_)
1094 return &d.current_work_area_->bufferView().buffer();
1099 void GuiView::setBuffer(Buffer * newBuffer)
1101 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << std::endl);
1102 LASSERT(newBuffer, return);
1105 GuiWorkArea * wa = workArea(*newBuffer);
1107 newBuffer->masterBuffer()->updateLabels();
1108 wa = addWorkArea(*newBuffer);
1110 //Disconnect the old buffer...there's no new one.
1113 connectBuffer(*newBuffer);
1114 connectBufferView(wa->bufferView());
1115 setCurrentWorkArea(wa);
1121 void GuiView::connectBuffer(Buffer & buf)
1123 buf.setGuiDelegate(this);
1127 void GuiView::disconnectBuffer()
1129 if (d.current_work_area_)
1130 d.current_work_area_->bufferView().setGuiDelegate(0);
1134 void GuiView::connectBufferView(BufferView & bv)
1136 bv.setGuiDelegate(this);
1140 void GuiView::disconnectBufferView()
1142 if (d.current_work_area_)
1143 d.current_work_area_->bufferView().setGuiDelegate(0);
1147 void GuiView::errors(string const & error_type, bool from_master)
1149 ErrorList & el = from_master ?
1150 buffer()->masterBuffer()->errorList(error_type)
1151 : buffer()->errorList(error_type);
1152 string data = error_type;
1154 data = "from_master|" + error_type;
1156 showDialog("errorlist", data);
1160 void GuiView::updateTocItem(std::string const & type, DocIterator const & dit)
1162 d.toc_models_.updateItem(toqstr(type), dit);
1166 void GuiView::structureChanged()
1168 d.toc_models_.reset(view());
1169 // Navigator needs more than a simple update in this case. It needs to be
1171 updateDialog("toc", "");
1175 void GuiView::updateDialog(string const & name, string const & data)
1177 if (!isDialogVisible(name))
1180 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1181 if (it == d.dialogs_.end())
1184 Dialog * const dialog = it->second.get();
1185 if (dialog->isVisibleView())
1186 dialog->initialiseParams(data);
1190 BufferView * GuiView::view()
1192 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1196 void GuiView::autoSave()
1198 LYXERR(Debug::INFO, "Running autoSave()");
1201 view()->buffer().autoSave();
1205 void GuiView::resetAutosaveTimers()
1208 d.autosave_timeout_.restart();
1212 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1215 Buffer * buf = buffer();
1217 /* In LyX/Mac, when a dialog is open, the menus of the
1218 application can still be accessed without giving focus to
1219 the main window. In this case, we want to disable the menu
1220 entries that are buffer-related.
1222 Note that this code is not perfect, as bug 1941 attests:
1223 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
1225 if (cmd.origin == FuncRequest::MENU && !hasFocus())
1228 if (cmd.origin == FuncRequest::TOC) {
1229 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1231 if (toc->getStatus(view()->cursor(), cmd, fs))
1234 flag.setEnabled(false);
1238 switch(cmd.action) {
1239 case LFUN_BUFFER_WRITE:
1240 enable = buf && (buf->isUnnamed() || !buf->isClean());
1243 case LFUN_BUFFER_WRITE_AS:
1247 case LFUN_SPLIT_VIEW:
1248 if (cmd.getArg(0) == "vertical")
1249 enable = buf && (d.splitter_->count() == 1 ||
1250 d.splitter_->orientation() == Qt::Vertical);
1252 enable = buf && (d.splitter_->count() == 1 ||
1253 d.splitter_->orientation() == Qt::Horizontal);
1256 case LFUN_CLOSE_TAB_GROUP:
1257 enable = d.currentTabWorkArea();
1260 case LFUN_TOOLBAR_TOGGLE:
1261 if (GuiToolbar * t = toolbar(cmd.getArg(0)))
1262 flag.setOnOff(t->isVisible());
1265 case LFUN_UI_TOGGLE:
1266 flag.setOnOff(isFullScreen());
1269 case LFUN_DIALOG_TOGGLE:
1270 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
1271 // fall through to set "enable"
1272 case LFUN_DIALOG_SHOW: {
1273 string const name = cmd.getArg(0);
1275 enable = name == "aboutlyx"
1276 || name == "file" //FIXME: should be removed.
1278 || name == "texinfo";
1279 else if (name == "print")
1280 enable = buf->isExportable("dvi")
1281 && lyxrc.print_command != "none";
1282 else if (name == "character") {
1286 InsetCode ic = view()->cursor().inset().lyxCode();
1287 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1290 else if (name == "symbols") {
1291 if (!view() || view()->cursor().inMathed())
1294 InsetCode ic = view()->cursor().inset().lyxCode();
1295 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
1298 else if (name == "latexlog")
1299 enable = FileName(buf->logName()).isReadableFile();
1300 else if (name == "spellchecker")
1301 #if defined (USE_ASPELL)
1302 enable = !buf->isReadonly();
1306 else if (name == "vclog")
1307 enable = buf->lyxvc().inUse();
1311 case LFUN_DIALOG_UPDATE: {
1312 string const name = cmd.getArg(0);
1314 enable = name == "prefs";
1318 case LFUN_INSET_APPLY: {
1319 string const name = cmd.getArg(0);
1320 Inset * inset = getOpenInset(name);
1322 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1324 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1325 // Every inset is supposed to handle this
1326 LASSERT(false, break);
1330 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1331 flag |= lyx::getStatus(fr);
1333 enable = flag.enabled();
1337 case LFUN_COMPLETION_INLINE:
1338 if (!d.current_work_area_
1339 || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1343 case LFUN_COMPLETION_POPUP:
1344 if (!d.current_work_area_
1345 || !d.current_work_area_->completer().popupPossible(view()->cursor()))
1349 case LFUN_COMPLETION_COMPLETE:
1350 if (!d.current_work_area_
1351 || !d.current_work_area_->completer().inlinePossible(view()->cursor()))
1355 case LFUN_COMPLETION_ACCEPT:
1356 if (!d.current_work_area_
1357 || (!d.current_work_area_->completer().popupVisible()
1358 && !d.current_work_area_->completer().inlineVisible()
1359 && !d.current_work_area_->completer().completionAvailable()))
1363 case LFUN_COMPLETION_CANCEL:
1364 if (!d.current_work_area_
1365 || (!d.current_work_area_->completer().popupVisible()
1366 && !d.current_work_area_->completer().inlineVisible()))
1370 case LFUN_BUFFER_ZOOM_OUT:
1371 enable = buf && lyxrc.zoom > 10;
1374 case LFUN_BUFFER_ZOOM_IN:
1383 flag.setEnabled(false);
1389 static FileName selectTemplateFile()
1391 FileDialog dlg(qt_("Select template file"));
1392 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1393 dlg.setButton1(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1395 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
1396 QStringList(qt_("LyX Documents (*.lyx)")));
1398 if (result.first == FileDialog::Later)
1400 if (result.second.isEmpty())
1402 return FileName(fromqstr(result.second));
1406 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
1410 Buffer * newBuffer = checkAndLoadLyXFile(filename);
1413 message(_("Document not loaded."));
1418 setBuffer(newBuffer);
1420 // scroll to the position when the file was last closed
1421 if (lyxrc.use_lastfilepos) {
1422 LastFilePosSection::FilePos filepos =
1423 theSession().lastFilePos().load(filename);
1424 view()->moveToPosition(filepos.pit, filepos.pos, 0, 0);
1428 theSession().lastFiles().add(filename);
1435 void GuiView::openDocument(string const & fname)
1437 string initpath = lyxrc.document_path;
1440 string const trypath = buffer()->filePath();
1441 // If directory is writeable, use this as default.
1442 if (FileName(trypath).isDirWritable())
1448 if (fname.empty()) {
1449 FileDialog dlg(qt_("Select document to open"), LFUN_FILE_OPEN);
1450 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1451 dlg.setButton2(qt_("Examples|#E#e"),
1452 toqstr(addPath(package().system_support().absFilename(), "examples")));
1454 QStringList filter(qt_("LyX Documents (*.lyx)"));
1455 filter << qt_("LyX-1.3.x Documents (*.lyx13)")
1456 << qt_("LyX-1.4.x Documents (*.lyx14)")
1457 << qt_("LyX-1.5.x Documents (*.lyx15)")
1458 << qt_("LyX-1.6.x Documents (*.lyx16)");
1459 FileDialog::Result result =
1460 dlg.open(toqstr(initpath), filter);
1462 if (result.first == FileDialog::Later)
1465 filename = fromqstr(result.second);
1467 // check selected filename
1468 if (filename.empty()) {
1469 message(_("Canceled."));
1475 // get absolute path of file and add ".lyx" to the filename if
1477 FileName const fullname =
1478 fileSearch(string(), filename, "lyx", support::may_not_exist);
1479 if (!fullname.empty())
1480 filename = fullname.absFilename();
1482 if (!fullname.onlyPath().isDirectory()) {
1483 Alert::warning(_("Invalid filename"),
1484 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
1485 from_utf8(fullname.absFilename())));
1488 // if the file doesn't exist, let the user create one
1489 if (!fullname.exists()) {
1490 // the user specifically chose this name. Believe him.
1491 Buffer * const b = newFile(filename, string(), true);
1497 docstring const disp_fn = makeDisplayPath(filename);
1498 message(bformat(_("Opening document %1$s..."), disp_fn));
1501 Buffer * buf = loadDocument(fullname);
1503 buf->updateLabels();
1505 buf->errors("Parse");
1506 str2 = bformat(_("Document %1$s opened."), disp_fn);
1507 if (buf->lyxvc().inUse())
1508 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
1509 " " + _("Version control detected.");
1511 str2 = bformat(_("Could not open document %1$s"), disp_fn);
1516 // FIXME: clean that
1517 static bool import(GuiView * lv, FileName const & filename,
1518 string const & format, ErrorList & errorList)
1520 FileName const lyxfile(support::changeExtension(filename.absFilename(), ".lyx"));
1522 string loader_format;
1523 vector<string> loaders = theConverters().loaders();
1524 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
1525 for (vector<string>::const_iterator it = loaders.begin();
1526 it != loaders.end(); ++it) {
1527 if (!theConverters().isReachable(format, *it))
1530 string const tofile =
1531 support::changeExtension(filename.absFilename(),
1532 formats.extension(*it));
1533 if (!theConverters().convert(0, filename, FileName(tofile),
1534 filename, format, *it, errorList))
1536 loader_format = *it;
1539 if (loader_format.empty()) {
1540 frontend::Alert::error(_("Couldn't import file"),
1541 bformat(_("No information for importing the format %1$s."),
1542 formats.prettyName(format)));
1546 loader_format = format;
1548 if (loader_format == "lyx") {
1549 Buffer * buf = lv->loadDocument(lyxfile);
1552 buf->updateLabels();
1554 buf->errors("Parse");
1556 Buffer * const b = newFile(lyxfile.absFilename(), string(), true);
1560 bool as_paragraphs = loader_format == "textparagraph";
1561 string filename2 = (loader_format == format) ? filename.absFilename()
1562 : support::changeExtension(filename.absFilename(),
1563 formats.extension(loader_format));
1564 lv->view()->insertPlaintextFile(FileName(filename2), as_paragraphs);
1565 theLyXFunc().setLyXView(lv);
1566 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
1573 void GuiView::importDocument(string const & argument)
1576 string filename = split(argument, format, ' ');
1578 LYXERR(Debug::INFO, format << " file: " << filename);
1580 // need user interaction
1581 if (filename.empty()) {
1582 string initpath = lyxrc.document_path;
1584 Buffer const * buf = buffer();
1586 string const trypath = buf->filePath();
1587 // If directory is writeable, use this as default.
1588 if (FileName(trypath).isDirWritable())
1592 docstring const text = bformat(_("Select %1$s file to import"),
1593 formats.prettyName(format));
1595 FileDialog dlg(toqstr(text), LFUN_BUFFER_IMPORT);
1596 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1597 dlg.setButton2(qt_("Examples|#E#e"),
1598 toqstr(addPath(package().system_support().absFilename(), "examples")));
1600 docstring filter = formats.prettyName(format);
1603 filter += from_utf8(formats.extension(format));
1606 FileDialog::Result result =
1607 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
1609 if (result.first == FileDialog::Later)
1612 filename = fromqstr(result.second);
1614 // check selected filename
1615 if (filename.empty())
1616 message(_("Canceled."));
1619 if (filename.empty())
1622 // get absolute path of file
1623 FileName const fullname(support::makeAbsPath(filename));
1625 FileName const lyxfile(support::changeExtension(fullname.absFilename(), ".lyx"));
1627 // Check if the document already is open
1628 Buffer * buf = theBufferList().getBuffer(lyxfile);
1631 if (!closeBuffer()) {
1632 message(_("Canceled."));
1637 docstring const displaypath = makeDisplayPath(lyxfile.absFilename(), 30);
1639 // if the file exists already, and we didn't do
1640 // -i lyx thefile.lyx, warn
1641 if (lyxfile.exists() && fullname != lyxfile) {
1643 docstring text = bformat(_("The document %1$s already exists.\n\n"
1644 "Do you want to overwrite that document?"), displaypath);
1645 int const ret = Alert::prompt(_("Overwrite document?"),
1646 text, 0, 1, _("&Overwrite"), _("&Cancel"));
1649 message(_("Canceled."));
1654 message(bformat(_("Importing %1$s..."), displaypath));
1655 ErrorList errorList;
1656 if (import(this, fullname, format, errorList))
1657 message(_("imported."));
1659 message(_("file not imported!"));
1661 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
1665 void GuiView::newDocument(string const & filename, bool from_template)
1667 FileName initpath(lyxrc.document_path);
1668 Buffer * buf = buffer();
1670 FileName const trypath(buf->filePath());
1671 // If directory is writeable, use this as default.
1672 if (trypath.isDirWritable())
1676 string templatefile;
1677 if (from_template) {
1678 templatefile = selectTemplateFile().absFilename();
1679 if (templatefile.empty())
1684 if (filename.empty())
1685 b = newUnnamedFile(templatefile, initpath);
1687 b = newFile(filename, templatefile, true);
1692 // If no new document could be created, it is unsure
1693 // whether there is a valid BufferView.
1695 // Ensure the cursor is correctly positioned on screen.
1696 view()->showCursor();
1700 void GuiView::insertLyXFile(docstring const & fname)
1702 BufferView * bv = view();
1707 FileName filename(to_utf8(fname));
1709 if (!filename.empty()) {
1710 bv->insertLyXFile(filename);
1714 // Launch a file browser
1716 string initpath = lyxrc.document_path;
1717 string const trypath = bv->buffer().filePath();
1718 // If directory is writeable, use this as default.
1719 if (FileName(trypath).isDirWritable())
1723 FileDialog dlg(qt_("Select LyX document to insert"), LFUN_FILE_INSERT);
1724 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1725 dlg.setButton2(qt_("Examples|#E#e"),
1726 toqstr(addPath(package().system_support().absFilename(),
1729 FileDialog::Result result = dlg.open(toqstr(initpath),
1730 QStringList(qt_("LyX Documents (*.lyx)")));
1732 if (result.first == FileDialog::Later)
1736 filename.set(fromqstr(result.second));
1738 // check selected filename
1739 if (filename.empty()) {
1740 // emit message signal.
1741 message(_("Canceled."));
1745 bv->insertLyXFile(filename);
1749 void GuiView::insertPlaintextFile(docstring const & fname,
1752 BufferView * bv = view();
1756 if (!fname.empty() && !FileName::isAbsolute(to_utf8(fname))) {
1757 message(_("Absolute filename expected."));
1762 FileName filename(to_utf8(fname));
1764 if (!filename.empty()) {
1765 bv->insertPlaintextFile(filename, asParagraph);
1769 FileDialog dlg(qt_("Select file to insert"), (asParagraph ?
1770 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1772 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
1773 QStringList(qt_("All Files (*)")));
1775 if (result.first == FileDialog::Later)
1779 filename.set(fromqstr(result.second));
1781 // check selected filename
1782 if (filename.empty()) {
1783 // emit message signal.
1784 message(_("Canceled."));
1788 bv->insertPlaintextFile(filename, asParagraph);
1792 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1794 FileName fname = b.fileName();
1795 FileName const oldname = fname;
1797 if (!newname.empty()) {
1799 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1801 // Switch to this Buffer.
1804 // No argument? Ask user through dialog.
1806 FileDialog dlg(qt_("Choose a filename to save document as"),
1807 LFUN_BUFFER_WRITE_AS);
1808 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
1809 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
1811 if (!isLyXFilename(fname.absFilename()))
1812 fname.changeExtension(".lyx");
1814 FileDialog::Result result =
1815 dlg.save(toqstr(fname.onlyPath().absFilename()),
1816 QStringList(qt_("LyX Documents (*.lyx)")),
1817 toqstr(fname.onlyFileName()));
1819 if (result.first == FileDialog::Later)
1822 fname.set(fromqstr(result.second));
1827 if (!isLyXFilename(fname.absFilename()))
1828 fname.changeExtension(".lyx");
1831 if (FileName(fname).exists()) {
1832 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1833 docstring text = bformat(_("The document %1$s already "
1834 "exists.\n\nDo you want to "
1835 "overwrite that document?"),
1837 int const ret = Alert::prompt(_("Overwrite document?"),
1838 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1841 case 1: return renameBuffer(b, docstring());
1842 case 2: return false;
1846 FileName oldauto = b.getAutosaveFilename();
1848 // Ok, change the name of the buffer
1849 b.setFileName(fname.absFilename());
1851 bool unnamed = b.isUnnamed();
1852 b.setUnnamed(false);
1853 b.saveCheckSum(fname);
1855 // bring the autosave file with us, just in case.
1856 b.moveAutosaveFile(oldauto);
1858 if (!saveBuffer(b)) {
1859 oldauto = b.getAutosaveFilename();
1860 b.setFileName(oldname.absFilename());
1861 b.setUnnamed(unnamed);
1862 b.saveCheckSum(oldname);
1863 b.moveAutosaveFile(oldauto);
1871 bool GuiView::saveBuffer(Buffer & b)
1873 if (workArea(b) && workArea(b)->inDialogMode())
1877 return renameBuffer(b, docstring());
1880 theSession().lastFiles().add(b.fileName());
1884 // Switch to this Buffer.
1887 // FIXME: we don't tell the user *WHY* the save failed !!
1888 docstring const file = makeDisplayPath(b.absFileName(), 30);
1889 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1890 "Do you want to rename the document and "
1891 "try again?"), file);
1892 int const ret = Alert::prompt(_("Rename and save?"),
1893 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1896 if (!renameBuffer(b, docstring()))
1905 return saveBuffer(b);
1909 bool GuiView::closeBuffer()
1911 Buffer * buf = buffer();
1912 return buf && closeBuffer(*buf);
1916 bool GuiView::closeBuffer(Buffer & buf, bool tolastopened, bool mark_active)
1918 // goto bookmark to update bookmark pit.
1919 //FIXME: we should update only the bookmarks related to this buffer!
1920 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
1921 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
1922 theLyXFunc().gotoBookmark(i+1, false, false);
1924 if (buf.isClean() || buf.paragraphs().empty()) {
1925 // save in sessions if requested
1926 // do not save childs if their master
1927 // is opened as well
1929 theSession().lastOpened().add(buf.fileName(), mark_active);
1931 // Don't close child documents.
1932 removeWorkArea(currentMainWorkArea());
1934 theBufferList().release(&buf);
1937 // Switch to this Buffer.
1942 if (buf.isUnnamed())
1943 file = from_utf8(buf.fileName().onlyFileName());
1945 file = buf.fileName().displayName(30);
1947 // Bring this window to top before asking questions.
1951 docstring const text = bformat(_("The document %1$s has unsaved changes."
1952 "\n\nDo you want to save the document or discard the changes?"), file);
1953 int const ret = Alert::prompt(_("Save changed document?"),
1954 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1958 if (!saveBuffer(buf))
1962 // if we crash after this we could
1963 // have no autosave file but I guess
1964 // this is really improbable (Jug)
1965 buf.removeAutosaveFile();
1971 // save file names to .lyx/session
1973 theSession().lastOpened().add(buf.fileName(), mark_active);
1976 // Don't close child documents.
1977 removeWorkArea(currentMainWorkArea());
1979 theBufferList().release(&buf);
1985 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np)
1987 Buffer * const curbuf = buffer();
1988 Buffer * nextbuf = curbuf;
1990 if (np == NEXTBUFFER)
1991 nextbuf = theBufferList().next(nextbuf);
1993 nextbuf = theBufferList().previous(nextbuf);
1994 if (nextbuf == curbuf)
2000 if (workArea(*nextbuf))
2007 bool GuiView::dispatch(FuncRequest const & cmd)
2009 BufferView * bv = view();
2010 // By default we won't need any update.
2012 bv->cursor().updateFlags(Update::None);
2013 bool dispatched = true;
2015 if (cmd.origin == FuncRequest::TOC) {
2016 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2017 toc->doDispatch(bv->cursor(), cmd);
2021 switch(cmd.action) {
2022 case LFUN_BUFFER_IMPORT:
2023 importDocument(to_utf8(cmd.argument()));
2026 case LFUN_BUFFER_SWITCH:
2027 if (FileName::isAbsolute(to_utf8(cmd.argument()))) {
2029 theBufferList().getBuffer(FileName(to_utf8(cmd.argument())));
2033 bv->cursor().message(_("Document not loaded"));
2037 case LFUN_BUFFER_NEXT:
2038 gotoNextOrPreviousBuffer(NEXTBUFFER);
2041 case LFUN_BUFFER_PREVIOUS:
2042 gotoNextOrPreviousBuffer(PREVBUFFER);
2045 case LFUN_COMMAND_EXECUTE: {
2046 bool const show_it = cmd.argument() != "off";
2047 // FIXME: this is a hack, "minibuffer" should not be
2049 if (GuiToolbar * t = toolbar("minibuffer")) {
2050 t->setVisible(show_it);
2051 if (show_it && t->commandBuffer())
2052 t->commandBuffer()->setFocus();
2056 case LFUN_DROP_LAYOUTS_CHOICE:
2058 d.layout_->showPopup();
2061 case LFUN_MENU_OPEN:
2062 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
2063 menu->exec(QCursor::pos());
2066 case LFUN_FILE_INSERT:
2067 insertLyXFile(cmd.argument());
2069 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2070 insertPlaintextFile(cmd.argument(), true);
2073 case LFUN_FILE_INSERT_PLAINTEXT:
2074 insertPlaintextFile(cmd.argument(), false);
2077 case LFUN_BUFFER_WRITE:
2079 saveBuffer(bv->buffer());
2082 case LFUN_BUFFER_WRITE_AS:
2084 renameBuffer(bv->buffer(), cmd.argument());
2087 case LFUN_BUFFER_WRITE_ALL: {
2088 Buffer * first = theBufferList().first();
2091 message(_("Saving all documents..."));
2092 // We cannot use a for loop as the buffer list cycles.
2095 if (!b->isClean()) {
2097 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
2099 b = theBufferList().next(b);
2100 } while (b != first);
2101 message(_("All documents saved."));
2105 case LFUN_TOOLBAR_TOGGLE: {
2106 string const name = cmd.getArg(0);
2107 if (GuiToolbar * t = toolbar(name))
2112 case LFUN_DIALOG_UPDATE: {
2113 string const name = to_utf8(cmd.argument());
2114 // Can only update a dialog connected to an existing inset
2115 Inset * inset = getOpenInset(name);
2117 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
2118 inset->dispatch(view()->cursor(), fr);
2119 } else if (name == "paragraph") {
2120 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
2121 } else if (name == "prefs" || name == "document") {
2122 updateDialog(name, string());
2127 case LFUN_DIALOG_TOGGLE: {
2128 if (isDialogVisible(cmd.getArg(0)))
2129 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
2131 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
2135 case LFUN_DIALOG_DISCONNECT_INSET:
2136 disconnectDialog(to_utf8(cmd.argument()));
2139 case LFUN_DIALOG_HIDE: {
2140 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
2144 case LFUN_DIALOG_SHOW: {
2145 string const name = cmd.getArg(0);
2146 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
2148 if (name == "character") {
2149 data = freefont2string();
2151 showDialog("character", data);
2152 } else if (name == "latexlog") {
2153 Buffer::LogType type;
2154 string const logfile = buffer()->logName(&type);
2156 case Buffer::latexlog:
2159 case Buffer::buildlog:
2163 data += Lexer::quoteString(logfile);
2164 showDialog("log", data);
2165 } else if (name == "vclog") {
2166 string const data = "vc " +
2167 Lexer::quoteString(buffer()->lyxvc().getLogFile());
2168 showDialog("log", data);
2169 } else if (name == "symbols") {
2170 data = bv->cursor().getEncoding()->name();
2172 showDialog("symbols", data);
2174 } else if (name == "prefs" && isFullScreen()) {
2175 FuncRequest fr(LFUN_INSET_INSERT, "fullscreen");
2177 showDialog("prefs", data);
2179 showDialog(name, data);
2183 case LFUN_INSET_APPLY: {
2184 string const name = cmd.getArg(0);
2185 Inset * inset = getOpenInset(name);
2187 // put cursor in front of inset.
2188 if (!view()->setCursorFromInset(inset)) {
2189 LASSERT(false, break);
2192 // useful if we are called from a dialog.
2193 view()->cursor().beginUndoGroup();
2194 view()->cursor().recordUndo();
2195 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
2196 inset->dispatch(view()->cursor(), fr);
2197 view()->cursor().endUndoGroup();
2199 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
2205 case LFUN_UI_TOGGLE:
2207 // Make sure the keyboard focus stays in the work area.
2211 case LFUN_SPLIT_VIEW:
2212 if (Buffer * buf = buffer()) {
2213 string const orientation = cmd.getArg(0);
2214 d.splitter_->setOrientation(orientation == "vertical"
2215 ? Qt::Vertical : Qt::Horizontal);
2216 TabWorkArea * twa = addTabWorkArea();
2217 GuiWorkArea * wa = twa->addWorkArea(*buf, *this);
2218 setCurrentWorkArea(wa);
2222 case LFUN_CLOSE_TAB_GROUP:
2223 if (TabWorkArea * twa = d.currentTabWorkArea()) {
2225 twa = d.currentTabWorkArea();
2226 // Switch to the next GuiWorkArea in the found TabWorkArea.
2228 // Make sure the work area is up to date.
2229 setCurrentWorkArea(twa->currentWorkArea());
2231 setCurrentWorkArea(0);
2236 case LFUN_COMPLETION_INLINE:
2237 if (d.current_work_area_)
2238 d.current_work_area_->completer().showInline();
2241 case LFUN_COMPLETION_POPUP:
2242 if (d.current_work_area_)
2243 d.current_work_area_->completer().showPopup();
2247 case LFUN_COMPLETION_COMPLETE:
2248 if (d.current_work_area_)
2249 d.current_work_area_->completer().tab();
2252 case LFUN_COMPLETION_CANCEL:
2253 if (d.current_work_area_) {
2254 if (d.current_work_area_->completer().popupVisible())
2255 d.current_work_area_->completer().hidePopup();
2257 d.current_work_area_->completer().hideInline();
2261 case LFUN_COMPLETION_ACCEPT:
2262 if (d.current_work_area_)
2263 d.current_work_area_->completer().activate();
2266 case LFUN_BUFFER_ZOOM_IN:
2267 case LFUN_BUFFER_ZOOM_OUT:
2268 if (cmd.argument().empty()) {
2269 if (cmd.action == LFUN_BUFFER_ZOOM_IN)
2274 lyxrc.zoom += convert<int>(cmd.argument());
2276 if (lyxrc.zoom < 10)
2279 // The global QPixmapCache is used in GuiPainter to cache text
2280 // painting so we must reset it.
2281 QPixmapCache::clear();
2282 guiApp->fontLoader().update();
2283 lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
2291 // Part of automatic menu appearance feature.
2292 if (isFullScreen()) {
2293 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
2295 if (statusBar()->isVisible())
2296 statusBar()->hide();
2303 void GuiView::lfunUiToggle(FuncRequest const & cmd)
2305 string const arg = cmd.getArg(0);
2306 if (arg == "scrollbar") {
2307 // hide() is of no help
2308 if (d.current_work_area_->verticalScrollBarPolicy() ==
2309 Qt::ScrollBarAlwaysOff)
2311 d.current_work_area_->setVerticalScrollBarPolicy(
2312 Qt::ScrollBarAsNeeded);
2314 d.current_work_area_->setVerticalScrollBarPolicy(
2315 Qt::ScrollBarAlwaysOff);
2318 if (arg == "statusbar") {
2319 statusBar()->setVisible(!statusBar()->isVisible());
2322 if (arg == "menubar") {
2323 menuBar()->setVisible(!menuBar()->isVisible());
2326 #if QT_VERSION >= 0x040300
2327 if (arg == "frame") {
2329 getContentsMargins(&l, &t, &r, &b);
2330 //are the frames in default state?
2331 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
2333 setContentsMargins(-2, -2, -2, -2);
2335 setContentsMargins(0, 0, 0, 0);
2340 if (arg == "fullscreen") {
2345 message(bformat("LFUN_UI_TOGGLE " + _("%1$s unknown command!"), from_utf8(arg)));
2349 void GuiView::toggleFullScreen()
2351 if (isFullScreen()) {
2352 for (int i = 0; i != d.splitter_->count(); ++i)
2353 d.tabWorkArea(i)->setFullScreen(false);
2354 #if QT_VERSION >= 0x040300
2355 setContentsMargins(0, 0, 0, 0);
2357 setWindowState(windowState() ^ Qt::WindowFullScreen);
2360 statusBar()->show();
2363 hideDialogs("prefs", 0);
2364 for (int i = 0; i != d.splitter_->count(); ++i)
2365 d.tabWorkArea(i)->setFullScreen(true);
2366 #if QT_VERSION >= 0x040300
2367 setContentsMargins(-2, -2, -2, -2);
2370 setWindowState(windowState() ^ Qt::WindowFullScreen);
2371 statusBar()->hide();
2372 if (lyxrc.full_screen_menubar)
2374 if (lyxrc.full_screen_toolbars) {
2375 ToolbarMap::iterator end = d.toolbars_.end();
2376 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
2381 // give dialogs like the TOC a chance to adapt
2386 Buffer const * GuiView::updateInset(Inset const * inset)
2388 if (!d.current_work_area_)
2392 d.current_work_area_->scheduleRedraw();
2394 return &d.current_work_area_->bufferView().buffer();
2398 void GuiView::restartCursor()
2400 /* When we move around, or type, it's nice to be able to see
2401 * the cursor immediately after the keypress.
2403 if (d.current_work_area_)
2404 d.current_work_area_->startBlinkingCursor();
2406 // Take this occasion to update the other GUI elements.
2412 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
2414 if (d.current_work_area_)
2415 d.current_work_area_->completer().updateVisibility(cur, start, keep);
2420 // This list should be kept in sync with the list of insets in
2421 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
2422 // dialog should have the same name as the inset.
2423 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
2424 // docs in LyXAction.cpp.
2426 char const * const dialognames[] = {
2427 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
2428 "citation", "document", "errorlist", "ert", "external", "file", "findreplace",
2429 "findreplaceadv", "float", "graphics", "href", "include", "index",
2430 "index_print", "info", "listings", "label", "log", "mathdelimiter",
2431 "mathmatrix", "mathspace", "nomenclature", "nomencl_print", "note",
2432 "paragraph", "phantom", "prefs", "print", "ref", "sendto", "space",
2433 "spellchecker", "symbols", "tabular", "tabularcreate", "thesaurus", "texinfo",
2434 "toc", "view-source", "vspace", "wrap" };
2436 char const * const * const end_dialognames =
2437 dialognames + (sizeof(dialognames) / sizeof(char *));
2441 cmpCStr(char const * name) : name_(name) {}
2442 bool operator()(char const * other) {
2443 return strcmp(other, name_) == 0;
2450 bool isValidName(string const & name)
2452 return find_if(dialognames, end_dialognames,
2453 cmpCStr(name.c_str())) != end_dialognames;
2459 void GuiView::resetDialogs()
2461 // Make sure that no LFUN uses any LyXView.
2462 theLyXFunc().setLyXView(0);
2465 constructToolbars();
2466 guiApp->menus().fillMenuBar(menuBar(), this, false);
2468 d.layout_->updateContents(true);
2469 // Now update controls with current buffer.
2470 theLyXFunc().setLyXView(this);
2476 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
2478 if (!isValidName(name))
2481 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
2483 if (it != d.dialogs_.end()) {
2485 it->second->hideView();
2486 return it->second.get();
2489 Dialog * dialog = build(name);
2490 d.dialogs_[name].reset(dialog);
2491 if (lyxrc.allow_geometry_session)
2492 dialog->restoreSession();
2499 void GuiView::showDialog(string const & name, string const & data,
2507 Dialog * dialog = findOrBuild(name, false);
2509 dialog->showData(data);
2511 d.open_insets_[name] = inset;
2514 catch (ExceptionMessage const & ex) {
2522 bool GuiView::isDialogVisible(string const & name) const
2524 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2525 if (it == d.dialogs_.end())
2527 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
2531 void GuiView::hideDialog(string const & name, Inset * inset)
2533 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2534 if (it == d.dialogs_.end())
2537 if (inset && inset != getOpenInset(name))
2540 Dialog * const dialog = it->second.get();
2541 if (dialog->isVisibleView())
2543 d.open_insets_[name] = 0;
2547 void GuiView::disconnectDialog(string const & name)
2549 if (!isValidName(name))
2552 if (d.open_insets_.find(name) != d.open_insets_.end())
2553 d.open_insets_[name] = 0;
2557 Inset * GuiView::getOpenInset(string const & name) const
2559 if (!isValidName(name))
2562 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
2563 return it == d.open_insets_.end() ? 0 : it->second;
2567 void GuiView::hideAll() const
2569 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
2570 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2572 for(; it != end; ++it)
2573 it->second->hideView();
2577 void GuiView::updateDialogs()
2579 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
2580 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
2582 for(; it != end; ++it) {
2583 Dialog * dialog = it->second.get();
2584 if (dialog && dialog->isVisibleView())
2585 dialog->checkStatus();
2592 // will be replaced by a proper factory...
2593 Dialog * createGuiAbout(GuiView & lv);
2594 Dialog * createGuiBibitem(GuiView & lv);
2595 Dialog * createGuiBibtex(GuiView & lv);
2596 Dialog * createGuiBox(GuiView & lv);
2597 Dialog * createGuiBranch(GuiView & lv);
2598 Dialog * createGuiChanges(GuiView & lv);
2599 Dialog * createGuiCharacter(GuiView & lv);
2600 Dialog * createGuiCitation(GuiView & lv);
2601 Dialog * createGuiDelimiter(GuiView & lv);
2602 Dialog * createGuiDocument(GuiView & lv);
2603 Dialog * createGuiErrorList(GuiView & lv);
2604 Dialog * createGuiERT(GuiView & lv);
2605 Dialog * createGuiExternal(GuiView & lv);
2606 Dialog * createGuiFloat(GuiView & lv);
2607 Dialog * createGuiGraphics(GuiView & lv);
2608 Dialog * createGuiInclude(GuiView & lv);
2609 Dialog * createGuiIndex(GuiView & lv);
2610 Dialog * createGuiInfo(GuiView & lv);
2611 Dialog * createGuiLabel(GuiView & lv);
2612 Dialog * createGuiListings(GuiView & lv);
2613 Dialog * createGuiLog(GuiView & lv);
2614 Dialog * createGuiMathHSpace(GuiView & lv);
2615 Dialog * createGuiMathMatrix(GuiView & lv);
2616 Dialog * createGuiNomenclature(GuiView & lv);
2617 Dialog * createGuiNote(GuiView & lv);
2618 Dialog * createGuiParagraph(GuiView & lv);
2619 Dialog * createGuiPhantom(GuiView & lv);
2620 Dialog * createGuiPreferences(GuiView & lv);
2621 Dialog * createGuiPrint(GuiView & lv);
2622 Dialog * createGuiPrintindex(GuiView & lv);
2623 Dialog * createGuiPrintNomencl(GuiView & lv);
2624 Dialog * createGuiRef(GuiView & lv);
2625 Dialog * createGuiSearch(GuiView & lv);
2626 Dialog * createGuiSearchAdv(GuiView & lv);
2627 Dialog * createGuiSendTo(GuiView & lv);
2628 Dialog * createGuiShowFile(GuiView & lv);
2629 Dialog * createGuiSpellchecker(GuiView & lv);
2630 Dialog * createGuiSymbols(GuiView & lv);
2631 Dialog * createGuiTabularCreate(GuiView & lv);
2632 Dialog * createGuiTabular(GuiView & lv);
2633 Dialog * createGuiTexInfo(GuiView & lv);
2634 Dialog * createGuiTextHSpace(GuiView & lv);
2635 Dialog * createGuiToc(GuiView & lv);
2636 Dialog * createGuiThesaurus(GuiView & lv);
2637 Dialog * createGuiHyperlink(GuiView & lv);
2638 Dialog * createGuiVSpace(GuiView & lv);
2639 Dialog * createGuiViewSource(GuiView & lv);
2640 Dialog * createGuiWrap(GuiView & lv);
2643 Dialog * GuiView::build(string const & name)
2645 LASSERT(isValidName(name), return 0);
2647 if (name == "aboutlyx")
2648 return createGuiAbout(*this);
2649 if (name == "bibitem")
2650 return createGuiBibitem(*this);
2651 if (name == "bibtex")
2652 return createGuiBibtex(*this);
2654 return createGuiBox(*this);
2655 if (name == "branch")
2656 return createGuiBranch(*this);
2657 if (name == "changes")
2658 return createGuiChanges(*this);
2659 if (name == "character")
2660 return createGuiCharacter(*this);
2661 if (name == "citation")
2662 return createGuiCitation(*this);
2663 if (name == "document")
2664 return createGuiDocument(*this);
2665 if (name == "errorlist")
2666 return createGuiErrorList(*this);
2668 return createGuiERT(*this);
2669 if (name == "external")
2670 return createGuiExternal(*this);
2672 return createGuiShowFile(*this);
2673 if (name == "findreplace")
2674 return createGuiSearch(*this);
2675 if (name == "findreplaceadv")
2676 return createGuiSearchAdv(*this);
2677 if (name == "float")
2678 return createGuiFloat(*this);
2679 if (name == "graphics")
2680 return createGuiGraphics(*this);
2682 return createGuiHyperlink(*this);
2683 if (name == "include")
2684 return createGuiInclude(*this);
2685 if (name == "index")
2686 return createGuiIndex(*this);
2687 if (name == "index_print")
2688 return createGuiPrintindex(*this);
2690 return createGuiInfo(*this);
2691 if (name == "label")
2692 return createGuiLabel(*this);
2693 if (name == "listings")
2694 return createGuiListings(*this);
2696 return createGuiLog(*this);
2697 if (name == "mathdelimiter")
2698 return createGuiDelimiter(*this);
2699 if (name == "mathspace")
2700 return createGuiMathHSpace(*this);
2701 if (name == "mathmatrix")
2702 return createGuiMathMatrix(*this);
2703 if (name == "nomenclature")
2704 return createGuiNomenclature(*this);
2705 if (name == "nomencl_print")
2706 return createGuiPrintNomencl(*this);
2708 return createGuiNote(*this);
2709 if (name == "paragraph")
2710 return createGuiParagraph(*this);
2711 if (name == "phantom")
2712 return createGuiPhantom(*this);
2713 if (name == "prefs")
2714 return createGuiPreferences(*this);
2715 if (name == "print")
2716 return createGuiPrint(*this);
2718 return createGuiRef(*this);
2719 if (name == "sendto")
2720 return createGuiSendTo(*this);
2721 if (name == "space")
2722 return createGuiTextHSpace(*this);
2723 if (name == "spellchecker")
2724 return createGuiSpellchecker(*this);
2725 if (name == "symbols")
2726 return createGuiSymbols(*this);
2727 if (name == "tabular")
2728 return createGuiTabular(*this);
2729 if (name == "tabularcreate")
2730 return createGuiTabularCreate(*this);
2731 if (name == "texinfo")
2732 return createGuiTexInfo(*this);
2733 if (name == "thesaurus")
2734 return createGuiThesaurus(*this);
2736 return createGuiToc(*this);
2737 if (name == "view-source")
2738 return createGuiViewSource(*this);
2739 if (name == "vspace")
2740 return createGuiVSpace(*this);
2742 return createGuiWrap(*this);
2748 } // namespace frontend
2751 #include "moc_GuiView.cpp"