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 "GuiToolbar.h"
24 #include "GuiToolbars.h"
27 #include "qt_helpers.h"
29 #include "frontends/alert.h"
31 #include "buffer_funcs.h"
33 #include "BufferList.h"
34 #include "BufferParams.h"
35 #include "BufferView.h"
37 #include "support/debug.h"
38 #include "ErrorList.h"
39 #include "FuncRequest.h"
40 #include "support/gettext.h"
48 #include "MenuBackend.h"
49 #include "Paragraph.h"
50 #include "TextClass.h"
52 #include "ToolbarBackend.h"
55 #include "support/FileFilterList.h"
56 #include "support/FileName.h"
57 #include "support/filetools.h"
58 #include "support/lstrings.h"
59 #include "support/os.h"
60 #include "support/Package.h"
61 #include "support/Timeout.h"
64 #include <QApplication>
65 #include <QCloseEvent>
67 #include <QDesktopWidget>
68 #include <QDragEnterEvent>
75 #include <QPushButton>
79 #include <QStackedWidget>
85 #include <boost/assert.hpp>
86 #include <boost/bind.hpp>
88 #ifdef HAVE_SYS_TIME_H
89 # include <sys/time.h>
96 using namespace lyx::support;
100 extern bool quitting;
106 class BackgroundWidget : public QWidget
111 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
112 /// The text to be written on top of the pixmap
113 QString const text = lyx_version ? lyx_version : qt_("unknown version");
114 splash_ = QPixmap(":/images/banner.png");
116 QPainter pain(&splash_);
117 pain.setPen(QColor(255, 255, 0));
119 // The font used to display the version info
120 font.setStyleHint(QFont::SansSerif);
121 font.setWeight(QFont::Bold);
122 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
124 pain.drawText(260, 270, text);
127 void paintEvent(QPaintEvent *)
129 int x = (width() - splash_.width()) / 2;
130 int y = (height() - splash_.height()) / 2;
132 pain.drawPixmap(x, y, splash_);
142 typedef boost::shared_ptr<Dialog> DialogPtr;
144 struct GuiView::GuiViewPrivate
147 : current_work_area_(0), layout_(0),
148 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
150 // hardcode here the platform specific icon size
151 smallIconSize = 14; // scaling problems
152 normalIconSize = 20; // ok, default
153 bigIconSize = 26; // better for some math icons
155 splitter_ = new QSplitter;
156 bg_widget_ = new BackgroundWidget;
157 stack_widget_ = new QStackedWidget;
158 stack_widget_->addWidget(bg_widget_);
159 stack_widget_->addWidget(splitter_);
167 delete stack_widget_;
171 QMenu * toolBarPopup(GuiView * parent)
173 // FIXME: translation
174 QMenu * menu = new QMenu(parent);
175 QActionGroup * iconSizeGroup = new QActionGroup(parent);
177 QAction * smallIcons = new QAction(iconSizeGroup);
178 smallIcons->setText(qt_("Small-sized icons"));
179 smallIcons->setCheckable(true);
180 QObject::connect(smallIcons, SIGNAL(triggered()),
181 parent, SLOT(smallSizedIcons()));
182 menu->addAction(smallIcons);
184 QAction * normalIcons = new QAction(iconSizeGroup);
185 normalIcons->setText(qt_("Normal-sized icons"));
186 normalIcons->setCheckable(true);
187 QObject::connect(normalIcons, SIGNAL(triggered()),
188 parent, SLOT(normalSizedIcons()));
189 menu->addAction(normalIcons);
191 QAction * bigIcons = new QAction(iconSizeGroup);
192 bigIcons->setText(qt_("Big-sized icons"));
193 bigIcons->setCheckable(true);
194 QObject::connect(bigIcons, SIGNAL(triggered()),
195 parent, SLOT(bigSizedIcons()));
196 menu->addAction(bigIcons);
198 unsigned int cur = parent->iconSize().width();
199 if ( cur == parent->d.smallIconSize)
200 smallIcons->setChecked(true);
201 else if (cur == parent->d.normalIconSize)
202 normalIcons->setChecked(true);
203 else if (cur == parent->d.bigIconSize)
204 bigIcons->setChecked(true);
211 stack_widget_->setCurrentWidget(bg_widget_);
212 bg_widget_->setUpdatesEnabled(true);
215 TabWorkArea * tabWorkArea(int i)
217 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
220 TabWorkArea * currentTabWorkArea()
222 if (splitter_->count() == 1)
223 // The first TabWorkArea is always the first one, if any.
224 return tabWorkArea(0);
226 TabWorkArea * tab_widget = 0;
227 for (int i = 0; i != splitter_->count(); ++i) {
228 QWidget * w = splitter_->widget(i);
231 tab_widget = dynamic_cast<TabWorkArea *>(w);
240 GuiWorkArea * current_work_area_;
241 QSplitter * splitter_;
242 QStackedWidget * stack_widget_;
243 BackgroundWidget * bg_widget_;
245 GuiToolbars * toolbars_;
246 /// The main layout box.
248 * \warning Don't Delete! The layout box is actually owned by
249 * whichever toolbar contains it. All the GuiView class needs is a
250 * means of accessing it.
252 * FIXME: replace that with a proper model so that we are not limited
253 * to only one dialog.
255 GuiLayoutBox * layout_;
258 map<string, Inset *> open_insets_;
261 map<string, DialogPtr> dialogs_;
263 unsigned int smallIconSize;
264 unsigned int normalIconSize;
265 unsigned int bigIconSize;
267 QTimer statusbar_timer_;
268 /// are we quitting by the menu?
269 bool quitting_by_menu_;
270 /// auto-saving of buffers
271 Timeout autosave_timeout_;
272 /// flag against a race condition due to multiclicks, see bug #1119
277 GuiView::GuiView(int id)
278 : d(*new GuiViewPrivate), id_(id)
280 // GuiToolbars *must* be initialised before the menu bar.
281 d.toolbars_ = new GuiToolbars(*this);
283 // Fill up the menu bar.
284 guiApp->menus().fillMenuBar(this);
286 setCentralWidget(d.stack_widget_);
288 // Start autosave timer
289 if (lyxrc.autosave) {
290 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
291 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
292 d.autosave_timeout_.start();
294 connect(&d.statusbar_timer_, SIGNAL(timeout()),
295 this, SLOT(clearMessage()));
297 // Qt bug? signal lastWindowClosed does not work
298 setAttribute(Qt::WA_QuitOnClose, false);
299 setAttribute(Qt::WA_DeleteOnClose, true);
301 // assign an icon to main form. We do not do it under Qt/Mac,
302 // since the icon is provided in the application bundle.
303 setWindowIcon(QPixmap(":/images/lyx.png"));
307 setAcceptDrops(true);
309 statusBar()->setSizeGripEnabled(true);
311 // Forbid too small unresizable window because it can happen
312 // with some window manager under X11.
313 setMinimumSize(300, 200);
315 if (!lyxrc.allow_geometry_session)
316 // No session handling, default to a sane size.
317 setGeometry(50, 50, 690, 510);
319 // Now take care of session management.
321 QString const key = "view-" + QString::number(id_);
323 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
324 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
328 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
329 setGeometry(50, 50, 690, 510);
331 setIconSize(settings.value(key + "/icon_size").toSize());
341 void GuiView::close()
343 d.quitting_by_menu_ = true;
344 d.current_work_area_ = 0;
345 for (int i = 0; i != d.splitter_->count(); ++i) {
346 TabWorkArea * twa = d.tabWorkArea(i);
350 QMainWindow::close();
351 d.quitting_by_menu_ = false;
355 void GuiView::setFocus()
357 if (d.current_work_area_)
358 d.current_work_area_->setFocus();
364 QMenu * GuiView::createPopupMenu()
366 return d.toolBarPopup(this);
370 void GuiView::showEvent(QShowEvent * e)
372 LYXERR(Debug::GUI, "Passed Geometry "
373 << size().height() << "x" << size().width()
374 << "+" << pos().x() << "+" << pos().y());
376 if (d.splitter_->count() == 0)
377 // No work area, switch to the background widget.
380 QMainWindow::showEvent(e);
384 void GuiView::closeEvent(QCloseEvent * close_event)
386 // we may have been called through the close window button
387 // which bypasses the LFUN machinery.
388 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
389 if (!quitWriteAll()) {
390 close_event->ignore();
395 // Make sure that no LFUN use this close to be closed View.
396 theLyXFunc().setLyXView(0);
397 // Make sure the timer time out will not trigger a statusbar update.
398 d.statusbar_timer_.stop();
400 if (lyxrc.allow_geometry_session) {
402 QString const key = "view-" + QString::number(id_);
404 settings.setValue(key + "/pos", pos());
405 settings.setValue(key + "/size", size());
407 settings.setValue(key + "/geometry", saveGeometry());
409 settings.setValue(key + "/icon_size", iconSize());
410 d.toolbars_->saveToolbarInfo();
411 // Now take care of all other dialogs:
412 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
413 for (; it!= d.dialogs_.end(); ++it)
414 it->second->saveSession();
417 guiApp->unregisterView(id_);
418 if (guiApp->viewCount() > 0) {
419 // Just close the window and do nothing else if this is not the
421 close_event->accept();
427 // this is the place where we leave the frontend.
428 // it is the only point at which we start quitting.
429 close_event->accept();
430 // quit the event loop
435 void GuiView::dragEnterEvent(QDragEnterEvent * event)
437 if (event->mimeData()->hasUrls())
439 /// \todo Ask lyx-devel is this is enough:
440 /// if (event->mimeData()->hasFormat("text/plain"))
441 /// event->acceptProposedAction();
445 void GuiView::dropEvent(QDropEvent* event)
447 QList<QUrl> files = event->mimeData()->urls();
451 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
452 for (int i = 0; i != files.size(); ++i) {
453 string const file = os::internal_path(fromqstr(
454 files.at(i).toLocalFile()));
456 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
461 void GuiView::message(docstring const & str)
463 statusBar()->showMessage(toqstr(str));
464 d.statusbar_timer_.stop();
465 d.statusbar_timer_.start(3000);
469 void GuiView::smallSizedIcons()
471 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
475 void GuiView::normalSizedIcons()
477 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
481 void GuiView::bigSizedIcons()
483 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
487 void GuiView::clearMessage()
491 theLyXFunc().setLyXView(this);
492 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
493 d.statusbar_timer_.stop();
497 void GuiView::updateWindowTitle(GuiWorkArea * wa)
499 if (wa != d.current_work_area_)
501 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
502 setWindowIconText(wa->windowIconText());
506 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
509 disconnectBufferView();
510 connectBufferView(wa->bufferView());
511 connectBuffer(wa->bufferView().buffer());
512 d.current_work_area_ = wa;
513 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
514 this, SLOT(updateWindowTitle(GuiWorkArea *)));
515 updateWindowTitle(wa);
518 // Buffer-dependent dialogs should be updated or
519 // hidden. This should go here because some dialogs (eg ToC)
520 // require bv_->text.
521 updateBufferDependent(true);
528 void GuiView::updateStatusBar()
530 // let the user see the explicit message
531 if (d.statusbar_timer_.isActive())
534 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
538 bool GuiView::hasFocus() const
540 return qApp->activeWindow() == this;
544 bool GuiView::event(QEvent * e)
548 // Useful debug code:
549 //case QEvent::ActivationChange:
550 //case QEvent::WindowDeactivate:
551 //case QEvent::Paint:
552 //case QEvent::Enter:
553 //case QEvent::Leave:
554 //case QEvent::HoverEnter:
555 //case QEvent::HoverLeave:
556 //case QEvent::HoverMove:
557 //case QEvent::StatusTip:
558 //case QEvent::DragEnter:
559 //case QEvent::DragLeave:
563 case QEvent::WindowActivate: {
564 guiApp->setCurrentView(*this);
565 if (d.current_work_area_) {
566 BufferView & bv = d.current_work_area_->bufferView();
567 connectBufferView(bv);
568 connectBuffer(bv.buffer());
569 // The document structure, name and dialogs might have
570 // changed in another view.
571 updateBufferDependent(true);
573 setWindowTitle(qt_("LyX"));
574 setWindowIconText(qt_("LyX"));
576 return QMainWindow::event(e);
578 case QEvent::ShortcutOverride: {
579 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
580 if (!d.current_work_area_) {
581 theLyXFunc().setLyXView(this);
583 setKeySymbol(&sym, ke);
584 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
588 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
590 setKeySymbol(&sym, ke);
591 d.current_work_area_->processKeySym(sym, NoModifier);
597 return QMainWindow::event(e);
602 bool GuiView::focusNextPrevChild(bool /*next*/)
609 void GuiView::setBusy(bool yes)
611 if (d.current_work_area_) {
612 d.current_work_area_->setUpdatesEnabled(!yes);
614 d.current_work_area_->stopBlinkingCursor();
616 d.current_work_area_->startBlinkingCursor();
620 QApplication::setOverrideCursor(Qt::WaitCursor);
622 QApplication::restoreOverrideCursor();
626 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
628 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
630 if (tbinfo.flags & ToolbarInfo::TOP) {
632 addToolBarBreak(Qt::TopToolBarArea);
633 addToolBar(Qt::TopToolBarArea, toolBar);
636 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
637 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
638 #if (QT_VERSION >= 0x040202)
640 addToolBarBreak(Qt::BottomToolBarArea);
642 addToolBar(Qt::BottomToolBarArea, toolBar);
645 if (tbinfo.flags & ToolbarInfo::LEFT) {
646 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
647 #if (QT_VERSION >= 0x040202)
649 addToolBarBreak(Qt::LeftToolBarArea);
651 addToolBar(Qt::LeftToolBarArea, toolBar);
654 if (tbinfo.flags & ToolbarInfo::RIGHT) {
655 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
656 #if (QT_VERSION >= 0x040202)
658 addToolBarBreak(Qt::RightToolBarArea);
660 addToolBar(Qt::RightToolBarArea, toolBar);
663 // The following does not work so I cannot restore to exact toolbar location
665 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
666 toolBar->move(tbinfo.posx, tbinfo.posy);
673 GuiWorkArea * GuiView::workArea(Buffer & buffer)
675 for (int i = 0; i != d.splitter_->count(); ++i) {
676 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
684 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
687 // Automatically create a TabWorkArea if there are none yet.
688 if (!d.splitter_->count())
691 TabWorkArea * tab_widget = d.currentTabWorkArea();
692 return tab_widget->addWorkArea(buffer, *this);
696 void GuiView::addTabWorkArea()
698 TabWorkArea * twa = new TabWorkArea;
699 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
700 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
701 d.splitter_->addWidget(twa);
702 d.stack_widget_->setCurrentWidget(d.splitter_);
706 GuiWorkArea const * GuiView::currentWorkArea() const
708 return d.current_work_area_;
712 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
716 // Changing work area can result from opening a file so
717 // update the toc in any case.
720 d.current_work_area_ = wa;
721 for (int i = 0; i != d.splitter_->count(); ++i) {
722 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
728 void GuiView::removeWorkArea(GuiWorkArea * wa)
731 if (wa == d.current_work_area_) {
733 disconnectBufferView();
734 hideBufferDependent();
735 d.current_work_area_ = 0;
738 for (int i = 0; i != d.splitter_->count(); ++i) {
739 TabWorkArea * twa = d.tabWorkArea(i);
740 if (!twa->removeWorkArea(wa))
741 // Not found in this tab group.
744 // We found and removed the GuiWorkArea.
746 // No more WorkAreas in this tab group, so delete it.
751 if (d.current_work_area_)
752 // This means that we are not closing the current GuiWorkArea;
755 // Switch to the next GuiWorkArea in the found TabWorkArea.
756 d.current_work_area_ = twa->currentWorkArea();
760 if (d.splitter_->count() == 0)
761 // No more work area, switch to the background widget.
766 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
772 void GuiView::updateLayoutList()
775 d.layout_->updateContents(false);
779 void GuiView::updateToolbars()
781 if (d.current_work_area_) {
783 d.current_work_area_->bufferView().cursor().inMathed();
785 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
787 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
788 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
789 bool const mathmacrotemplate =
790 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
792 d.toolbars_->update(math, table, review, mathmacrotemplate);
794 d.toolbars_->update(false, false, false, false);
796 // update read-only status of open dialogs.
801 Buffer * GuiView::buffer()
803 if (d.current_work_area_)
804 return &d.current_work_area_->bufferView().buffer();
809 Buffer const * GuiView::buffer() const
811 if (d.current_work_area_)
812 return &d.current_work_area_->bufferView().buffer();
817 void GuiView::setBuffer(Buffer * newBuffer)
819 BOOST_ASSERT(newBuffer);
822 GuiWorkArea * wa = workArea(*newBuffer);
824 updateLabels(*newBuffer->masterBuffer());
825 wa = addWorkArea(*newBuffer);
827 //Disconnect the old buffer...there's no new one.
830 connectBuffer(*newBuffer);
831 connectBufferView(wa->bufferView());
832 setCurrentWorkArea(wa);
838 void GuiView::connectBuffer(Buffer & buf)
840 buf.setGuiDelegate(this);
844 void GuiView::disconnectBuffer()
846 if (d.current_work_area_)
847 d.current_work_area_->bufferView().setGuiDelegate(0);
851 void GuiView::connectBufferView(BufferView & bv)
853 bv.setGuiDelegate(this);
857 void GuiView::disconnectBufferView()
859 if (d.current_work_area_)
860 d.current_work_area_->bufferView().setGuiDelegate(0);
864 void GuiView::errors(string const & error_type)
866 ErrorList & el = buffer()->errorList(error_type);
868 showDialog("errorlist", error_type);
872 void GuiView::updateDialog(string const & name, string const & data)
874 if (!isDialogVisible(name))
877 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
878 if (it == d.dialogs_.end())
881 Dialog * const dialog = it->second.get();
882 if (dialog->isVisibleView())
883 dialog->updateData(data);
887 BufferView * GuiView::view()
889 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
893 void GuiView::updateToc()
895 updateDialog("toc", "");
899 void GuiView::updateEmbeddedFiles()
901 updateDialog("embedding", "");
905 void GuiView::autoSave()
907 LYXERR(Debug::INFO, "Running autoSave()");
910 view()->buffer().autoSave();
914 void GuiView::resetAutosaveTimers()
917 d.autosave_timeout_.restart();
921 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
925 Buffer * buf = buffer();
927 /* In LyX/Mac, when a dialog is open, the menus of the
928 application can still be accessed without giving focus to
929 the main window. In this case, we want to disable the menu
930 entries that are buffer-related.
932 Note that this code is not perfect, as bug 1941 attests:
933 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
935 if (cmd.origin == FuncRequest::MENU && !hasFocus())
939 case LFUN_BUFFER_WRITE:
940 enable = buf && (buf->isUnnamed() || !buf->isClean());
943 case LFUN_BUFFER_WRITE_AS:
947 case LFUN_TOOLBAR_TOGGLE:
948 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
951 case LFUN_DIALOG_TOGGLE:
952 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
953 // fall through to set "enable"
954 case LFUN_DIALOG_SHOW: {
955 string const name = cmd.getArg(0);
957 enable = name == "aboutlyx"
958 || name == "file" //FIXME: should be removed.
960 || name == "texinfo";
961 else if (name == "print")
962 enable = buf->isExportable("dvi")
963 && lyxrc.print_command != "none";
964 else if (name == "character") {
968 InsetCode ic = view()->cursor().inset().lyxCode();
969 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
972 else if (name == "latexlog")
973 enable = FileName(buf->logName()).isReadableFile();
974 else if (name == "spellchecker")
975 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
976 enable = !buf->isReadonly();
980 else if (name == "vclog")
981 enable = buf->lyxvc().inUse();
985 case LFUN_DIALOG_UPDATE: {
986 string const name = cmd.getArg(0);
988 enable = name == "prefs";
992 case LFUN_INSET_APPLY: {
997 string const name = cmd.getArg(0);
998 Inset * inset = getOpenInset(name);
1000 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1002 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1003 // Every inset is supposed to handle this
1004 BOOST_ASSERT(false);
1008 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1009 flag |= getStatus(fr);
1011 enable = flag.enabled();
1023 flag.enabled(false);
1029 static FileName selectTemplateFile()
1031 FileDialog dlg(_("Select template file"));
1032 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1033 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1035 FileDialog::Result result =
1036 dlg.open(from_utf8(lyxrc.template_path),
1037 FileFilterList(_("LyX Documents (*.lyx)")),
1040 if (result.first == FileDialog::Later)
1042 if (result.second.empty())
1044 return FileName(to_utf8(result.second));
1048 void GuiView::newDocument(string const & filename, bool from_template)
1050 FileName initpath(lyxrc.document_path);
1051 Buffer * buf = buffer();
1053 FileName const trypath(buf->filePath());
1054 // If directory is writeable, use this as default.
1055 if (trypath.isDirWritable())
1059 string templatefile = from_template ?
1060 selectTemplateFile().absFilename() : string();
1062 if (filename.empty())
1063 b = newUnnamedFile(templatefile, initpath);
1065 b = newFile(filename, templatefile, true);
1069 // Ensure the cursor is correctly positionned on screen.
1070 view()->showCursor();
1074 void GuiView::insertLyXFile(docstring const & fname)
1076 BufferView * bv = view();
1081 FileName filename(to_utf8(fname));
1083 if (!filename.empty()) {
1084 bv->insertLyXFile(filename);
1088 // Launch a file browser
1090 string initpath = lyxrc.document_path;
1091 string const trypath = bv->buffer().filePath();
1092 // If directory is writeable, use this as default.
1093 if (FileName(trypath).isDirWritable())
1097 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1098 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1099 dlg.setButton2(_("Examples|#E#e"),
1100 from_utf8(addPath(package().system_support().absFilename(),
1103 FileDialog::Result result =
1104 dlg.open(from_utf8(initpath),
1105 FileFilterList(_("LyX Documents (*.lyx)")),
1108 if (result.first == FileDialog::Later)
1112 filename.set(to_utf8(result.second));
1114 // check selected filename
1115 if (filename.empty()) {
1116 // emit message signal.
1117 message(_("Canceled."));
1121 bv->insertLyXFile(filename);
1125 void GuiView::insertPlaintextFile(docstring const & fname,
1128 BufferView * bv = view();
1133 FileName filename(to_utf8(fname));
1135 if (!filename.empty()) {
1136 bv->insertPlaintextFile(filename, asParagraph);
1140 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1141 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1143 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1144 FileFilterList(), docstring());
1146 if (result.first == FileDialog::Later)
1150 filename.set(to_utf8(result.second));
1152 // check selected filename
1153 if (filename.empty()) {
1154 // emit message signal.
1155 message(_("Canceled."));
1159 bv->insertPlaintextFile(filename, asParagraph);
1163 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1165 FileName fname = b.fileName();
1166 FileName const oldname = fname;
1168 if (!newname.empty()) {
1170 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1172 // Switch to this Buffer.
1175 /// No argument? Ask user through dialog.
1177 FileDialog dlg(_("Choose a filename to save document as"),
1178 LFUN_BUFFER_WRITE_AS);
1179 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1180 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1182 if (!isLyXFilename(fname.absFilename()))
1183 fname.changeExtension(".lyx");
1185 FileFilterList const filter(_("LyX Documents (*.lyx)"));
1187 FileDialog::Result result =
1188 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1190 from_utf8(fname.onlyFileName()));
1192 if (result.first == FileDialog::Later)
1195 fname.set(to_utf8(result.second));
1200 if (!isLyXFilename(fname.absFilename()))
1201 fname.changeExtension(".lyx");
1204 if (FileName(fname).exists()) {
1205 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1206 docstring text = bformat(_("The document %1$s already "
1207 "exists.\n\nDo you want to "
1208 "overwrite that document?"),
1210 int const ret = Alert::prompt(_("Overwrite document?"),
1211 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1214 case 1: return renameBuffer(b, docstring());
1215 case 2: return false;
1219 // Ok, change the name of the buffer
1220 b.setFileName(fname.absFilename());
1222 bool unnamed = b.isUnnamed();
1223 b.setUnnamed(false);
1224 b.saveCheckSum(fname);
1226 if (!saveBuffer(b)) {
1227 b.setFileName(oldname.absFilename());
1228 b.setUnnamed(unnamed);
1229 b.saveCheckSum(oldname);
1237 bool GuiView::saveBuffer(Buffer & b)
1240 return renameBuffer(b, docstring());
1243 LyX::ref().session().lastFiles().add(b.fileName());
1247 // Switch to this Buffer.
1250 // FIXME: we don't tell the user *WHY* the save failed !!
1251 docstring const file = makeDisplayPath(b.absFileName(), 30);
1252 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1253 "Do you want to rename the document and "
1254 "try again?"), file);
1255 int const ret = Alert::prompt(_("Rename and save?"),
1256 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1259 if (!renameBuffer(b, docstring()))
1268 return saveBuffer(b);
1272 bool GuiView::closeBuffer()
1274 Buffer * buf = buffer();
1275 return buf && closeBuffer(*buf);
1279 bool GuiView::closeBuffer(Buffer & buf)
1281 if (buf.isClean() || buf.paragraphs().empty()) {
1282 theBufferList().release(&buf);
1285 // Switch to this Buffer.
1290 if (buf.isUnnamed())
1291 file = from_utf8(buf.fileName().onlyFileName());
1293 file = buf.fileName().displayName(30);
1295 docstring const text = bformat(_("The document %1$s has unsaved changes."
1296 "\n\nDo you want to save the document or discard the changes?"), file);
1297 int const ret = Alert::prompt(_("Save changed document?"),
1298 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1302 if (!saveBuffer(buf))
1306 // if we crash after this we could
1307 // have no autosave file but I guess
1308 // this is really improbable (Jug)
1309 removeAutosaveFile(buf.absFileName());
1315 // save file names to .lyx/session
1316 // if master/slave are both open, do not save slave since it
1317 // will be automatically loaded when the master is loaded
1318 if (buf.masterBuffer() == &buf)
1319 LyX::ref().session().lastOpened().add(buf.fileName());
1321 theBufferList().release(&buf);
1326 bool GuiView::quitWriteAll()
1328 while (!theBufferList().empty()) {
1329 Buffer * b = theBufferList().first();
1330 if (!closeBuffer(*b))
1337 bool GuiView::dispatch(FuncRequest const & cmd)
1339 BufferView * bv = view();
1340 // By default we won't need any update.
1342 bv->cursor().updateFlags(Update::None);
1344 switch(cmd.action) {
1345 case LFUN_BUFFER_SWITCH:
1346 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1349 case LFUN_BUFFER_NEXT:
1350 setBuffer(theBufferList().next(buffer()));
1353 case LFUN_BUFFER_PREVIOUS:
1354 setBuffer(theBufferList().previous(buffer()));
1357 case LFUN_COMMAND_EXECUTE: {
1358 bool const show_it = cmd.argument() != "off";
1359 d.toolbars_->showCommandBuffer(show_it);
1362 case LFUN_DROP_LAYOUTS_CHOICE:
1364 d.layout_->showPopup();
1367 case LFUN_MENU_OPEN:
1368 guiApp->menus().openByName(toqstr(cmd.argument()));
1371 case LFUN_FILE_INSERT:
1372 insertLyXFile(cmd.argument());
1374 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1375 insertPlaintextFile(cmd.argument(), true);
1378 case LFUN_FILE_INSERT_PLAINTEXT:
1379 insertPlaintextFile(cmd.argument(), false);
1382 case LFUN_BUFFER_WRITE:
1384 saveBuffer(bv->buffer());
1387 case LFUN_BUFFER_WRITE_AS:
1389 renameBuffer(bv->buffer(), cmd.argument());
1392 case LFUN_BUFFER_WRITE_ALL: {
1393 Buffer * first = theBufferList().first();
1396 message(_("Saving all documents..."));
1397 // We cannot use a for loop as the buffer list cycles.
1403 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1404 b = theBufferList().next(b);
1405 } while (b != first);
1406 message(_("All documents saved."));
1410 case LFUN_TOOLBAR_TOGGLE: {
1411 string const name = cmd.getArg(0);
1412 bool const allowauto = cmd.getArg(1) == "allowauto";
1413 // it is possible to get current toolbar status like this,...
1414 // but I decide to obey the order of ToolbarBackend::flags
1415 // and disregard real toolbar status.
1416 // toolbars_->saveToolbarInfo();
1418 // toggle state on/off/auto
1419 d.toolbars_->toggleToolbarState(name, allowauto);
1423 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1425 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1429 if (tbi->flags & ToolbarInfo::ON)
1431 else if (tbi->flags & ToolbarInfo::OFF)
1433 else if (tbi->flags & ToolbarInfo::AUTO)
1436 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1437 _(tbi->gui_name), state));
1441 case LFUN_DIALOG_UPDATE: {
1442 string const name = to_utf8(cmd.argument());
1443 // Can only update a dialog connected to an existing inset
1444 Inset * inset = getOpenInset(name);
1446 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1447 inset->dispatch(view()->cursor(), fr);
1448 } else if (name == "paragraph") {
1449 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1450 } else if (name == "prefs") {
1451 updateDialog(name, string());
1456 case LFUN_DIALOG_TOGGLE: {
1457 if (isDialogVisible(cmd.getArg(0)))
1458 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1460 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1464 case LFUN_DIALOG_DISCONNECT_INSET:
1465 disconnectDialog(to_utf8(cmd.argument()));
1468 case LFUN_DIALOG_HIDE: {
1471 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1475 case LFUN_DIALOG_SHOW: {
1476 string const name = cmd.getArg(0);
1477 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1479 if (name == "character") {
1480 data = freefont2string();
1482 showDialog("character", data);
1483 } else if (name == "latexlog") {
1484 Buffer::LogType type;
1485 string const logfile = buffer()->logName(&type);
1487 case Buffer::latexlog:
1490 case Buffer::buildlog:
1494 data += Lexer::quoteString(logfile);
1495 showDialog("log", data);
1496 } else if (name == "vclog") {
1497 string const data = "vc " +
1498 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1499 showDialog("log", data);
1501 showDialog(name, data);
1505 case LFUN_INSET_APPLY: {
1506 string const name = cmd.getArg(0);
1507 Inset * inset = getOpenInset(name);
1509 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1510 inset->dispatch(view()->cursor(), fr);
1512 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1526 Buffer const * GuiView::updateInset(Inset const * inset)
1528 if (!d.current_work_area_)
1532 d.current_work_area_->scheduleRedraw();
1534 return &d.current_work_area_->bufferView().buffer();
1538 void GuiView::restartCursor()
1540 /* When we move around, or type, it's nice to be able to see
1541 * the cursor immediately after the keypress.
1543 if (d.current_work_area_)
1544 d.current_work_area_->startBlinkingCursor();
1546 // Take this occasion to update the toobars and layout list.
1553 // This list should be kept in sync with the list of insets in
1554 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1555 // dialog should have the same name as the inset.
1557 char const * const dialognames[] = {
1558 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1559 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1560 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1561 "mathdelimiter", "mathmatrix", "note", "paragraph",
1562 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1564 #ifdef HAVE_LIBAIKSAURUS
1568 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1570 char const * const * const end_dialognames =
1571 dialognames + (sizeof(dialognames) / sizeof(char *));
1575 cmpCStr(char const * name) : name_(name) {}
1576 bool operator()(char const * other) {
1577 return strcmp(other, name_) == 0;
1584 bool isValidName(string const & name)
1586 return find_if(dialognames, end_dialognames,
1587 cmpCStr(name.c_str())) != end_dialognames;
1593 void GuiView::resetDialogs()
1595 // Make sure that no LFUN uses any LyXView.
1596 theLyXFunc().setLyXView(0);
1597 d.toolbars_->init();
1598 guiApp->menus().fillMenuBar(this);
1600 d.layout_->updateContents(true);
1601 // Now update controls with current buffer.
1602 theLyXFunc().setLyXView(this);
1607 Dialog * GuiView::find_or_build(string const & name)
1609 if (!isValidName(name))
1612 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1614 if (it != d.dialogs_.end())
1615 return it->second.get();
1617 Dialog * dialog = build(name);
1618 d.dialogs_[name].reset(dialog);
1619 if (lyxrc.allow_geometry_session)
1620 dialog->restoreSession();
1625 void GuiView::showDialog(string const & name, string const & data,
1632 Dialog * dialog = find_or_build(name);
1634 dialog->showData(data);
1636 d.open_insets_[name] = inset;
1642 bool GuiView::isDialogVisible(string const & name) const
1644 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1645 if (it == d.dialogs_.end())
1647 return it->second.get()->isVisibleView();
1651 void GuiView::hideDialog(string const & name, Inset * inset)
1653 // Don't send the signal if we are quitting, because on MSVC it is
1654 // destructed before the cut stack in CutAndPaste.cpp, and this method
1655 // is called from some inset destructor if the cut stack is not empty
1660 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1661 if (it == d.dialogs_.end())
1664 if (inset && inset != getOpenInset(name))
1667 Dialog * const dialog = it->second.get();
1668 if (dialog->isVisibleView())
1670 d.open_insets_[name] = 0;
1674 void GuiView::disconnectDialog(string const & name)
1676 if (!isValidName(name))
1679 if (d.open_insets_.find(name) != d.open_insets_.end())
1680 d.open_insets_[name] = 0;
1684 Inset * GuiView::getOpenInset(string const & name) const
1686 if (!isValidName(name))
1689 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1690 return it == d.open_insets_.end() ? 0 : it->second;
1694 void GuiView::hideAll() const
1696 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1697 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1699 for(; it != end; ++it)
1700 it->second->hideView();
1704 void GuiView::hideBufferDependent() const
1706 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1707 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1709 for(; it != end; ++it) {
1710 Dialog * dialog = it->second.get();
1711 if (dialog->isBufferDependent())
1717 void GuiView::updateBufferDependent(bool switched) const
1719 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1720 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1722 for(; it != end; ++it) {
1723 Dialog * dialog = it->second.get();
1724 if (!dialog->isVisibleView())
1726 if (switched && dialog->isBufferDependent()) {
1727 if (dialog->initialiseParams(""))
1728 dialog->updateView();
1732 // A bit clunky, but the dialog will request
1733 // that the kernel provides it with the necessary
1735 dialog->updateDialog();
1741 void GuiView::checkStatus()
1743 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1744 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1746 for(; it != end; ++it) {
1747 Dialog * const dialog = it->second.get();
1748 if (dialog && dialog->isVisibleView())
1749 dialog->checkStatus();
1755 // will be replaced by a proper factory...
1756 Dialog * createGuiAbout(GuiView & lv);
1757 Dialog * createGuiBibitem(GuiView & lv);
1758 Dialog * createGuiBibtex(GuiView & lv);
1759 Dialog * createGuiBox(GuiView & lv);
1760 Dialog * createGuiBranch(GuiView & lv);
1761 Dialog * createGuiChanges(GuiView & lv);
1762 Dialog * createGuiCharacter(GuiView & lv);
1763 Dialog * createGuiCitation(GuiView & lv);
1764 Dialog * createGuiDelimiter(GuiView & lv);
1765 Dialog * createGuiDocument(GuiView & lv);
1766 Dialog * createGuiErrorList(GuiView & lv);
1767 Dialog * createGuiERT(GuiView & lv);
1768 Dialog * createGuiExternal(GuiView & lv);
1769 Dialog * createGuiFloat(GuiView & lv);
1770 Dialog * createGuiGraphics(GuiView & lv);
1771 Dialog * createGuiInclude(GuiView & lv);
1772 Dialog * createGuiIndex(GuiView & lv);
1773 Dialog * createGuiLabel(GuiView & lv);
1774 Dialog * createGuiListings(GuiView & lv);
1775 Dialog * createGuiLog(GuiView & lv);
1776 Dialog * createGuiMathMatrix(GuiView & lv);
1777 Dialog * createGuiNomenclature(GuiView & lv);
1778 Dialog * createGuiNote(GuiView & lv);
1779 Dialog * createGuiParagraph(GuiView & lv);
1780 Dialog * createGuiPreferences(GuiView & lv);
1781 Dialog * createGuiPrint(GuiView & lv);
1782 Dialog * createGuiRef(GuiView & lv);
1783 Dialog * createGuiSearch(GuiView & lv);
1784 Dialog * createGuiSendTo(GuiView & lv);
1785 Dialog * createGuiShowFile(GuiView & lv);
1786 Dialog * createGuiSpellchecker(GuiView & lv);
1787 Dialog * createGuiTabularCreate(GuiView & lv);
1788 Dialog * createGuiTabular(GuiView & lv);
1789 Dialog * createGuiTexInfo(GuiView & lv);
1790 Dialog * createGuiToc(GuiView & lv);
1791 Dialog * createGuiThesaurus(GuiView & lv);
1792 Dialog * createGuiHyperlink(GuiView & lv);
1793 Dialog * createGuiVSpace(GuiView & lv);
1794 Dialog * createGuiViewSource(GuiView & lv);
1795 Dialog * createGuiWrap(GuiView & lv);
1798 Dialog * GuiView::build(string const & name)
1800 BOOST_ASSERT(isValidName(name));
1802 if (name == "aboutlyx")
1803 return createGuiAbout(*this);
1804 if (name == "bibitem")
1805 return createGuiBibitem(*this);
1806 if (name == "bibtex")
1807 return createGuiBibtex(*this);
1809 return createGuiBox(*this);
1810 if (name == "branch")
1811 return createGuiBranch(*this);
1812 if (name == "changes")
1813 return createGuiChanges(*this);
1814 if (name == "character")
1815 return createGuiCharacter(*this);
1816 if (name == "citation")
1817 return createGuiCitation(*this);
1818 if (name == "document")
1819 return createGuiDocument(*this);
1820 if (name == "errorlist")
1821 return createGuiErrorList(*this);
1823 return createGuiERT(*this);
1824 if (name == "external")
1825 return createGuiExternal(*this);
1827 return createGuiShowFile(*this);
1828 if (name == "findreplace")
1829 return createGuiSearch(*this);
1830 if (name == "float")
1831 return createGuiFloat(*this);
1832 if (name == "graphics")
1833 return createGuiGraphics(*this);
1834 if (name == "include")
1835 return createGuiInclude(*this);
1836 if (name == "index")
1837 return createGuiIndex(*this);
1838 if (name == "nomenclature")
1839 return createGuiNomenclature(*this);
1840 if (name == "label")
1841 return createGuiLabel(*this);
1843 return createGuiLog(*this);
1844 if (name == "view-source")
1845 return createGuiViewSource(*this);
1846 if (name == "mathdelimiter")
1847 return createGuiDelimiter(*this);
1848 if (name == "mathmatrix")
1849 return createGuiMathMatrix(*this);
1851 return createGuiNote(*this);
1852 if (name == "paragraph")
1853 return createGuiParagraph(*this);
1854 if (name == "prefs")
1855 return createGuiPreferences(*this);
1856 if (name == "print")
1857 return createGuiPrint(*this);
1859 return createGuiRef(*this);
1860 if (name == "sendto")
1861 return createGuiSendTo(*this);
1862 if (name == "spellchecker")
1863 return createGuiSpellchecker(*this);
1864 if (name == "tabular")
1865 return createGuiTabular(*this);
1866 if (name == "tabularcreate")
1867 return createGuiTabularCreate(*this);
1868 if (name == "texinfo")
1869 return createGuiTexInfo(*this);
1870 #ifdef HAVE_LIBAIKSAURUS
1871 if (name == "thesaurus")
1872 return createGuiThesaurus(*this);
1875 return createGuiToc(*this);
1877 return createGuiHyperlink(*this);
1878 if (name == "vspace")
1879 return createGuiVSpace(*this);
1881 return createGuiWrap(*this);
1882 if (name == "listings")
1883 return createGuiListings(*this);
1889 } // namespace frontend
1892 #include "GuiView_moc.cpp"