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 "frontends/FileDialog.h"
20 #include "GuiApplication.h"
21 #include "GuiWorkArea.h"
22 #include "GuiKeySymbol.h"
23 #include "GuiMenubar.h"
24 #include "GuiToolbar.h"
25 #include "GuiToolbars.h"
26 #include "GuiProgress.h"
28 #include "qt_helpers.h"
30 #include "frontends/alert.h"
32 #include "buffer_funcs.h"
34 #include "BufferList.h"
35 #include "BufferParams.h"
36 #include "BufferView.h"
38 #include "support/debug.h"
39 #include "ErrorList.h"
40 #include "FuncRequest.h"
41 #include "support/gettext.h"
49 #include "MenuBackend.h"
50 #include "Paragraph.h"
51 #include "TextClass.h"
53 #include "ToolbarBackend.h"
56 #include "support/FileFilterList.h"
57 #include "support/FileName.h"
58 #include "support/filetools.h"
59 #include "support/lstrings.h"
60 #include "support/os.h"
61 #include "support/Package.h"
62 #include "support/Timeout.h"
65 #include <QApplication>
66 #include <QCloseEvent>
68 #include <QDesktopWidget>
69 #include <QDragEnterEvent>
76 #include <QPushButton>
80 #include <QStackedWidget>
86 #include <boost/assert.hpp>
87 #include <boost/bind.hpp>
89 #ifdef HAVE_SYS_TIME_H
90 # include <sys/time.h>
100 extern bool quitting;
104 using support::addPath;
105 using support::bformat;
106 using support::FileFilterList;
107 using support::FileName;
108 using support::makeAbsPath;
109 using support::makeDisplayPath;
110 using support::package;
111 using support::removeAutosaveFile;
116 class BackgroundWidget : public QWidget
121 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
122 /// The text to be written on top of the pixmap
123 QString const text = lyx_version ? lyx_version : qt_("unknown version");
124 splash_ = QPixmap(":/images/banner.png");
126 QPainter pain(&splash_);
127 pain.setPen(QColor(255, 255, 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, 270, 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_);
152 typedef boost::shared_ptr<Dialog> DialogPtr;
154 struct GuiView::GuiViewPrivate
157 : current_work_area_(0), layout_(0),
158 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
160 // hardcode here the platform specific icon size
161 smallIconSize = 14; // scaling problems
162 normalIconSize = 20; // ok, default
163 bigIconSize = 26; // better for some math icons
165 splitter_ = new QSplitter;
166 bg_widget_ = new BackgroundWidget;
167 stack_widget_ = new QStackedWidget;
168 stack_widget_->addWidget(bg_widget_);
169 stack_widget_->addWidget(splitter_);
177 delete stack_widget_;
182 QMenu * toolBarPopup(GuiView * parent)
184 // FIXME: translation
185 QMenu * menu = new QMenu(parent);
186 QActionGroup * iconSizeGroup = new QActionGroup(parent);
188 QAction * smallIcons = new QAction(iconSizeGroup);
189 smallIcons->setText(qt_("Small-sized icons"));
190 smallIcons->setCheckable(true);
191 QObject::connect(smallIcons, SIGNAL(triggered()),
192 parent, SLOT(smallSizedIcons()));
193 menu->addAction(smallIcons);
195 QAction * normalIcons = new QAction(iconSizeGroup);
196 normalIcons->setText(qt_("Normal-sized icons"));
197 normalIcons->setCheckable(true);
198 QObject::connect(normalIcons, SIGNAL(triggered()),
199 parent, SLOT(normalSizedIcons()));
200 menu->addAction(normalIcons);
202 QAction * bigIcons = new QAction(iconSizeGroup);
203 bigIcons->setText(qt_("Big-sized icons"));
204 bigIcons->setCheckable(true);
205 QObject::connect(bigIcons, SIGNAL(triggered()),
206 parent, SLOT(bigSizedIcons()));
207 menu->addAction(bigIcons);
209 unsigned int cur = parent->iconSize().width();
210 if ( cur == parent->d.smallIconSize)
211 smallIcons->setChecked(true);
212 else if (cur == parent->d.normalIconSize)
213 normalIcons->setChecked(true);
214 else if (cur == parent->d.bigIconSize)
215 bigIcons->setChecked(true);
222 stack_widget_->setCurrentWidget(bg_widget_);
223 bg_widget_->setUpdatesEnabled(true);
226 TabWorkArea * tabWorkArea(int i)
228 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
231 TabWorkArea * currentTabWorkArea()
233 if (splitter_->count() == 1)
234 // The first TabWorkArea is always the first one, if any.
235 return tabWorkArea(0);
237 TabWorkArea * tab_widget = 0;
238 for (int i = 0; i != splitter_->count(); ++i) {
239 QWidget * w = splitter_->widget(i);
242 tab_widget = dynamic_cast<TabWorkArea *>(w);
251 GuiWorkArea * current_work_area_;
252 QSplitter * splitter_;
253 QStackedWidget * stack_widget_;
254 BackgroundWidget * bg_widget_;
256 GuiMenubar * menubar_;
258 GuiToolbars * toolbars_;
259 /// The main layout box.
261 * \warning Don't Delete! The layout box is actually owned by
262 * whichever toolbar contains it. All the GuiView class needs is a
263 * means of accessing it.
265 * FIXME: replace that with a proper model so that we are not limited
266 * to only one dialog.
268 GuiLayoutBox * layout_;
271 std::map<std::string, Inset *> open_insets_;
274 std::map<std::string, DialogPtr> dialogs_;
276 unsigned int smallIconSize;
277 unsigned int normalIconSize;
278 unsigned int bigIconSize;
280 QTimer statusbar_timer_;
281 /// are we quitting by the menu?
282 bool quitting_by_menu_;
283 /// auto-saving of buffers
284 Timeout autosave_timeout_;
285 /// flag against a race condition due to multiclicks, see bug #1119
290 GuiView::GuiView(int id)
291 : d(*new GuiViewPrivate), id_(id)
293 // GuiToolbars *must* be initialised before GuiMenubar.
294 d.toolbars_ = new GuiToolbars(*this);
295 d.menubar_ = new GuiMenubar(this, menubackend);
297 setCentralWidget(d.stack_widget_);
299 // Start autosave timer
300 if (lyxrc.autosave) {
301 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
302 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
303 d.autosave_timeout_.start();
305 connect(&d.statusbar_timer_, SIGNAL(timeout()),
306 this, SLOT(clearMessage()));
308 // Qt bug? signal lastWindowClosed does not work
309 setAttribute(Qt::WA_QuitOnClose, false);
310 setAttribute(Qt::WA_DeleteOnClose, true);
312 // assign an icon to main form. We do not do it under Qt/Mac,
313 // since the icon is provided in the application bundle.
314 setWindowIcon(QPixmap(":/images/lyx.png"));
318 setAcceptDrops(true);
320 statusBar()->setSizeGripEnabled(true);
322 // Forbid too small unresizable window because it can happen
323 // with some window manager under X11.
324 setMinimumSize(300, 200);
326 if (!lyxrc.allow_geometry_session)
327 // No session handling, default to a sane size.
328 setGeometry(50, 50, 690, 510);
330 // Now take care of session management.
332 QString const key = "view-" + QString::number(id_);
334 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
335 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
339 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
340 setGeometry(50, 50, 690, 510);
342 setIconSize(settings.value(key + "/icon_size").toSize());
352 void GuiView::close()
354 d.quitting_by_menu_ = true;
355 d.current_work_area_ = 0;
356 for (int i = 0; i != d.splitter_->count(); ++i) {
357 TabWorkArea * twa = d.tabWorkArea(i);
361 QMainWindow::close();
362 d.quitting_by_menu_ = false;
366 void GuiView::setFocus()
368 if (d.current_work_area_)
369 d.current_work_area_->setFocus();
375 QMenu * GuiView::createPopupMenu()
377 return d.toolBarPopup(this);
381 void GuiView::showEvent(QShowEvent * e)
383 LYXERR(Debug::GUI, "Passed Geometry "
384 << size().height() << "x" << size().width()
385 << "+" << pos().x() << "+" << pos().y());
387 if (d.splitter_->count() == 0)
388 // No work area, switch to the background widget.
391 QMainWindow::showEvent(e);
395 void GuiView::closeEvent(QCloseEvent * close_event)
397 // we may have been called through the close window button
398 // which bypasses the LFUN machinery.
399 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
400 if (!quitWriteAll()) {
401 close_event->ignore();
406 // Make sure that no LFUN use this close to be closed View.
407 theLyXFunc().setLyXView(0);
408 // Make sure the timer time out will not trigger a statusbar update.
409 d.statusbar_timer_.stop();
411 if (lyxrc.allow_geometry_session) {
413 QString const key = "view-" + QString::number(id_);
415 settings.setValue(key + "/pos", pos());
416 settings.setValue(key + "/size", size());
418 settings.setValue(key + "/geometry", saveGeometry());
420 settings.setValue(key + "/icon_size", iconSize());
421 d.toolbars_->saveToolbarInfo();
422 // Now take care of all other dialogs:
423 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
424 for (; it!= d.dialogs_.end(); ++it)
425 it->second->saveSession();
428 guiApp->unregisterView(id_);
429 if (guiApp->viewCount() > 0) {
430 // Just close the window and do nothing else if this is not the
432 close_event->accept();
438 // this is the place where we leave the frontend.
439 // it is the only point at which we start quitting.
440 close_event->accept();
441 // quit the event loop
446 void GuiView::dragEnterEvent(QDragEnterEvent * event)
448 if (event->mimeData()->hasUrls())
450 /// \todo Ask lyx-devel is this is enough:
451 /// if (event->mimeData()->hasFormat("text/plain"))
452 /// event->acceptProposedAction();
456 void GuiView::dropEvent(QDropEvent* event)
458 QList<QUrl> files = event->mimeData()->urls();
462 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
463 for (int i = 0; i != files.size(); ++i) {
464 string const file = support::os::internal_path(fromqstr(
465 files.at(i).toLocalFile()));
467 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
472 void GuiView::message(docstring const & str)
474 statusBar()->showMessage(toqstr(str));
475 d.statusbar_timer_.stop();
476 d.statusbar_timer_.start(3000);
480 void GuiView::smallSizedIcons()
482 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
486 void GuiView::normalSizedIcons()
488 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
492 void GuiView::bigSizedIcons()
494 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
498 void GuiView::clearMessage()
502 theLyXFunc().setLyXView(this);
503 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
504 d.statusbar_timer_.stop();
508 void GuiView::updateWindowTitle(GuiWorkArea * wa)
510 if (wa != d.current_work_area_)
512 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
513 setWindowIconText(wa->windowIconText());
517 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
520 disconnectBufferView();
521 connectBufferView(wa->bufferView());
522 connectBuffer(wa->bufferView().buffer());
523 d.current_work_area_ = wa;
524 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
525 this, SLOT(updateWindowTitle(GuiWorkArea *)));
526 updateWindowTitle(wa);
529 // Buffer-dependent dialogs should be updated or
530 // hidden. This should go here because some dialogs (eg ToC)
531 // require bv_->text.
532 updateBufferDependent(true);
539 void GuiView::updateStatusBar()
541 // let the user see the explicit message
542 if (d.statusbar_timer_.isActive())
545 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
549 bool GuiView::hasFocus() const
551 return qApp->activeWindow() == this;
555 bool GuiView::event(QEvent * e)
559 // Useful debug code:
560 //case QEvent::ActivationChange:
561 //case QEvent::WindowDeactivate:
562 //case QEvent::Paint:
563 //case QEvent::Enter:
564 //case QEvent::Leave:
565 //case QEvent::HoverEnter:
566 //case QEvent::HoverLeave:
567 //case QEvent::HoverMove:
568 //case QEvent::StatusTip:
569 //case QEvent::DragEnter:
570 //case QEvent::DragLeave:
574 case QEvent::WindowActivate: {
575 guiApp->setCurrentView(*this);
576 if (d.current_work_area_) {
577 BufferView & bv = d.current_work_area_->bufferView();
578 connectBufferView(bv);
579 connectBuffer(bv.buffer());
580 // The document structure, name and dialogs might have
581 // changed in another view.
582 updateBufferDependent(true);
584 setWindowTitle(qt_("LyX"));
585 setWindowIconText(qt_("LyX"));
587 return QMainWindow::event(e);
589 case QEvent::ShortcutOverride: {
590 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
591 if (!d.current_work_area_) {
592 theLyXFunc().setLyXView(this);
594 setKeySymbol(&sym, ke);
595 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
599 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
601 setKeySymbol(&sym, ke);
602 d.current_work_area_->processKeySym(sym, NoModifier);
608 return QMainWindow::event(e);
613 bool GuiView::focusNextPrevChild(bool /*next*/)
620 void GuiView::setBusy(bool yes)
622 if (d.current_work_area_) {
623 d.current_work_area_->setUpdatesEnabled(!yes);
625 d.current_work_area_->stopBlinkingCursor();
627 d.current_work_area_->startBlinkingCursor();
631 QApplication::setOverrideCursor(Qt::WaitCursor);
633 QApplication::restoreOverrideCursor();
637 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
639 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
641 if (tbinfo.flags & ToolbarInfo::TOP) {
643 addToolBarBreak(Qt::TopToolBarArea);
644 addToolBar(Qt::TopToolBarArea, toolBar);
647 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
648 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
649 #if (QT_VERSION >= 0x040202)
651 addToolBarBreak(Qt::BottomToolBarArea);
653 addToolBar(Qt::BottomToolBarArea, toolBar);
656 if (tbinfo.flags & ToolbarInfo::LEFT) {
657 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
658 #if (QT_VERSION >= 0x040202)
660 addToolBarBreak(Qt::LeftToolBarArea);
662 addToolBar(Qt::LeftToolBarArea, toolBar);
665 if (tbinfo.flags & ToolbarInfo::RIGHT) {
666 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
667 #if (QT_VERSION >= 0x040202)
669 addToolBarBreak(Qt::RightToolBarArea);
671 addToolBar(Qt::RightToolBarArea, toolBar);
674 // The following does not work so I cannot restore to exact toolbar location
676 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
677 toolBar->move(tbinfo.posx, tbinfo.posy);
684 GuiWorkArea * GuiView::workArea(Buffer & buffer)
686 for (int i = 0; i != d.splitter_->count(); ++i) {
687 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
695 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
698 // Automatically create a TabWorkArea if there are none yet.
699 if (!d.splitter_->count())
702 TabWorkArea * tab_widget = d.currentTabWorkArea();
703 return tab_widget->addWorkArea(buffer, *this);
707 void GuiView::addTabWorkArea()
709 TabWorkArea * twa = new TabWorkArea;
710 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
711 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
712 d.splitter_->addWidget(twa);
713 d.stack_widget_->setCurrentWidget(d.splitter_);
717 GuiWorkArea const * GuiView::currentWorkArea() const
719 return d.current_work_area_;
723 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
727 // Changing work area can result from opening a file so
728 // update the toc in any case.
731 d.current_work_area_ = wa;
732 for (int i = 0; i != d.splitter_->count(); ++i) {
733 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
739 void GuiView::removeWorkArea(GuiWorkArea * wa)
742 if (wa == d.current_work_area_) {
744 disconnectBufferView();
745 hideBufferDependent();
746 d.current_work_area_ = 0;
749 for (int i = 0; i != d.splitter_->count(); ++i) {
750 TabWorkArea * twa = d.tabWorkArea(i);
751 if (!twa->removeWorkArea(wa))
752 // Not found in this tab group.
755 // We found and removed the GuiWorkArea.
757 // No more WorkAreas in this tab group, so delete it.
762 if (d.current_work_area_)
763 // This means that we are not closing the current GuiWorkArea;
766 // Switch to the next GuiWorkArea in the found TabWorkArea.
767 d.current_work_area_ = twa->currentWorkArea();
771 if (d.splitter_->count() == 0)
772 // No more work area, switch to the background widget.
777 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
783 void GuiView::updateLayoutList()
786 d.layout_->updateContents(false);
790 void GuiView::updateToolbars()
792 if (d.current_work_area_) {
794 d.current_work_area_->bufferView().cursor().inMathed();
796 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
798 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
799 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
801 d.toolbars_->update(math, table, review);
803 d.toolbars_->update(false, false, false);
805 // update read-only status of open dialogs.
810 Buffer * GuiView::buffer()
812 if (d.current_work_area_)
813 return &d.current_work_area_->bufferView().buffer();
818 Buffer const * GuiView::buffer() const
820 if (d.current_work_area_)
821 return &d.current_work_area_->bufferView().buffer();
826 void GuiView::setBuffer(Buffer * newBuffer)
828 BOOST_ASSERT(newBuffer);
831 GuiWorkArea * wa = workArea(*newBuffer);
833 updateLabels(*newBuffer->masterBuffer());
834 wa = addWorkArea(*newBuffer);
836 //Disconnect the old buffer...there's no new one.
839 connectBuffer(*newBuffer);
840 connectBufferView(wa->bufferView());
841 setCurrentWorkArea(wa);
847 void GuiView::connectBuffer(Buffer & buf)
849 buf.setGuiDelegate(this);
853 void GuiView::disconnectBuffer()
855 if (d.current_work_area_)
856 d.current_work_area_->bufferView().setGuiDelegate(0);
860 void GuiView::connectBufferView(BufferView & bv)
862 bv.setGuiDelegate(this);
866 void GuiView::disconnectBufferView()
868 if (d.current_work_area_)
869 d.current_work_area_->bufferView().setGuiDelegate(0);
873 void GuiView::errors(string const & error_type)
875 ErrorList & el = buffer()->errorList(error_type);
877 showDialog("errorlist", error_type);
881 void GuiView::updateDialog(string const & name, string const & data)
883 if (!isDialogVisible(name))
886 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
887 if (it == d.dialogs_.end())
890 Dialog * const dialog = it->second.get();
891 if (dialog->isVisibleView())
892 dialog->updateData(data);
896 BufferView * GuiView::view()
898 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
902 void GuiView::updateToc()
904 updateDialog("toc", "");
908 void GuiView::updateEmbeddedFiles()
910 updateDialog("embedding", "");
914 void GuiView::autoSave()
916 LYXERR(Debug::INFO, "Running autoSave()");
919 view()->buffer().autoSave();
923 void GuiView::resetAutosaveTimers()
926 d.autosave_timeout_.restart();
930 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
934 Buffer * buf = buffer();
936 /* In LyX/Mac, when a dialog is open, the menus of the
937 application can still be accessed without giving focus to
938 the main window. In this case, we want to disable the menu
939 entries that are buffer-related.
941 Note that this code is not perfect, as bug 1941 attests:
942 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
944 if (cmd.origin == FuncRequest::MENU && !hasFocus())
948 case LFUN_BUFFER_WRITE:
949 enable = buf && (buf->isUnnamed() || !buf->isClean());
952 case LFUN_BUFFER_WRITE_AS:
956 case LFUN_TOOLBAR_TOGGLE:
957 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
960 case LFUN_DIALOG_TOGGLE:
961 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
962 // fall through to set "enable"
963 case LFUN_DIALOG_SHOW: {
964 string const name = cmd.getArg(0);
966 enable = name == "aboutlyx"
967 || name == "file" //FIXME: should be removed.
969 || name == "texinfo";
970 else if (name == "print")
971 enable = buf->isExportable("dvi")
972 && lyxrc.print_command != "none";
973 else if (name == "character") {
977 InsetCode ic = view()->cursor().inset().lyxCode();
978 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
981 else if (name == "latexlog")
982 enable = FileName(buf->logName()).isReadableFile();
983 else if (name == "spellchecker")
984 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
985 enable = !buf->isReadonly();
989 else if (name == "vclog")
990 enable = buf->lyxvc().inUse();
994 case LFUN_DIALOG_UPDATE: {
995 string const name = cmd.getArg(0);
997 enable = name == "prefs";
1001 case LFUN_INSET_APPLY: {
1006 string const name = cmd.getArg(0);
1007 Inset * inset = getOpenInset(name);
1009 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1011 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1012 // Every inset is supposed to handle this
1013 BOOST_ASSERT(false);
1017 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1018 flag |= getStatus(fr);
1020 enable = flag.enabled();
1032 flag.enabled(false);
1038 static FileName selectTemplateFile()
1040 FileDialog dlg(_("Select template file"));
1041 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1042 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1044 FileDialog::Result result =
1045 dlg.open(from_utf8(lyxrc.template_path),
1046 FileFilterList(_("LyX Documents (*.lyx)")),
1049 if (result.first == FileDialog::Later)
1051 if (result.second.empty())
1053 return FileName(to_utf8(result.second));
1057 void GuiView::newDocument(string const & filename, bool from_template)
1059 FileName initpath(lyxrc.document_path);
1060 Buffer * buf = buffer();
1062 FileName const trypath(buf->filePath());
1063 // If directory is writeable, use this as default.
1064 if (trypath.isDirWritable())
1068 string templatefile = from_template ?
1069 selectTemplateFile().absFilename() : string();
1071 if (filename.empty())
1072 b = newUnnamedFile(templatefile, initpath);
1074 b = newFile(filename, templatefile, true);
1081 void GuiView::insertLyXFile(docstring const & fname)
1083 BufferView * bv = view();
1088 FileName filename(to_utf8(fname));
1090 if (!filename.empty()) {
1091 bv->insertLyXFile(filename);
1095 // Launch a file browser
1097 string initpath = lyxrc.document_path;
1098 string const trypath = bv->buffer().filePath();
1099 // If directory is writeable, use this as default.
1100 if (FileName(trypath).isDirWritable())
1104 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1105 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1106 dlg.setButton2(_("Examples|#E#e"),
1107 from_utf8(addPath(package().system_support().absFilename(),
1110 FileDialog::Result result =
1111 dlg.open(from_utf8(initpath),
1112 FileFilterList(_("LyX Documents (*.lyx)")),
1115 if (result.first == FileDialog::Later)
1119 filename.set(to_utf8(result.second));
1121 // check selected filename
1122 if (filename.empty()) {
1123 // emit message signal.
1124 message(_("Canceled."));
1128 bv->insertLyXFile(filename);
1132 void GuiView::insertPlaintextFile(docstring const & fname,
1135 BufferView * bv = view();
1140 FileName filename(to_utf8(fname));
1142 if (!filename.empty()) {
1143 bv->insertPlaintextFile(filename, asParagraph);
1147 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1148 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1150 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1151 FileFilterList(), docstring());
1153 if (result.first == FileDialog::Later)
1157 filename.set(to_utf8(result.second));
1159 // check selected filename
1160 if (filename.empty()) {
1161 // emit message signal.
1162 message(_("Canceled."));
1166 bv->insertPlaintextFile(filename, asParagraph);
1170 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1172 FileName fname = b.fileName();
1173 FileName const oldname = fname;
1175 if (!newname.empty()) {
1177 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1179 // Switch to this Buffer.
1182 /// No argument? Ask user through dialog.
1184 FileDialog dlg(_("Choose a filename to save document as"),
1185 LFUN_BUFFER_WRITE_AS);
1186 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1187 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1189 if (!support::isLyXFilename(fname.absFilename()))
1190 fname.changeExtension(".lyx");
1192 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1194 FileDialog::Result result =
1195 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1197 from_utf8(fname.onlyFileName()));
1199 if (result.first == FileDialog::Later)
1202 fname.set(to_utf8(result.second));
1207 if (!support::isLyXFilename(fname.absFilename()))
1208 fname.changeExtension(".lyx");
1211 if (FileName(fname).exists()) {
1212 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1213 docstring text = bformat(_("The document %1$s already "
1214 "exists.\n\nDo you want to "
1215 "overwrite that document?"),
1217 int const ret = Alert::prompt(_("Overwrite document?"),
1218 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1221 case 1: return renameBuffer(b, docstring());
1222 case 2: return false;
1226 // Ok, change the name of the buffer
1227 b.setFileName(fname.absFilename());
1229 bool unnamed = b.isUnnamed();
1230 b.setUnnamed(false);
1231 b.saveCheckSum(fname);
1233 if (!saveBuffer(b)) {
1234 b.setFileName(oldname.absFilename());
1235 b.setUnnamed(unnamed);
1236 b.saveCheckSum(oldname);
1244 bool GuiView::saveBuffer(Buffer & b)
1247 return renameBuffer(b, docstring());
1250 LyX::ref().session().lastFiles().add(b.fileName());
1254 // Switch to this Buffer.
1257 // FIXME: we don't tell the user *WHY* the save failed !!
1258 docstring const file = makeDisplayPath(b.absFileName(), 30);
1259 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1260 "Do you want to rename the document and "
1261 "try again?"), file);
1262 int const ret = Alert::prompt(_("Rename and save?"),
1263 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1266 if (!renameBuffer(b, docstring()))
1275 return saveBuffer(b);
1279 bool GuiView::closeBuffer()
1281 Buffer * buf = buffer();
1282 return buf && closeBuffer(*buf);
1286 bool GuiView::closeBuffer(Buffer & buf)
1288 if (buf.isClean() || buf.paragraphs().empty()) {
1289 theBufferList().release(&buf);
1292 // Switch to this Buffer.
1297 if (buf.isUnnamed())
1298 file = from_utf8(buf.fileName().onlyFileName());
1300 file = buf.fileName().displayName(30);
1302 docstring const text = bformat(_("The document %1$s has unsaved changes."
1303 "\n\nDo you want to save the document or discard the changes?"), file);
1304 int const ret = Alert::prompt(_("Save changed document?"),
1305 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1309 if (!saveBuffer(buf))
1313 // if we crash after this we could
1314 // have no autosave file but I guess
1315 // this is really improbable (Jug)
1316 removeAutosaveFile(buf.absFileName());
1322 // save file names to .lyx/session
1323 // if master/slave are both open, do not save slave since it
1324 // will be automatically loaded when the master is loaded
1325 if (buf.masterBuffer() == &buf)
1326 LyX::ref().session().lastOpened().add(buf.fileName());
1328 theBufferList().release(&buf);
1333 bool GuiView::quitWriteAll()
1335 while (!theBufferList().empty()) {
1336 Buffer * b = theBufferList().first();
1337 if (!closeBuffer(*b))
1344 bool GuiView::dispatch(FuncRequest const & cmd)
1346 BufferView * bv = view();
1347 // By default we won't need any update.
1349 bv->cursor().updateFlags(Update::None);
1351 switch(cmd.action) {
1352 case LFUN_BUFFER_SWITCH:
1353 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1356 case LFUN_BUFFER_NEXT:
1357 setBuffer(theBufferList().next(buffer()));
1360 case LFUN_BUFFER_PREVIOUS:
1361 setBuffer(theBufferList().previous(buffer()));
1364 case LFUN_COMMAND_EXECUTE: {
1365 bool const show_it = cmd.argument() != "off";
1366 d.toolbars_->showCommandBuffer(show_it);
1369 case LFUN_DROP_LAYOUTS_CHOICE:
1371 d.layout_->showPopup();
1374 case LFUN_MENU_OPEN:
1375 d.menubar_->openByName(toqstr(cmd.argument()));
1378 case LFUN_FILE_INSERT:
1379 insertLyXFile(cmd.argument());
1381 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1382 insertPlaintextFile(cmd.argument(), true);
1385 case LFUN_FILE_INSERT_PLAINTEXT:
1386 insertPlaintextFile(cmd.argument(), false);
1389 case LFUN_BUFFER_WRITE:
1391 saveBuffer(bv->buffer());
1394 case LFUN_BUFFER_WRITE_AS:
1396 renameBuffer(bv->buffer(), cmd.argument());
1399 case LFUN_BUFFER_WRITE_ALL: {
1400 Buffer * first = theBufferList().first();
1403 message(_("Saving all documents..."));
1404 // We cannot use a for loop as the buffer list cycles.
1410 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1411 b = theBufferList().next(b);
1412 } while (b != first);
1413 message(_("All documents saved."));
1417 case LFUN_TOOLBAR_TOGGLE: {
1418 string const name = cmd.getArg(0);
1419 bool const allowauto = cmd.getArg(1) == "allowauto";
1420 // it is possible to get current toolbar status like this,...
1421 // but I decide to obey the order of ToolbarBackend::flags
1422 // and disregard real toolbar status.
1423 // toolbars_->saveToolbarInfo();
1425 // toggle state on/off/auto
1426 d.toolbars_->toggleToolbarState(name, allowauto);
1430 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1432 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1436 if (tbi->flags & ToolbarInfo::ON)
1438 else if (tbi->flags & ToolbarInfo::OFF)
1440 else if (tbi->flags & ToolbarInfo::AUTO)
1443 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1444 _(tbi->gui_name), state));
1448 case LFUN_DIALOG_UPDATE: {
1449 string const name = to_utf8(cmd.argument());
1450 // Can only update a dialog connected to an existing inset
1451 Inset * inset = getOpenInset(name);
1453 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1454 inset->dispatch(view()->cursor(), fr);
1455 } else if (name == "paragraph") {
1456 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1457 } else if (name == "prefs") {
1458 updateDialog(name, string());
1463 case LFUN_DIALOG_TOGGLE: {
1464 if (isDialogVisible(cmd.getArg(0)))
1465 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1467 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1471 case LFUN_DIALOG_DISCONNECT_INSET:
1472 disconnectDialog(to_utf8(cmd.argument()));
1475 case LFUN_DIALOG_HIDE: {
1478 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1482 case LFUN_DIALOG_SHOW: {
1483 string const name = cmd.getArg(0);
1484 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1486 if (name == "character") {
1487 data = freefont2string();
1489 showDialog("character", data);
1490 } else if (name == "latexlog") {
1491 Buffer::LogType type;
1492 string const logfile = buffer()->logName(&type);
1494 case Buffer::latexlog:
1497 case Buffer::buildlog:
1501 data += Lexer::quoteString(logfile);
1502 showDialog("log", data);
1503 } else if (name == "vclog") {
1504 string const data = "vc " +
1505 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1506 showDialog("log", data);
1508 showDialog(name, data);
1512 case LFUN_INSET_APPLY: {
1513 string const name = cmd.getArg(0);
1514 Inset * inset = getOpenInset(name);
1516 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1517 inset->dispatch(view()->cursor(), fr);
1519 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1533 Buffer const * GuiView::updateInset(Inset const * inset)
1535 if (!d.current_work_area_)
1539 d.current_work_area_->scheduleRedraw();
1541 return &d.current_work_area_->bufferView().buffer();
1545 void GuiView::restartCursor()
1547 /* When we move around, or type, it's nice to be able to see
1548 * the cursor immediately after the keypress.
1550 if (d.current_work_area_)
1551 d.current_work_area_->startBlinkingCursor();
1553 // Take this occasion to update the toobars and layout list.
1560 // This list should be kept in sync with the list of insets in
1561 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1562 // dialog should have the same name as the inset.
1564 char const * const dialognames[] = {
1565 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1566 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1567 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1568 "mathdelimiter", "mathmatrix", "note", "paragraph",
1569 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1571 #ifdef HAVE_LIBAIKSAURUS
1575 "texinfo", "toc", "href", "view-source", "view-console", "vspace", "wrap", "listings" };
1577 char const * const * const end_dialognames =
1578 dialognames + (sizeof(dialognames) / sizeof(char *));
1582 cmpCStr(char const * name) : name_(name) {}
1583 bool operator()(char const * other) {
1584 return strcmp(other, name_) == 0;
1591 bool isValidName(string const & name)
1593 return std::find_if(dialognames, end_dialognames,
1594 cmpCStr(name.c_str())) != end_dialognames;
1600 void GuiView::resetDialogs()
1602 // Make sure that no LFUN uses any LyXView.
1603 theLyXFunc().setLyXView(0);
1604 d.toolbars_->init();
1607 d.layout_->updateContents(true);
1608 // Now update controls with current buffer.
1609 theLyXFunc().setLyXView(this);
1614 Dialog * GuiView::find_or_build(string const & name)
1616 if (!isValidName(name))
1619 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1621 if (it != d.dialogs_.end())
1622 return it->second.get();
1624 Dialog * dialog = build(name);
1625 d.dialogs_[name].reset(dialog);
1626 if (lyxrc.allow_geometry_session)
1627 dialog->restoreSession();
1632 void GuiView::showDialog(string const & name, string const & data,
1639 Dialog * dialog = find_or_build(name);
1641 dialog->showData(data);
1643 d.open_insets_[name] = inset;
1649 bool GuiView::isDialogVisible(string const & name) const
1651 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1652 if (it == d.dialogs_.end())
1654 return it->second.get()->isVisibleView();
1658 void GuiView::hideDialog(string const & name, Inset * inset)
1660 // Don't send the signal if we are quitting, because on MSVC it is
1661 // destructed before the cut stack in CutAndPaste.cpp, and this method
1662 // is called from some inset destructor if the cut stack is not empty
1667 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1668 if (it == d.dialogs_.end())
1671 if (inset && inset != getOpenInset(name))
1674 Dialog * const dialog = it->second.get();
1675 if (dialog->isVisibleView())
1677 d.open_insets_[name] = 0;
1681 void GuiView::disconnectDialog(string const & name)
1683 if (!isValidName(name))
1686 if (d.open_insets_.find(name) != d.open_insets_.end())
1687 d.open_insets_[name] = 0;
1691 Inset * GuiView::getOpenInset(string const & name) const
1693 if (!isValidName(name))
1696 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1697 return it == d.open_insets_.end() ? 0 : it->second;
1701 void GuiView::hideAll() const
1703 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1704 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1706 for(; it != end; ++it)
1707 it->second->hideView();
1711 void GuiView::hideBufferDependent() const
1713 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1714 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1716 for(; it != end; ++it) {
1717 Dialog * dialog = it->second.get();
1718 if (dialog->isBufferDependent())
1724 void GuiView::updateBufferDependent(bool switched) const
1726 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1727 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1729 for(; it != end; ++it) {
1730 Dialog * dialog = it->second.get();
1731 if (!dialog->isVisibleView())
1733 if (switched && dialog->isBufferDependent()) {
1734 if (dialog->initialiseParams(""))
1735 dialog->updateView();
1739 // A bit clunky, but the dialog will request
1740 // that the kernel provides it with the necessary
1742 dialog->updateDialog();
1748 void GuiView::checkStatus()
1750 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1751 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1753 for(; it != end; ++it) {
1754 Dialog * const dialog = it->second.get();
1755 if (dialog && dialog->isVisibleView())
1756 dialog->checkStatus();
1762 // will be replaced by a proper factory...
1763 Dialog * createGuiAbout(GuiView & lv);
1764 Dialog * createGuiBibitem(GuiView & lv);
1765 Dialog * createGuiBibtex(GuiView & lv);
1766 Dialog * createGuiBox(GuiView & lv);
1767 Dialog * createGuiBranch(GuiView & lv);
1768 Dialog * createGuiChanges(GuiView & lv);
1769 Dialog * createGuiCharacter(GuiView & lv);
1770 Dialog * createGuiCitation(GuiView & lv);
1771 Dialog * createGuiDelimiter(GuiView & lv);
1772 Dialog * createGuiDocument(GuiView & lv);
1773 Dialog * createGuiErrorList(GuiView & lv);
1774 Dialog * createGuiERT(GuiView & lv);
1775 Dialog * createGuiExternal(GuiView & lv);
1776 Dialog * createGuiFloat(GuiView & lv);
1777 Dialog * createGuiGraphics(GuiView & lv);
1778 Dialog * createGuiInclude(GuiView & lv);
1779 Dialog * createGuiIndex(GuiView & lv);
1780 Dialog * createGuiLabel(GuiView & lv);
1781 Dialog * createGuiListings(GuiView & lv);
1782 Dialog * createGuiLog(GuiView & lv);
1783 Dialog * createGuiMathMatrix(GuiView & lv);
1784 Dialog * createGuiNomenclature(GuiView & lv);
1785 Dialog * createGuiNote(GuiView & lv);
1786 Dialog * createGuiParagraph(GuiView & lv);
1787 Dialog * createGuiPreferences(GuiView & lv);
1788 Dialog * createGuiPrint(GuiView & lv);
1789 Dialog * createGuiRef(GuiView & lv);
1790 Dialog * createGuiSearch(GuiView & lv);
1791 Dialog * createGuiSendTo(GuiView & lv);
1792 Dialog * createGuiShowFile(GuiView & lv);
1793 Dialog * createGuiSpellchecker(GuiView & lv);
1794 Dialog * createGuiTabularCreate(GuiView & lv);
1795 Dialog * createGuiTabular(GuiView & lv);
1796 Dialog * createGuiTexInfo(GuiView & lv);
1797 Dialog * createGuiToc(GuiView & lv);
1798 Dialog * createGuiThesaurus(GuiView & lv);
1799 Dialog * createGuiHyperlink(GuiView & lv);
1800 Dialog * createGuiVSpace(GuiView & lv);
1801 Dialog * createGuiViewSource(GuiView & lv);
1802 Dialog * createGuiProgress(GuiView & lv);
1803 Dialog * createGuiWrap(GuiView & lv);
1806 Dialog * GuiView::build(string const & name)
1808 BOOST_ASSERT(isValidName(name));
1810 if (name == "aboutlyx")
1811 return createGuiAbout(*this);
1812 if (name == "bibitem")
1813 return createGuiBibitem(*this);
1814 if (name == "bibtex")
1815 return createGuiBibtex(*this);
1817 return createGuiBox(*this);
1818 if (name == "branch")
1819 return createGuiBranch(*this);
1820 if (name == "changes")
1821 return createGuiChanges(*this);
1822 if (name == "character")
1823 return createGuiCharacter(*this);
1824 if (name == "citation")
1825 return createGuiCitation(*this);
1826 if (name == "document")
1827 return createGuiDocument(*this);
1828 if (name == "errorlist")
1829 return createGuiErrorList(*this);
1831 return createGuiERT(*this);
1832 if (name == "external")
1833 return createGuiExternal(*this);
1835 return createGuiShowFile(*this);
1836 if (name == "findreplace")
1837 return createGuiSearch(*this);
1838 if (name == "float")
1839 return createGuiFloat(*this);
1840 if (name == "graphics")
1841 return createGuiGraphics(*this);
1842 if (name == "include")
1843 return createGuiInclude(*this);
1844 if (name == "index")
1845 return createGuiIndex(*this);
1846 if (name == "nomenclature")
1847 return createGuiNomenclature(*this);
1848 if (name == "label")
1849 return createGuiLabel(*this);
1851 return createGuiLog(*this);
1852 if (name == "view-source")
1853 return createGuiViewSource(*this);
1854 if (name == "view-console")
1855 return createGuiProgress(*this);
1856 if (name == "mathdelimiter")
1857 return createGuiDelimiter(*this);
1858 if (name == "mathmatrix")
1859 return createGuiMathMatrix(*this);
1861 return createGuiNote(*this);
1862 if (name == "paragraph")
1863 return createGuiParagraph(*this);
1864 if (name == "prefs")
1865 return createGuiPreferences(*this);
1866 if (name == "print")
1867 return createGuiPrint(*this);
1869 return createGuiRef(*this);
1870 if (name == "sendto")
1871 return createGuiSendTo(*this);
1872 if (name == "spellchecker")
1873 return createGuiSpellchecker(*this);
1874 if (name == "tabular")
1875 return createGuiTabular(*this);
1876 if (name == "tabularcreate")
1877 return createGuiTabularCreate(*this);
1878 if (name == "texinfo")
1879 return createGuiTexInfo(*this);
1880 #ifdef HAVE_LIBAIKSAURUS
1881 if (name == "thesaurus")
1882 return createGuiThesaurus(*this);
1885 return createGuiToc(*this);
1887 return createGuiHyperlink(*this);
1888 if (name == "vspace")
1889 return createGuiVSpace(*this);
1891 return createGuiWrap(*this);
1892 if (name == "listings")
1893 return createGuiListings(*this);
1899 } // namespace frontend
1902 #include "GuiView_moc.cpp"