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"
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>
101 extern bool quitting;
105 using support::addPath;
106 using support::bformat;
107 using support::FileFilterList;
108 using support::FileName;
109 using support::makeAbsPath;
110 using support::makeDisplayPath;
111 using support::package;
112 using support::removeAutosaveFile;
117 class BackgroundWidget : public QWidget
122 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
123 /// The text to be written on top of the pixmap
124 QString const text = lyx_version ? lyx_version : qt_("unknown version");
125 splash_ = QPixmap(":/images/banner.png");
127 QPainter pain(&splash_);
128 pain.setPen(QColor(255, 255, 0));
130 // The font used to display the version info
131 font.setStyleHint(QFont::SansSerif);
132 font.setWeight(QFont::Bold);
133 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
135 pain.drawText(260, 270, text);
138 void paintEvent(QPaintEvent *)
140 int x = (width() - splash_.width()) / 2;
141 int y = (height() - splash_.height()) / 2;
143 pain.drawPixmap(x, y, splash_);
153 typedef boost::shared_ptr<Dialog> DialogPtr;
155 struct GuiView::GuiViewPrivate
158 : current_work_area_(0), layout_(0),
159 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
161 // hardcode here the platform specific icon size
162 smallIconSize = 14; // scaling problems
163 normalIconSize = 20; // ok, default
164 bigIconSize = 26; // better for some math icons
166 splitter_ = new QSplitter;
167 bg_widget_ = new BackgroundWidget;
168 stack_widget_ = new QStackedWidget;
169 stack_widget_->addWidget(bg_widget_);
170 stack_widget_->addWidget(splitter_);
178 delete stack_widget_;
183 QMenu * toolBarPopup(GuiView * parent)
185 // FIXME: translation
186 QMenu * menu = new QMenu(parent);
187 QActionGroup * iconSizeGroup = new QActionGroup(parent);
189 QAction * smallIcons = new QAction(iconSizeGroup);
190 smallIcons->setText(qt_("Small-sized icons"));
191 smallIcons->setCheckable(true);
192 QObject::connect(smallIcons, SIGNAL(triggered()),
193 parent, SLOT(smallSizedIcons()));
194 menu->addAction(smallIcons);
196 QAction * normalIcons = new QAction(iconSizeGroup);
197 normalIcons->setText(qt_("Normal-sized icons"));
198 normalIcons->setCheckable(true);
199 QObject::connect(normalIcons, SIGNAL(triggered()),
200 parent, SLOT(normalSizedIcons()));
201 menu->addAction(normalIcons);
203 QAction * bigIcons = new QAction(iconSizeGroup);
204 bigIcons->setText(qt_("Big-sized icons"));
205 bigIcons->setCheckable(true);
206 QObject::connect(bigIcons, SIGNAL(triggered()),
207 parent, SLOT(bigSizedIcons()));
208 menu->addAction(bigIcons);
210 unsigned int cur = parent->iconSize().width();
211 if ( cur == parent->d.smallIconSize)
212 smallIcons->setChecked(true);
213 else if (cur == parent->d.normalIconSize)
214 normalIcons->setChecked(true);
215 else if (cur == parent->d.bigIconSize)
216 bigIcons->setChecked(true);
223 stack_widget_->setCurrentWidget(bg_widget_);
224 bg_widget_->setUpdatesEnabled(true);
227 TabWorkArea * tabWorkArea(int i)
229 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
232 TabWorkArea * currentTabWorkArea()
234 if (splitter_->count() == 1)
235 // The first TabWorkArea is always the first one, if any.
236 return tabWorkArea(0);
238 TabWorkArea * tab_widget = 0;
239 for (int i = 0; i != splitter_->count(); ++i) {
240 QWidget * w = splitter_->widget(i);
243 tab_widget = dynamic_cast<TabWorkArea *>(w);
252 GuiWorkArea * current_work_area_;
253 QSplitter * splitter_;
254 QStackedWidget * stack_widget_;
255 BackgroundWidget * bg_widget_;
257 GuiMenubar * menubar_;
259 GuiToolbars * toolbars_;
260 /// The main layout box.
262 * \warning Don't Delete! The layout box is actually owned by
263 * whichever toolbar contains it. All the GuiView class needs is a
264 * means of accessing it.
266 * FIXME: replace that with a proper model so that we are not limited
267 * to only one dialog.
269 GuiLayoutBox * layout_;
272 std::map<std::string, Inset *> open_insets_;
275 std::map<std::string, DialogPtr> dialogs_;
277 unsigned int smallIconSize;
278 unsigned int normalIconSize;
279 unsigned int bigIconSize;
281 QTimer statusbar_timer_;
282 /// are we quitting by the menu?
283 bool quitting_by_menu_;
284 /// auto-saving of buffers
285 Timeout autosave_timeout_;
286 /// flag against a race condition due to multiclicks, see bug #1119
291 GuiView::GuiView(int id)
292 : d(*new GuiViewPrivate), id_(id)
294 // GuiToolbars *must* be initialised before GuiMenubar.
295 d.toolbars_ = new GuiToolbars(*this);
296 d.menubar_ = new GuiMenubar(this, menubackend);
298 setCentralWidget(d.stack_widget_);
300 // Start autosave timer
301 if (lyxrc.autosave) {
302 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
303 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
304 d.autosave_timeout_.start();
306 connect(&d.statusbar_timer_, SIGNAL(timeout()),
307 this, SLOT(clearMessage()));
309 // Qt bug? signal lastWindowClosed does not work
310 setAttribute(Qt::WA_QuitOnClose, false);
311 setAttribute(Qt::WA_DeleteOnClose, true);
313 // assign an icon to main form. We do not do it under Qt/Mac,
314 // since the icon is provided in the application bundle.
315 setWindowIcon(QPixmap(":/images/lyx.png"));
319 setAcceptDrops(true);
321 statusBar()->setSizeGripEnabled(true);
323 // Forbid too small unresizable window because it can happen
324 // with some window manager under X11.
325 setMinimumSize(300, 200);
327 if (!lyxrc.allow_geometry_session)
328 // No session handling, default to a sane size.
329 setGeometry(50, 50, 690, 510);
331 // Now take care of session management.
333 QString const key = "view-" + QString::number(id_);
335 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
336 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
340 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
341 setGeometry(50, 50, 690, 510);
343 setIconSize(settings.value(key + "/icon_size").toSize());
353 void GuiView::close()
355 d.quitting_by_menu_ = true;
356 d.current_work_area_ = 0;
357 for (int i = 0; i != d.splitter_->count(); ++i) {
358 TabWorkArea * twa = d.tabWorkArea(i);
362 QMainWindow::close();
363 d.quitting_by_menu_ = false;
367 void GuiView::setFocus()
369 if (d.current_work_area_)
370 d.current_work_area_->setFocus();
376 QMenu * GuiView::createPopupMenu()
378 return d.toolBarPopup(this);
382 void GuiView::showEvent(QShowEvent * e)
384 LYXERR(Debug::GUI, "Passed Geometry "
385 << size().height() << "x" << size().width()
386 << "+" << pos().x() << "+" << pos().y());
388 if (d.splitter_->count() == 0)
389 // No work area, switch to the background widget.
392 QMainWindow::showEvent(e);
396 void GuiView::closeEvent(QCloseEvent * close_event)
398 // we may have been called through the close window button
399 // which bypasses the LFUN machinery.
400 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
401 if (!quitWriteAll()) {
402 close_event->ignore();
407 // Make sure that no LFUN use this close to be closed View.
408 theLyXFunc().setLyXView(0);
409 // Make sure the timer time out will not trigger a statusbar update.
410 d.statusbar_timer_.stop();
412 if (lyxrc.allow_geometry_session) {
414 QString const key = "view-" + QString::number(id_);
416 settings.setValue(key + "/pos", pos());
417 settings.setValue(key + "/size", size());
419 settings.setValue(key + "/geometry", saveGeometry());
421 settings.setValue(key + "/icon_size", iconSize());
422 d.toolbars_->saveToolbarInfo();
425 guiApp->unregisterView(id_);
426 if (guiApp->viewCount() > 0) {
427 // Just close the window and do nothing else if this is not the
429 close_event->accept();
435 // this is the place where we leave the frontend.
436 // it is the only point at which we start quitting.
437 close_event->accept();
438 // quit the event loop
443 void GuiView::dragEnterEvent(QDragEnterEvent * event)
445 if (event->mimeData()->hasUrls())
447 /// \todo Ask lyx-devel is this is enough:
448 /// if (event->mimeData()->hasFormat("text/plain"))
449 /// event->acceptProposedAction();
453 void GuiView::dropEvent(QDropEvent* event)
455 QList<QUrl> files = event->mimeData()->urls();
459 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
460 for (int i = 0; i != files.size(); ++i) {
461 string const file = support::os::internal_path(fromqstr(
462 files.at(i).toLocalFile()));
464 dispatch(FuncRequest(LFUN_FILE_OPEN, file));
469 void GuiView::message(docstring const & str)
471 statusBar()->showMessage(toqstr(str));
472 d.statusbar_timer_.stop();
473 d.statusbar_timer_.start(3000);
477 void GuiView::smallSizedIcons()
479 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
483 void GuiView::normalSizedIcons()
485 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
489 void GuiView::bigSizedIcons()
491 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
495 void GuiView::clearMessage()
499 theLyXFunc().setLyXView(this);
500 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
501 d.statusbar_timer_.stop();
505 void GuiView::updateWindowTitle(GuiWorkArea * wa)
507 if (wa != d.current_work_area_)
509 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
510 setWindowIconText(wa->windowIconText());
514 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
517 disconnectBufferView();
518 connectBufferView(wa->bufferView());
519 connectBuffer(wa->bufferView().buffer());
520 d.current_work_area_ = wa;
521 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
522 this, SLOT(updateWindowTitle(GuiWorkArea *)));
523 updateWindowTitle(wa);
526 // Buffer-dependent dialogs should be updated or
527 // hidden. This should go here because some dialogs (eg ToC)
528 // require bv_->text.
529 updateBufferDependent(true);
536 void GuiView::updateStatusBar()
538 // let the user see the explicit message
539 if (d.statusbar_timer_.isActive())
542 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
546 bool GuiView::hasFocus() const
548 return qApp->activeWindow() == this;
552 bool GuiView::event(QEvent * e)
556 // Useful debug code:
557 //case QEvent::ActivationChange:
558 //case QEvent::WindowDeactivate:
559 //case QEvent::Paint:
560 //case QEvent::Enter:
561 //case QEvent::Leave:
562 //case QEvent::HoverEnter:
563 //case QEvent::HoverLeave:
564 //case QEvent::HoverMove:
565 //case QEvent::StatusTip:
566 //case QEvent::DragEnter:
567 //case QEvent::DragLeave:
571 case QEvent::WindowActivate: {
572 guiApp->setCurrentView(*this);
573 if (d.current_work_area_) {
574 BufferView & bv = d.current_work_area_->bufferView();
575 connectBufferView(bv);
576 connectBuffer(bv.buffer());
577 // The document structure, name and dialogs might have
578 // changed in another view.
579 updateBufferDependent(true);
581 setWindowTitle(qt_("LyX"));
582 setWindowIconText(qt_("LyX"));
584 return QMainWindow::event(e);
586 case QEvent::ShortcutOverride: {
587 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
588 if (!d.current_work_area_) {
589 theLyXFunc().setLyXView(this);
591 setKeySymbol(&sym, ke);
592 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
596 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
598 setKeySymbol(&sym, ke);
599 d.current_work_area_->processKeySym(sym, NoModifier);
605 return QMainWindow::event(e);
610 bool GuiView::focusNextPrevChild(bool /*next*/)
617 void GuiView::setBusy(bool yes)
619 if (d.current_work_area_) {
620 d.current_work_area_->setUpdatesEnabled(!yes);
622 d.current_work_area_->stopBlinkingCursor();
624 d.current_work_area_->startBlinkingCursor();
628 QApplication::setOverrideCursor(Qt::WaitCursor);
630 QApplication::restoreOverrideCursor();
634 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
636 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
638 if (tbinfo.flags & ToolbarInfo::TOP) {
640 addToolBarBreak(Qt::TopToolBarArea);
641 addToolBar(Qt::TopToolBarArea, toolBar);
644 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
645 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
646 #if (QT_VERSION >= 0x040202)
648 addToolBarBreak(Qt::BottomToolBarArea);
650 addToolBar(Qt::BottomToolBarArea, toolBar);
653 if (tbinfo.flags & ToolbarInfo::LEFT) {
654 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
655 #if (QT_VERSION >= 0x040202)
657 addToolBarBreak(Qt::LeftToolBarArea);
659 addToolBar(Qt::LeftToolBarArea, toolBar);
662 if (tbinfo.flags & ToolbarInfo::RIGHT) {
663 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
664 #if (QT_VERSION >= 0x040202)
666 addToolBarBreak(Qt::RightToolBarArea);
668 addToolBar(Qt::RightToolBarArea, toolBar);
671 // The following does not work so I cannot restore to exact toolbar location
673 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
674 toolBar->move(tbinfo.posx, tbinfo.posy);
681 GuiWorkArea * GuiView::workArea(Buffer & buffer)
683 for (int i = 0; i != d.splitter_->count(); ++i) {
684 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
692 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
695 // Automatically create a TabWorkArea if there are none yet.
696 if (!d.splitter_->count())
699 TabWorkArea * tab_widget = d.currentTabWorkArea();
700 return tab_widget->addWorkArea(buffer, *this);
704 void GuiView::addTabWorkArea()
706 TabWorkArea * twa = new TabWorkArea;
707 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
708 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
709 d.splitter_->addWidget(twa);
710 d.stack_widget_->setCurrentWidget(d.splitter_);
714 GuiWorkArea const * GuiView::currentWorkArea() const
716 return d.current_work_area_;
720 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
724 // Changing work area can result from opening a file so
725 // update the toc in any case.
728 d.current_work_area_ = wa;
729 for (int i = 0; i != d.splitter_->count(); ++i) {
730 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
736 void GuiView::removeWorkArea(GuiWorkArea * wa)
739 if (wa == d.current_work_area_) {
741 disconnectBufferView();
742 hideBufferDependent();
743 d.current_work_area_ = 0;
746 for (int i = 0; i != d.splitter_->count(); ++i) {
747 TabWorkArea * twa = d.tabWorkArea(i);
748 if (!twa->removeWorkArea(wa))
749 // Not found in this tab group.
752 // We found and removed the GuiWorkArea.
754 // No more WorkAreas in this tab group, so delete it.
759 if (d.current_work_area_)
760 // This means that we are not closing the current GuiWorkArea;
763 // Switch to the next GuiWorkArea in the found TabWorkArea.
764 d.current_work_area_ = twa->currentWorkArea();
768 if (d.splitter_->count() == 0)
769 // No more work area, switch to the background widget.
774 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
780 void GuiView::updateLayoutList()
783 d.layout_->updateContents(false);
787 void GuiView::updateToolbars()
789 if (d.current_work_area_) {
791 d.current_work_area_->bufferView().cursor().inMathed();
793 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
795 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
796 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
798 d.toolbars_->update(math, table, review);
800 d.toolbars_->update(false, false, false);
802 // update read-only status of open dialogs.
807 Buffer * GuiView::buffer()
809 if (d.current_work_area_)
810 return &d.current_work_area_->bufferView().buffer();
815 Buffer const * GuiView::buffer() const
817 if (d.current_work_area_)
818 return &d.current_work_area_->bufferView().buffer();
823 void GuiView::setBuffer(Buffer * newBuffer)
825 BOOST_ASSERT(newBuffer);
828 GuiWorkArea * wa = workArea(*newBuffer);
830 updateLabels(*newBuffer->masterBuffer());
831 wa = addWorkArea(*newBuffer);
833 //Disconnect the old buffer...there's no new one.
836 connectBuffer(*newBuffer);
837 connectBufferView(wa->bufferView());
838 setCurrentWorkArea(wa);
844 void GuiView::connectBuffer(Buffer & buf)
846 buf.setGuiDelegate(this);
850 void GuiView::disconnectBuffer()
852 if (d.current_work_area_)
853 d.current_work_area_->bufferView().setGuiDelegate(0);
857 void GuiView::connectBufferView(BufferView & bv)
859 bv.setGuiDelegate(this);
863 void GuiView::disconnectBufferView()
865 if (d.current_work_area_)
866 d.current_work_area_->bufferView().setGuiDelegate(0);
870 void GuiView::errors(string const & error_type)
872 ErrorList & el = buffer()->errorList(error_type);
874 showDialog("errorlist", error_type);
878 void GuiView::updateDialog(string const & name, string const & data)
880 if (!isDialogVisible(name))
883 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
884 if (it == d.dialogs_.end())
887 Dialog * const dialog = it->second.get();
888 if (dialog->isVisibleView())
889 dialog->updateData(data);
893 BufferView * GuiView::view()
895 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
899 void GuiView::updateToc()
901 updateDialog("toc", "");
905 void GuiView::updateEmbeddedFiles()
907 updateDialog("embedding", "");
911 void GuiView::autoSave()
913 LYXERR(Debug::INFO, "Running autoSave()");
916 view()->buffer().autoSave();
920 void GuiView::resetAutosaveTimers()
923 d.autosave_timeout_.restart();
927 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
931 Buffer * buf = buffer();
933 /* In LyX/Mac, when a dialog is open, the menus of the
934 application can still be accessed without giving focus to
935 the main window. In this case, we want to disable the menu
936 entries that are buffer-related.
938 Note that this code is not perfect, as bug 1941 attests:
939 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
941 if (cmd.origin == FuncRequest::MENU && !hasFocus())
945 case LFUN_BUFFER_WRITE:
946 enable = buf && (buf->isUnnamed() || !buf->isClean());
949 case LFUN_BUFFER_WRITE_AS:
953 case LFUN_TOOLBAR_TOGGLE:
954 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
957 case LFUN_DIALOG_TOGGLE:
958 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
959 // fall through to set "enable"
960 case LFUN_DIALOG_SHOW: {
961 string const name = cmd.getArg(0);
963 enable = name == "aboutlyx"
964 || name == "file" //FIXME: should be removed.
966 || name == "texinfo";
967 else if (name == "print")
968 enable = buf->isExportable("dvi")
969 && lyxrc.print_command != "none";
970 else if (name == "character") {
974 InsetCode ic = view()->cursor().inset().lyxCode();
975 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
978 else if (name == "latexlog")
979 enable = FileName(buf->logName()).isReadableFile();
980 else if (name == "spellchecker")
981 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
982 enable = !buf->isReadonly();
986 else if (name == "vclog")
987 enable = buf->lyxvc().inUse();
991 case LFUN_DIALOG_UPDATE: {
992 string const name = cmd.getArg(0);
994 enable = name == "prefs";
998 case LFUN_INSET_APPLY: {
1003 string const name = cmd.getArg(0);
1004 Inset * inset = getOpenInset(name);
1006 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1008 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1009 // Every inset is supposed to handle this
1010 BOOST_ASSERT(false);
1014 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1015 flag |= getStatus(fr);
1017 enable = flag.enabled();
1029 flag.enabled(false);
1035 static FileName selectTemplateFile()
1037 FileDialog dlg(_("Select template file"));
1038 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1039 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1041 FileDialog::Result result =
1042 dlg.open(from_utf8(lyxrc.template_path),
1043 FileFilterList(_("LyX Documents (*.lyx)")),
1046 if (result.first == FileDialog::Later)
1048 if (result.second.empty())
1050 return FileName(to_utf8(result.second));
1054 void GuiView::newDocument(string const & filename, bool from_template)
1057 Buffer * buf = buffer();
1059 FileName const trypath(buf->filePath());
1060 // If directory is writeable, use this as default.
1061 if (trypath.isDirWritable())
1064 initpath.set(lyxrc.document_path);
1066 // FIXME: Up to now initpath was unconditionally set to the user document
1067 // path. Is it what we want? If yes, erase the code above.
1068 initpath.set(lyxrc.document_path);
1070 string templatefile = from_template ?
1071 selectTemplateFile().absFilename() : string();
1073 if (filename.empty())
1074 b = newUnnamedFile(templatefile, initpath);
1076 b = newFile(filename, templatefile, true);
1083 void GuiView::insertLyXFile(docstring const & fname)
1085 BufferView * bv = view();
1090 FileName filename(to_utf8(fname));
1092 if (!filename.empty()) {
1093 bv->insertLyXFile(filename);
1097 // Launch a file browser
1099 string initpath = lyxrc.document_path;
1100 string const trypath = bv->buffer().filePath();
1101 // If directory is writeable, use this as default.
1102 if (FileName(trypath).isDirWritable())
1106 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1107 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1108 dlg.setButton2(_("Examples|#E#e"),
1109 from_utf8(addPath(package().system_support().absFilename(),
1112 FileDialog::Result result =
1113 dlg.open(from_utf8(initpath),
1114 FileFilterList(_("LyX Documents (*.lyx)")),
1117 if (result.first == FileDialog::Later)
1121 filename.set(to_utf8(result.second));
1123 // check selected filename
1124 if (filename.empty()) {
1125 // emit message signal.
1126 message(_("Canceled."));
1130 bv->insertLyXFile(filename);
1134 void GuiView::insertPlaintextFile(docstring const & fname,
1137 BufferView * bv = view();
1142 FileName filename(to_utf8(fname));
1144 if (!filename.empty()) {
1145 bv->insertPlaintextFile(filename, asParagraph);
1149 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1150 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1152 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1153 FileFilterList(), docstring());
1155 if (result.first == FileDialog::Later)
1159 filename.set(to_utf8(result.second));
1161 // check selected filename
1162 if (filename.empty()) {
1163 // emit message signal.
1164 message(_("Canceled."));
1168 bv->insertPlaintextFile(filename, asParagraph);
1172 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1174 FileName fname = b.fileName();
1175 FileName const oldname = fname;
1177 if (!newname.empty()) {
1179 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1181 // Switch to this Buffer.
1184 /// No argument? Ask user through dialog.
1186 FileDialog dlg(_("Choose a filename to save document as"),
1187 LFUN_BUFFER_WRITE_AS);
1188 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1189 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1191 if (!support::isLyXFilename(fname.absFilename()))
1192 fname.changeExtension(".lyx");
1194 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1196 FileDialog::Result result =
1197 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1199 from_utf8(fname.onlyFileName()));
1201 if (result.first == FileDialog::Later)
1204 fname.set(to_utf8(result.second));
1209 if (!support::isLyXFilename(fname.absFilename()))
1210 fname.changeExtension(".lyx");
1213 if (FileName(fname).exists()) {
1214 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1215 docstring text = bformat(_("The document %1$s already "
1216 "exists.\n\nDo you want to "
1217 "overwrite that document?"),
1219 int const ret = Alert::prompt(_("Overwrite document?"),
1220 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1223 case 1: return renameBuffer(b, docstring());
1224 case 2: return false;
1228 // Ok, change the name of the buffer
1229 b.setFileName(fname.absFilename());
1231 bool unnamed = b.isUnnamed();
1232 b.setUnnamed(false);
1233 b.saveCheckSum(fname);
1235 if (!saveBuffer(b)) {
1236 b.setFileName(oldname.absFilename());
1237 b.setUnnamed(unnamed);
1238 b.saveCheckSum(oldname);
1246 bool GuiView::saveBuffer(Buffer & b)
1249 return renameBuffer(b, docstring());
1252 LyX::ref().session().lastFiles().add(b.fileName());
1256 // Switch to this Buffer.
1259 // FIXME: we don't tell the user *WHY* the save failed !!
1260 docstring const file = makeDisplayPath(b.absFileName(), 30);
1261 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1262 "Do you want to rename the document and "
1263 "try again?"), file);
1264 int const ret = Alert::prompt(_("Rename and save?"),
1265 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1268 if (!renameBuffer(b, docstring()))
1277 return saveBuffer(b);
1281 bool GuiView::closeBuffer()
1283 Buffer * buf = buffer();
1284 return buf && closeBuffer(*buf);
1288 bool GuiView::closeBuffer(Buffer & buf)
1290 if (buf.isClean() || buf.paragraphs().empty()) {
1291 theBufferList().release(&buf);
1294 // Switch to this Buffer.
1299 if (buf.isUnnamed())
1300 file = from_utf8(buf.fileName().onlyFileName());
1302 file = buf.fileName().displayName(30);
1304 docstring const text = bformat(_("The document %1$s has unsaved changes."
1305 "\n\nDo you want to save the document or discard the changes?"), file);
1306 int const ret = Alert::prompt(_("Save changed document?"),
1307 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1311 if (!saveBuffer(buf))
1315 // if we crash after this we could
1316 // have no autosave file but I guess
1317 // this is really improbable (Jug)
1318 removeAutosaveFile(buf.absFileName());
1324 // save file names to .lyx/session
1325 // if master/slave are both open, do not save slave since it
1326 // will be automatically loaded when the master is loaded
1327 if (buf.masterBuffer() == &buf)
1328 LyX::ref().session().lastOpened().add(buf.fileName());
1330 theBufferList().release(&buf);
1335 bool GuiView::quitWriteAll()
1337 while (!theBufferList().empty()) {
1338 Buffer * b = theBufferList().first();
1339 if (!closeBuffer(*b))
1346 bool GuiView::dispatch(FuncRequest const & cmd)
1348 BufferView * bv = view();
1349 // By default we won't need any update.
1351 bv->cursor().updateFlags(Update::None);
1353 switch(cmd.action) {
1354 case LFUN_BUFFER_SWITCH:
1355 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1358 case LFUN_BUFFER_NEXT:
1359 setBuffer(theBufferList().next(buffer()));
1362 case LFUN_BUFFER_PREVIOUS:
1363 setBuffer(theBufferList().previous(buffer()));
1366 case LFUN_COMMAND_EXECUTE: {
1367 bool const show_it = cmd.argument() != "off";
1368 d.toolbars_->showCommandBuffer(show_it);
1371 case LFUN_DROP_LAYOUTS_CHOICE:
1373 d.layout_->showPopup();
1376 case LFUN_MENU_OPEN:
1377 d.menubar_->openByName(toqstr(cmd.argument()));
1380 case LFUN_FILE_INSERT:
1381 insertLyXFile(cmd.argument());
1383 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1384 insertPlaintextFile(cmd.argument(), true);
1387 case LFUN_FILE_INSERT_PLAINTEXT:
1388 insertPlaintextFile(cmd.argument(), false);
1391 case LFUN_BUFFER_WRITE:
1393 saveBuffer(bv->buffer());
1396 case LFUN_BUFFER_WRITE_AS:
1398 renameBuffer(bv->buffer(), cmd.argument());
1401 case LFUN_BUFFER_WRITE_ALL: {
1402 Buffer * first = theBufferList().first();
1405 message(_("Saving all documents..."));
1406 // We cannot use a for loop as the buffer list cycles.
1412 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1413 b = theBufferList().next(b);
1414 } while (b != first);
1415 message(_("All documents saved."));
1419 case LFUN_TOOLBAR_TOGGLE: {
1420 string const name = cmd.getArg(0);
1421 bool const allowauto = cmd.getArg(1) == "allowauto";
1422 // it is possible to get current toolbar status like this,...
1423 // but I decide to obey the order of ToolbarBackend::flags
1424 // and disregard real toolbar status.
1425 // toolbars_->saveToolbarInfo();
1427 // toggle state on/off/auto
1428 d.toolbars_->toggleToolbarState(name, allowauto);
1432 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1434 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1438 if (tbi->flags & ToolbarInfo::ON)
1440 else if (tbi->flags & ToolbarInfo::OFF)
1442 else if (tbi->flags & ToolbarInfo::AUTO)
1445 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1446 _(tbi->gui_name), state));
1450 case LFUN_DIALOG_UPDATE: {
1451 string const name = to_utf8(cmd.argument());
1452 // Can only update a dialog connected to an existing inset
1453 Inset * inset = getOpenInset(name);
1455 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1456 inset->dispatch(view()->cursor(), fr);
1457 } else if (name == "paragraph") {
1458 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1459 } else if (name == "prefs") {
1460 updateDialog(name, string());
1465 case LFUN_DIALOG_TOGGLE: {
1466 if (isDialogVisible(cmd.getArg(0)))
1467 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1469 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1473 case LFUN_DIALOG_DISCONNECT_INSET:
1474 disconnectDialog(to_utf8(cmd.argument()));
1477 case LFUN_DIALOG_HIDE: {
1480 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1484 case LFUN_DIALOG_SHOW: {
1485 string const name = cmd.getArg(0);
1486 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1488 if (name == "character") {
1489 data = freefont2string();
1491 showDialog("character", data);
1492 } else if (name == "latexlog") {
1493 Buffer::LogType type;
1494 string const logfile = buffer()->logName(&type);
1496 case Buffer::latexlog:
1499 case Buffer::buildlog:
1503 data += Lexer::quoteString(logfile);
1504 showDialog("log", data);
1505 } else if (name == "vclog") {
1506 string const data = "vc " +
1507 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1508 showDialog("log", data);
1510 showDialog(name, data);
1514 case LFUN_INSET_APPLY: {
1515 string const name = cmd.getArg(0);
1516 Inset * inset = getOpenInset(name);
1518 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1519 inset->dispatch(view()->cursor(), fr);
1521 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1535 Buffer const * GuiView::updateInset(Inset const * inset)
1537 if (!d.current_work_area_)
1541 d.current_work_area_->scheduleRedraw();
1543 return &d.current_work_area_->bufferView().buffer();
1547 void GuiView::restartCursor()
1549 /* When we move around, or type, it's nice to be able to see
1550 * the cursor immediately after the keypress.
1552 if (d.current_work_area_)
1553 d.current_work_area_->startBlinkingCursor();
1555 // Take this occasion to update the toobars and layout list.
1562 // This list should be kept in sync with the list of insets in
1563 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1564 // dialog should have the same name as the inset.
1566 char const * const dialognames[] = {
1567 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1568 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1569 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1570 "mathdelimiter", "mathmatrix", "note", "paragraph",
1571 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1573 #ifdef HAVE_LIBAIKSAURUS
1577 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1579 char const * const * const end_dialognames =
1580 dialognames + (sizeof(dialognames) / sizeof(char *));
1584 cmpCStr(char const * name) : name_(name) {}
1585 bool operator()(char const * other) {
1586 return strcmp(other, name_) == 0;
1593 bool isValidName(string const & name)
1595 return std::find_if(dialognames, end_dialognames,
1596 cmpCStr(name.c_str())) != end_dialognames;
1602 void GuiView::resetDialogs()
1604 // Make sure that no LFUN uses any LyXView.
1605 theLyXFunc().setLyXView(0);
1606 d.toolbars_->init();
1609 d.layout_->updateContents(true);
1610 // Now update controls with current buffer.
1611 theLyXFunc().setLyXView(this);
1616 Dialog * GuiView::find_or_build(string const & name)
1618 if (!isValidName(name))
1621 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1623 if (it != d.dialogs_.end())
1624 return it->second.get();
1626 d.dialogs_[name].reset(build(name));
1627 return d.dialogs_[name].get();
1631 void GuiView::showDialog(string const & name, string const & data,
1638 Dialog * dialog = find_or_build(name);
1640 dialog->showData(data);
1642 d.open_insets_[name] = inset;
1648 bool GuiView::isDialogVisible(string const & name) const
1650 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1651 if (it == d.dialogs_.end())
1653 return it->second.get()->isVisibleView();
1657 void GuiView::hideDialog(string const & name, Inset * inset)
1659 // Don't send the signal if we are quitting, because on MSVC it is
1660 // destructed before the cut stack in CutAndPaste.cpp, and this method
1661 // is called from some inset destructor if the cut stack is not empty
1666 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1667 if (it == d.dialogs_.end())
1670 if (inset && inset != getOpenInset(name))
1673 Dialog * const dialog = it->second.get();
1674 if (dialog->isVisibleView())
1676 d.open_insets_[name] = 0;
1680 void GuiView::disconnectDialog(string const & name)
1682 if (!isValidName(name))
1685 if (d.open_insets_.find(name) != d.open_insets_.end())
1686 d.open_insets_[name] = 0;
1690 Inset * GuiView::getOpenInset(string const & name) const
1692 if (!isValidName(name))
1695 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1696 return it == d.open_insets_.end() ? 0 : it->second;
1700 void GuiView::hideAll() const
1702 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1703 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1705 for(; it != end; ++it)
1710 void GuiView::hideBufferDependent() const
1712 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1713 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1715 for(; it != end; ++it) {
1716 Dialog * dialog = it->second.get();
1717 if (dialog->isBufferDependent())
1723 void GuiView::updateBufferDependent(bool switched) const
1725 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1726 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1728 for(; it != end; ++it) {
1729 Dialog * dialog = it->second.get();
1730 if (switched && dialog->isBufferDependent()) {
1731 if (dialog->isVisibleView() && dialog->initialiseParams(""))
1732 dialog->updateView();
1736 // A bit clunky, but the dialog will request
1737 // that the kernel provides it with the necessary
1739 dialog->updateDialog();
1745 void GuiView::checkStatus()
1747 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1748 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1750 for(; it != end; ++it) {
1751 Dialog * const dialog = it->second.get();
1752 if (dialog && dialog->isVisibleView())
1753 dialog->checkStatus();
1759 // will be replaced by a proper factory...
1760 Dialog * createGuiAbout(GuiView & lv);
1761 Dialog * createGuiBibitem(GuiView & lv);
1762 Dialog * createGuiBibtex(GuiView & lv);
1763 Dialog * createGuiBox(GuiView & lv);
1764 Dialog * createGuiBranch(GuiView & lv);
1765 Dialog * createGuiChanges(GuiView & lv);
1766 Dialog * createGuiCharacter(GuiView & lv);
1767 Dialog * createGuiCitation(GuiView & lv);
1768 Dialog * createGuiDelimiter(GuiView & lv);
1769 Dialog * createGuiDocument(GuiView & lv);
1770 Dialog * createGuiErrorList(GuiView & lv);
1771 Dialog * createGuiERT(GuiView & lv);
1772 Dialog * createGuiExternal(GuiView & lv);
1773 Dialog * createGuiFloat(GuiView & lv);
1774 Dialog * createGuiGraphics(GuiView & lv);
1775 Dialog * createGuiInclude(GuiView & lv);
1776 Dialog * createGuiIndex(GuiView & lv);
1777 Dialog * createGuiLabel(GuiView & lv);
1778 Dialog * createGuiListings(GuiView & lv);
1779 Dialog * createGuiLog(GuiView & lv);
1780 Dialog * createGuiMathMatrix(GuiView & lv);
1781 Dialog * createGuiNomenclature(GuiView & lv);
1782 Dialog * createGuiNote(GuiView & lv);
1783 Dialog * createGuiParagraph(GuiView & lv);
1784 Dialog * createGuiPreferences(GuiView & lv);
1785 Dialog * createGuiPrint(GuiView & lv);
1786 Dialog * createGuiRef(GuiView & lv);
1787 Dialog * createGuiSearch(GuiView & lv);
1788 Dialog * createGuiSendTo(GuiView & lv);
1789 Dialog * createGuiShowFile(GuiView & lv);
1790 Dialog * createGuiSpellchecker(GuiView & lv);
1791 Dialog * createGuiTabularCreate(GuiView & lv);
1792 Dialog * createGuiTabular(GuiView & lv);
1793 Dialog * createGuiTexInfo(GuiView & lv);
1794 Dialog * createGuiToc(GuiView & lv);
1795 Dialog * createGuiThesaurus(GuiView & lv);
1796 Dialog * createGuiHyperlink(GuiView & lv);
1797 Dialog * createGuiVSpace(GuiView & lv);
1798 Dialog * createGuiViewSource(GuiView & lv);
1799 Dialog * createGuiWrap(GuiView & lv);
1802 Dialog * GuiView::build(string const & name)
1804 BOOST_ASSERT(isValidName(name));
1806 if (name == "aboutlyx")
1807 return createGuiAbout(*this);
1808 if (name == "bibitem")
1809 return createGuiBibitem(*this);
1810 if (name == "bibtex")
1811 return createGuiBibtex(*this);
1813 return createGuiBox(*this);
1814 if (name == "branch")
1815 return createGuiBranch(*this);
1816 if (name == "changes")
1817 return createGuiChanges(*this);
1818 if (name == "character")
1819 return createGuiCharacter(*this);
1820 if (name == "citation")
1821 return createGuiCitation(*this);
1822 if (name == "document")
1823 return createGuiDocument(*this);
1824 if (name == "errorlist")
1825 return createGuiErrorList(*this);
1827 return createGuiERT(*this);
1828 if (name == "external")
1829 return createGuiExternal(*this);
1831 return createGuiShowFile(*this);
1832 if (name == "findreplace")
1833 return createGuiSearch(*this);
1834 if (name == "float")
1835 return createGuiFloat(*this);
1836 if (name == "graphics")
1837 return createGuiGraphics(*this);
1838 if (name == "include")
1839 return createGuiInclude(*this);
1840 if (name == "index")
1841 return createGuiIndex(*this);
1842 if (name == "nomenclature")
1843 return createGuiNomenclature(*this);
1844 if (name == "label")
1845 return createGuiLabel(*this);
1847 return createGuiLog(*this);
1848 if (name == "view-source")
1849 return createGuiViewSource(*this);
1850 if (name == "mathdelimiter")
1851 return createGuiDelimiter(*this);
1852 if (name == "mathmatrix")
1853 return createGuiMathMatrix(*this);
1855 return createGuiNote(*this);
1856 if (name == "paragraph")
1857 return createGuiParagraph(*this);
1858 if (name == "prefs")
1859 return createGuiPreferences(*this);
1860 if (name == "print")
1861 return createGuiPrint(*this);
1863 return createGuiRef(*this);
1864 if (name == "sendto")
1865 return createGuiSendTo(*this);
1866 if (name == "spellchecker")
1867 return createGuiSpellchecker(*this);
1868 if (name == "tabular")
1869 return createGuiTabular(*this);
1870 if (name == "tabularcreate")
1871 return createGuiTabularCreate(*this);
1872 if (name == "texinfo")
1873 return createGuiTexInfo(*this);
1874 #ifdef HAVE_LIBAIKSAURUS
1875 if (name == "thesaurus")
1876 return createGuiThesaurus(*this);
1879 return createGuiToc(*this);
1881 return createGuiHyperlink(*this);
1882 if (name == "vspace")
1883 return createGuiVSpace(*this);
1885 return createGuiWrap(*this);
1886 if (name == "listings")
1887 return createGuiListings(*this);
1893 } // namespace frontend
1896 #include "GuiView_moc.cpp"