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 "Paragraph.h"
49 #include "TextClass.h"
51 #include "ToolbarBackend.h"
54 #include "support/FileFilterList.h"
55 #include "support/FileName.h"
56 #include "support/filetools.h"
57 #include "support/lstrings.h"
58 #include "support/os.h"
59 #include "support/Package.h"
60 #include "support/Timeout.h"
63 #include <QApplication>
64 #include <QCloseEvent>
66 #include <QDesktopWidget>
67 #include <QDragEnterEvent>
74 #include <QPushButton>
78 #include <QStackedWidget>
84 #include <boost/assert.hpp>
85 #include <boost/bind.hpp>
87 #ifdef HAVE_SYS_TIME_H
88 # include <sys/time.h>
95 using namespace lyx::support;
105 class BackgroundWidget : public QWidget
110 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
111 /// The text to be written on top of the pixmap
112 QString const text = lyx_version ? lyx_version : qt_("unknown version");
113 splash_ = QPixmap(":/images/banner.png");
115 QPainter pain(&splash_);
116 pain.setPen(QColor(255, 255, 0));
118 // The font used to display the version info
119 font.setStyleHint(QFont::SansSerif);
120 font.setWeight(QFont::Bold);
121 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
123 pain.drawText(260, 270, text);
126 void paintEvent(QPaintEvent *)
128 int x = (width() - splash_.width()) / 2;
129 int y = (height() - splash_.height()) / 2;
131 pain.drawPixmap(x, y, splash_);
141 typedef boost::shared_ptr<Dialog> DialogPtr;
143 struct GuiView::GuiViewPrivate
146 : current_work_area_(0), layout_(0),
147 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
149 // hardcode here the platform specific icon size
150 smallIconSize = 14; // scaling problems
151 normalIconSize = 20; // ok, default
152 bigIconSize = 26; // better for some math icons
154 splitter_ = new QSplitter;
155 bg_widget_ = new BackgroundWidget;
156 stack_widget_ = new QStackedWidget;
157 stack_widget_->addWidget(bg_widget_);
158 stack_widget_->addWidget(splitter_);
166 delete stack_widget_;
170 QMenu * toolBarPopup(GuiView * parent)
172 // FIXME: translation
173 QMenu * menu = new QMenu(parent);
174 QActionGroup * iconSizeGroup = new QActionGroup(parent);
176 QAction * smallIcons = new QAction(iconSizeGroup);
177 smallIcons->setText(qt_("Small-sized icons"));
178 smallIcons->setCheckable(true);
179 QObject::connect(smallIcons, SIGNAL(triggered()),
180 parent, SLOT(smallSizedIcons()));
181 menu->addAction(smallIcons);
183 QAction * normalIcons = new QAction(iconSizeGroup);
184 normalIcons->setText(qt_("Normal-sized icons"));
185 normalIcons->setCheckable(true);
186 QObject::connect(normalIcons, SIGNAL(triggered()),
187 parent, SLOT(normalSizedIcons()));
188 menu->addAction(normalIcons);
190 QAction * bigIcons = new QAction(iconSizeGroup);
191 bigIcons->setText(qt_("Big-sized icons"));
192 bigIcons->setCheckable(true);
193 QObject::connect(bigIcons, SIGNAL(triggered()),
194 parent, SLOT(bigSizedIcons()));
195 menu->addAction(bigIcons);
197 unsigned int cur = parent->iconSize().width();
198 if ( cur == parent->d.smallIconSize)
199 smallIcons->setChecked(true);
200 else if (cur == parent->d.normalIconSize)
201 normalIcons->setChecked(true);
202 else if (cur == parent->d.bigIconSize)
203 bigIcons->setChecked(true);
210 stack_widget_->setCurrentWidget(bg_widget_);
211 bg_widget_->setUpdatesEnabled(true);
214 TabWorkArea * tabWorkArea(int i)
216 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
219 TabWorkArea * currentTabWorkArea()
221 if (splitter_->count() == 1)
222 // The first TabWorkArea is always the first one, if any.
223 return tabWorkArea(0);
225 TabWorkArea * tab_widget = 0;
226 for (int i = 0; i != splitter_->count(); ++i) {
227 QWidget * w = splitter_->widget(i);
230 tab_widget = dynamic_cast<TabWorkArea *>(w);
239 GuiWorkArea * current_work_area_;
240 QSplitter * splitter_;
241 QStackedWidget * stack_widget_;
242 BackgroundWidget * bg_widget_;
244 GuiToolbars * toolbars_;
245 /// The main layout box.
247 * \warning Don't Delete! The layout box is actually owned by
248 * whichever toolbar contains it. All the GuiView class needs is a
249 * means of accessing it.
251 * FIXME: replace that with a proper model so that we are not limited
252 * to only one dialog.
254 GuiLayoutBox * layout_;
257 map<string, Inset *> open_insets_;
260 map<string, DialogPtr> dialogs_;
262 unsigned int smallIconSize;
263 unsigned int normalIconSize;
264 unsigned int bigIconSize;
266 QTimer statusbar_timer_;
267 /// are we quitting by the menu?
268 bool quitting_by_menu_;
269 /// auto-saving of buffers
270 Timeout autosave_timeout_;
271 /// flag against a race condition due to multiclicks, see bug #1119
276 GuiView::GuiView(int id)
277 : d(*new GuiViewPrivate), id_(id)
279 // GuiToolbars *must* be initialised before the menu bar.
280 d.toolbars_ = new GuiToolbars(*this);
282 // Fill up the menu bar.
283 guiApp->menus().fillMenuBar(this);
285 setCentralWidget(d.stack_widget_);
287 // Start autosave timer
288 if (lyxrc.autosave) {
289 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
290 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
291 d.autosave_timeout_.start();
293 connect(&d.statusbar_timer_, SIGNAL(timeout()),
294 this, SLOT(clearMessage()));
296 // Qt bug? signal lastWindowClosed does not work
297 setAttribute(Qt::WA_QuitOnClose, false);
298 setAttribute(Qt::WA_DeleteOnClose, true);
300 // assign an icon to main form. We do not do it under Qt/Mac,
301 // since the icon is provided in the application bundle.
302 setWindowIcon(QPixmap(":/images/lyx.png"));
306 setAcceptDrops(true);
308 statusBar()->setSizeGripEnabled(true);
310 // Forbid too small unresizable window because it can happen
311 // with some window manager under X11.
312 setMinimumSize(300, 200);
314 if (!lyxrc.allow_geometry_session)
315 // No session handling, default to a sane size.
316 setGeometry(50, 50, 690, 510);
318 // Now take care of session management.
320 QString const key = "view-" + QString::number(id_);
322 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
323 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
327 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
328 setGeometry(50, 50, 690, 510);
330 setIconSize(settings.value(key + "/icon_size").toSize());
340 void GuiView::close()
342 d.quitting_by_menu_ = true;
343 d.current_work_area_ = 0;
344 for (int i = 0; i != d.splitter_->count(); ++i) {
345 TabWorkArea * twa = d.tabWorkArea(i);
349 QMainWindow::close();
350 d.quitting_by_menu_ = false;
354 void GuiView::setFocus()
356 if (d.current_work_area_)
357 d.current_work_area_->setFocus();
363 QMenu * GuiView::createPopupMenu()
365 return d.toolBarPopup(this);
369 void GuiView::showEvent(QShowEvent * e)
371 LYXERR(Debug::GUI, "Passed Geometry "
372 << size().height() << "x" << size().width()
373 << "+" << pos().x() << "+" << pos().y());
375 if (d.splitter_->count() == 0)
376 // No work area, switch to the background widget.
379 QMainWindow::showEvent(e);
383 void GuiView::closeEvent(QCloseEvent * close_event)
385 // we may have been called through the close window button
386 // which bypasses the LFUN machinery.
387 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
388 if (!quitWriteAll()) {
389 close_event->ignore();
394 // Make sure that no LFUN use this close to be closed View.
395 theLyXFunc().setLyXView(0);
396 // Make sure the timer time out will not trigger a statusbar update.
397 d.statusbar_timer_.stop();
399 if (lyxrc.allow_geometry_session) {
401 QString const key = "view-" + QString::number(id_);
403 settings.setValue(key + "/pos", pos());
404 settings.setValue(key + "/size", size());
406 settings.setValue(key + "/geometry", saveGeometry());
408 settings.setValue(key + "/icon_size", iconSize());
409 d.toolbars_->saveToolbarInfo();
410 // Now take care of all other dialogs:
411 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
412 for (; it!= d.dialogs_.end(); ++it)
413 it->second->saveSession();
416 guiApp->unregisterView(id_);
417 if (guiApp->viewCount() > 0) {
418 // Just close the window and do nothing else if this is not the
420 close_event->accept();
426 // this is the place where we leave the frontend.
427 // it is the only point at which we start quitting.
428 close_event->accept();
429 // quit the event loop
434 void GuiView::dragEnterEvent(QDragEnterEvent * event)
436 if (event->mimeData()->hasUrls())
438 /// \todo Ask lyx-devel is this is enough:
439 /// if (event->mimeData()->hasFormat("text/plain"))
440 /// event->acceptProposedAction();
444 void GuiView::dropEvent(QDropEvent* event)
446 QList<QUrl> files = event->mimeData()->urls();
450 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
451 for (int i = 0; i != files.size(); ++i) {
452 string const file = os::internal_path(fromqstr(
453 files.at(i).toLocalFile()));
455 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
460 void GuiView::message(docstring const & str)
462 statusBar()->showMessage(toqstr(str));
463 d.statusbar_timer_.stop();
464 d.statusbar_timer_.start(3000);
468 void GuiView::smallSizedIcons()
470 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
474 void GuiView::normalSizedIcons()
476 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
480 void GuiView::bigSizedIcons()
482 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
486 void GuiView::clearMessage()
490 theLyXFunc().setLyXView(this);
491 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
492 d.statusbar_timer_.stop();
496 void GuiView::updateWindowTitle(GuiWorkArea * wa)
498 if (wa != d.current_work_area_)
500 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
501 setWindowIconText(wa->windowIconText());
505 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
508 disconnectBufferView();
509 connectBufferView(wa->bufferView());
510 connectBuffer(wa->bufferView().buffer());
511 d.current_work_area_ = wa;
512 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
513 this, SLOT(updateWindowTitle(GuiWorkArea *)));
514 updateWindowTitle(wa);
517 // Buffer-dependent dialogs should be updated or
518 // hidden. This should go here because some dialogs (eg ToC)
519 // require bv_->text.
520 updateBufferDependent(true);
527 void GuiView::updateStatusBar()
529 // let the user see the explicit message
530 if (d.statusbar_timer_.isActive())
533 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
537 bool GuiView::hasFocus() const
539 return qApp->activeWindow() == this;
543 bool GuiView::event(QEvent * e)
547 // Useful debug code:
548 //case QEvent::ActivationChange:
549 //case QEvent::WindowDeactivate:
550 //case QEvent::Paint:
551 //case QEvent::Enter:
552 //case QEvent::Leave:
553 //case QEvent::HoverEnter:
554 //case QEvent::HoverLeave:
555 //case QEvent::HoverMove:
556 //case QEvent::StatusTip:
557 //case QEvent::DragEnter:
558 //case QEvent::DragLeave:
562 case QEvent::WindowActivate: {
563 guiApp->setCurrentView(*this);
564 if (d.current_work_area_) {
565 BufferView & bv = d.current_work_area_->bufferView();
566 connectBufferView(bv);
567 connectBuffer(bv.buffer());
568 // The document structure, name and dialogs might have
569 // changed in another view.
570 updateBufferDependent(true);
572 setWindowTitle(qt_("LyX"));
573 setWindowIconText(qt_("LyX"));
575 return QMainWindow::event(e);
577 case QEvent::ShortcutOverride: {
578 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
579 if (!d.current_work_area_) {
580 theLyXFunc().setLyXView(this);
582 setKeySymbol(&sym, ke);
583 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
587 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
589 setKeySymbol(&sym, ke);
590 d.current_work_area_->processKeySym(sym, NoModifier);
596 return QMainWindow::event(e);
601 bool GuiView::focusNextPrevChild(bool /*next*/)
608 void GuiView::setBusy(bool yes)
610 if (d.current_work_area_) {
611 d.current_work_area_->setUpdatesEnabled(!yes);
613 d.current_work_area_->stopBlinkingCursor();
615 d.current_work_area_->startBlinkingCursor();
619 QApplication::setOverrideCursor(Qt::WaitCursor);
621 QApplication::restoreOverrideCursor();
625 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
627 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
629 if (tbinfo.flags & ToolbarInfo::TOP) {
631 addToolBarBreak(Qt::TopToolBarArea);
632 addToolBar(Qt::TopToolBarArea, toolBar);
635 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
636 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
637 #if (QT_VERSION >= 0x040202)
639 addToolBarBreak(Qt::BottomToolBarArea);
641 addToolBar(Qt::BottomToolBarArea, toolBar);
644 if (tbinfo.flags & ToolbarInfo::LEFT) {
645 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
646 #if (QT_VERSION >= 0x040202)
648 addToolBarBreak(Qt::LeftToolBarArea);
650 addToolBar(Qt::LeftToolBarArea, toolBar);
653 if (tbinfo.flags & ToolbarInfo::RIGHT) {
654 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
655 #if (QT_VERSION >= 0x040202)
657 addToolBarBreak(Qt::RightToolBarArea);
659 addToolBar(Qt::RightToolBarArea, toolBar);
662 // The following does not work so I cannot restore to exact toolbar location
664 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
665 toolBar->move(tbinfo.posx, tbinfo.posy);
672 GuiWorkArea * GuiView::workArea(Buffer & buffer)
674 for (int i = 0; i != d.splitter_->count(); ++i) {
675 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
683 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
686 // Automatically create a TabWorkArea if there are none yet.
687 if (!d.splitter_->count())
690 TabWorkArea * tab_widget = d.currentTabWorkArea();
691 return tab_widget->addWorkArea(buffer, *this);
695 void GuiView::addTabWorkArea()
697 TabWorkArea * twa = new TabWorkArea;
698 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
699 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
700 d.splitter_->addWidget(twa);
701 d.stack_widget_->setCurrentWidget(d.splitter_);
705 GuiWorkArea const * GuiView::currentWorkArea() const
707 return d.current_work_area_;
711 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
715 // Changing work area can result from opening a file so
716 // update the toc in any case.
719 d.current_work_area_ = wa;
720 for (int i = 0; i != d.splitter_->count(); ++i) {
721 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
727 void GuiView::removeWorkArea(GuiWorkArea * wa)
730 if (wa == d.current_work_area_) {
732 disconnectBufferView();
733 hideBufferDependent();
734 d.current_work_area_ = 0;
737 for (int i = 0; i != d.splitter_->count(); ++i) {
738 TabWorkArea * twa = d.tabWorkArea(i);
739 if (!twa->removeWorkArea(wa))
740 // Not found in this tab group.
743 // We found and removed the GuiWorkArea.
745 // No more WorkAreas in this tab group, so delete it.
750 if (d.current_work_area_)
751 // This means that we are not closing the current GuiWorkArea;
754 // Switch to the next GuiWorkArea in the found TabWorkArea.
755 d.current_work_area_ = twa->currentWorkArea();
759 if (d.splitter_->count() == 0)
760 // No more work area, switch to the background widget.
765 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
771 void GuiView::updateLayoutList()
774 d.layout_->updateContents(false);
778 void GuiView::updateToolbars()
780 if (d.current_work_area_) {
782 d.current_work_area_->bufferView().cursor().inMathed();
784 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
786 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
787 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
788 bool const mathmacrotemplate =
789 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
791 d.toolbars_->update(math, table, review, mathmacrotemplate);
793 d.toolbars_->update(false, false, false, false);
795 // update read-only status of open dialogs.
800 Buffer * GuiView::buffer()
802 if (d.current_work_area_)
803 return &d.current_work_area_->bufferView().buffer();
808 Buffer const * GuiView::buffer() const
810 if (d.current_work_area_)
811 return &d.current_work_area_->bufferView().buffer();
816 void GuiView::setBuffer(Buffer * newBuffer)
818 BOOST_ASSERT(newBuffer);
821 GuiWorkArea * wa = workArea(*newBuffer);
823 updateLabels(*newBuffer->masterBuffer());
824 wa = addWorkArea(*newBuffer);
826 //Disconnect the old buffer...there's no new one.
829 connectBuffer(*newBuffer);
830 connectBufferView(wa->bufferView());
831 setCurrentWorkArea(wa);
837 void GuiView::connectBuffer(Buffer & buf)
839 buf.setGuiDelegate(this);
843 void GuiView::disconnectBuffer()
845 if (d.current_work_area_)
846 d.current_work_area_->bufferView().setGuiDelegate(0);
850 void GuiView::connectBufferView(BufferView & bv)
852 bv.setGuiDelegate(this);
856 void GuiView::disconnectBufferView()
858 if (d.current_work_area_)
859 d.current_work_area_->bufferView().setGuiDelegate(0);
863 void GuiView::errors(string const & error_type)
865 ErrorList & el = buffer()->errorList(error_type);
867 showDialog("errorlist", error_type);
871 void GuiView::updateDialog(string const & name, string const & data)
873 if (!isDialogVisible(name))
876 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
877 if (it == d.dialogs_.end())
880 Dialog * const dialog = it->second.get();
881 if (dialog->isVisibleView())
882 dialog->updateData(data);
886 BufferView * GuiView::view()
888 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
892 void GuiView::updateToc()
894 updateDialog("toc", "");
898 void GuiView::updateEmbeddedFiles()
900 updateDialog("embedding", "");
904 void GuiView::autoSave()
906 LYXERR(Debug::INFO, "Running autoSave()");
909 view()->buffer().autoSave();
913 void GuiView::resetAutosaveTimers()
916 d.autosave_timeout_.restart();
920 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
924 Buffer * buf = buffer();
926 /* In LyX/Mac, when a dialog is open, the menus of the
927 application can still be accessed without giving focus to
928 the main window. In this case, we want to disable the menu
929 entries that are buffer-related.
931 Note that this code is not perfect, as bug 1941 attests:
932 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
934 if (cmd.origin == FuncRequest::MENU && !hasFocus())
938 case LFUN_BUFFER_WRITE:
939 enable = buf && (buf->isUnnamed() || !buf->isClean());
942 case LFUN_BUFFER_WRITE_AS:
946 case LFUN_TOOLBAR_TOGGLE:
947 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
950 case LFUN_DIALOG_TOGGLE:
951 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
952 // fall through to set "enable"
953 case LFUN_DIALOG_SHOW: {
954 string const name = cmd.getArg(0);
956 enable = name == "aboutlyx"
957 || name == "file" //FIXME: should be removed.
959 || name == "texinfo";
960 else if (name == "print")
961 enable = buf->isExportable("dvi")
962 && lyxrc.print_command != "none";
963 else if (name == "character") {
967 InsetCode ic = view()->cursor().inset().lyxCode();
968 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
971 else if (name == "latexlog")
972 enable = FileName(buf->logName()).isReadableFile();
973 else if (name == "spellchecker")
974 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
975 enable = !buf->isReadonly();
979 else if (name == "vclog")
980 enable = buf->lyxvc().inUse();
984 case LFUN_DIALOG_UPDATE: {
985 string const name = cmd.getArg(0);
987 enable = name == "prefs";
991 case LFUN_INSET_APPLY: {
996 string const name = cmd.getArg(0);
997 Inset * inset = getOpenInset(name);
999 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1001 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1002 // Every inset is supposed to handle this
1003 BOOST_ASSERT(false);
1007 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1008 flag |= getStatus(fr);
1010 enable = flag.enabled();
1022 flag.enabled(false);
1028 static FileName selectTemplateFile()
1030 FileDialog dlg(_("Select template file"));
1031 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1032 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1034 FileDialog::Result result =
1035 dlg.open(from_utf8(lyxrc.template_path),
1036 FileFilterList(_("LyX Documents (*.lyx)")),
1039 if (result.first == FileDialog::Later)
1041 if (result.second.empty())
1043 return FileName(to_utf8(result.second));
1047 void GuiView::newDocument(string const & filename, bool from_template)
1049 FileName initpath(lyxrc.document_path);
1050 Buffer * buf = buffer();
1052 FileName const trypath(buf->filePath());
1053 // If directory is writeable, use this as default.
1054 if (trypath.isDirWritable())
1058 string templatefile = from_template ?
1059 selectTemplateFile().absFilename() : string();
1061 if (filename.empty())
1062 b = newUnnamedFile(templatefile, initpath);
1064 b = newFile(filename, templatefile, true);
1068 // Ensure the cursor is correctly positionned on screen.
1069 view()->showCursor();
1073 void GuiView::insertLyXFile(docstring const & fname)
1075 BufferView * bv = view();
1080 FileName filename(to_utf8(fname));
1082 if (!filename.empty()) {
1083 bv->insertLyXFile(filename);
1087 // Launch a file browser
1089 string initpath = lyxrc.document_path;
1090 string const trypath = bv->buffer().filePath();
1091 // If directory is writeable, use this as default.
1092 if (FileName(trypath).isDirWritable())
1096 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1097 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1098 dlg.setButton2(_("Examples|#E#e"),
1099 from_utf8(addPath(package().system_support().absFilename(),
1102 FileDialog::Result result =
1103 dlg.open(from_utf8(initpath),
1104 FileFilterList(_("LyX Documents (*.lyx)")),
1107 if (result.first == FileDialog::Later)
1111 filename.set(to_utf8(result.second));
1113 // check selected filename
1114 if (filename.empty()) {
1115 // emit message signal.
1116 message(_("Canceled."));
1120 bv->insertLyXFile(filename);
1124 void GuiView::insertPlaintextFile(docstring const & fname,
1127 BufferView * bv = view();
1132 FileName filename(to_utf8(fname));
1134 if (!filename.empty()) {
1135 bv->insertPlaintextFile(filename, asParagraph);
1139 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1140 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1142 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1143 FileFilterList(), docstring());
1145 if (result.first == FileDialog::Later)
1149 filename.set(to_utf8(result.second));
1151 // check selected filename
1152 if (filename.empty()) {
1153 // emit message signal.
1154 message(_("Canceled."));
1158 bv->insertPlaintextFile(filename, asParagraph);
1162 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1164 FileName fname = b.fileName();
1165 FileName const oldname = fname;
1167 if (!newname.empty()) {
1169 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1171 // Switch to this Buffer.
1174 /// No argument? Ask user through dialog.
1176 FileDialog dlg(_("Choose a filename to save document as"),
1177 LFUN_BUFFER_WRITE_AS);
1178 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1179 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1181 if (!isLyXFilename(fname.absFilename()))
1182 fname.changeExtension(".lyx");
1184 FileFilterList const filter(_("LyX Documents (*.lyx)"));
1186 FileDialog::Result result =
1187 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1189 from_utf8(fname.onlyFileName()));
1191 if (result.first == FileDialog::Later)
1194 fname.set(to_utf8(result.second));
1199 if (!isLyXFilename(fname.absFilename()))
1200 fname.changeExtension(".lyx");
1203 if (FileName(fname).exists()) {
1204 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1205 docstring text = bformat(_("The document %1$s already "
1206 "exists.\n\nDo you want to "
1207 "overwrite that document?"),
1209 int const ret = Alert::prompt(_("Overwrite document?"),
1210 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1213 case 1: return renameBuffer(b, docstring());
1214 case 2: return false;
1218 // Ok, change the name of the buffer
1219 b.setFileName(fname.absFilename());
1221 bool unnamed = b.isUnnamed();
1222 b.setUnnamed(false);
1223 b.saveCheckSum(fname);
1225 if (!saveBuffer(b)) {
1226 b.setFileName(oldname.absFilename());
1227 b.setUnnamed(unnamed);
1228 b.saveCheckSum(oldname);
1236 bool GuiView::saveBuffer(Buffer & b)
1239 return renameBuffer(b, docstring());
1242 LyX::ref().session().lastFiles().add(b.fileName());
1246 // Switch to this Buffer.
1249 // FIXME: we don't tell the user *WHY* the save failed !!
1250 docstring const file = makeDisplayPath(b.absFileName(), 30);
1251 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1252 "Do you want to rename the document and "
1253 "try again?"), file);
1254 int const ret = Alert::prompt(_("Rename and save?"),
1255 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1258 if (!renameBuffer(b, docstring()))
1267 return saveBuffer(b);
1271 bool GuiView::closeBuffer()
1273 Buffer * buf = buffer();
1274 return buf && closeBuffer(*buf);
1278 bool GuiView::closeBuffer(Buffer & buf)
1280 if (buf.isClean() || buf.paragraphs().empty()) {
1281 theBufferList().release(&buf);
1284 // Switch to this Buffer.
1289 if (buf.isUnnamed())
1290 file = from_utf8(buf.fileName().onlyFileName());
1292 file = buf.fileName().displayName(30);
1294 docstring const text = bformat(_("The document %1$s has unsaved changes."
1295 "\n\nDo you want to save the document or discard the changes?"), file);
1296 int const ret = Alert::prompt(_("Save changed document?"),
1297 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1301 if (!saveBuffer(buf))
1305 // if we crash after this we could
1306 // have no autosave file but I guess
1307 // this is really improbable (Jug)
1308 removeAutosaveFile(buf.absFileName());
1314 // save file names to .lyx/session
1315 // if master/slave are both open, do not save slave since it
1316 // will be automatically loaded when the master is loaded
1317 if (buf.masterBuffer() == &buf)
1318 LyX::ref().session().lastOpened().add(buf.fileName());
1320 theBufferList().release(&buf);
1325 bool GuiView::quitWriteAll()
1327 while (!theBufferList().empty()) {
1328 Buffer * b = theBufferList().first();
1329 if (!closeBuffer(*b))
1336 bool GuiView::dispatch(FuncRequest const & cmd)
1338 BufferView * bv = view();
1339 // By default we won't need any update.
1341 bv->cursor().updateFlags(Update::None);
1343 switch(cmd.action) {
1344 case LFUN_BUFFER_SWITCH:
1345 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1348 case LFUN_BUFFER_NEXT:
1349 setBuffer(theBufferList().next(buffer()));
1352 case LFUN_BUFFER_PREVIOUS:
1353 setBuffer(theBufferList().previous(buffer()));
1356 case LFUN_COMMAND_EXECUTE: {
1357 bool const show_it = cmd.argument() != "off";
1358 d.toolbars_->showCommandBuffer(show_it);
1361 case LFUN_DROP_LAYOUTS_CHOICE:
1363 d.layout_->showPopup();
1366 case LFUN_MENU_OPEN:
1367 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument())))
1368 menu->exec(QCursor::pos());
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"