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);
578 case QEvent::ShortcutOverride: {
579 if (d.current_work_area_)
580 // Nothing special to do.
581 return QMainWindow::event(e);
583 // Allow processing of shortcuts that are allowed even when no Buffer
585 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
586 theLyXFunc().setLyXView(this);
588 setKeySymbol(&sym, ke);
589 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
595 return QMainWindow::event(e);
600 bool GuiView::focusNextPrevChild(bool /*next*/)
607 void GuiView::setBusy(bool yes)
609 if (d.current_work_area_) {
610 d.current_work_area_->setUpdatesEnabled(!yes);
612 d.current_work_area_->stopBlinkingCursor();
614 d.current_work_area_->startBlinkingCursor();
618 QApplication::setOverrideCursor(Qt::WaitCursor);
620 QApplication::restoreOverrideCursor();
624 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
626 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
628 if (tbinfo.flags & ToolbarInfo::TOP) {
630 addToolBarBreak(Qt::TopToolBarArea);
631 addToolBar(Qt::TopToolBarArea, toolBar);
634 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
635 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
636 #if (QT_VERSION >= 0x040202)
638 addToolBarBreak(Qt::BottomToolBarArea);
640 addToolBar(Qt::BottomToolBarArea, toolBar);
643 if (tbinfo.flags & ToolbarInfo::LEFT) {
644 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
645 #if (QT_VERSION >= 0x040202)
647 addToolBarBreak(Qt::LeftToolBarArea);
649 addToolBar(Qt::LeftToolBarArea, toolBar);
652 if (tbinfo.flags & ToolbarInfo::RIGHT) {
653 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
654 #if (QT_VERSION >= 0x040202)
656 addToolBarBreak(Qt::RightToolBarArea);
658 addToolBar(Qt::RightToolBarArea, toolBar);
661 // The following does not work so I cannot restore to exact toolbar location
663 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
664 toolBar->move(tbinfo.posx, tbinfo.posy);
671 GuiWorkArea * GuiView::workArea(Buffer & buffer)
673 for (int i = 0; i != d.splitter_->count(); ++i) {
674 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
682 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
685 // Automatically create a TabWorkArea if there are none yet.
686 if (!d.splitter_->count())
689 TabWorkArea * tab_widget = d.currentTabWorkArea();
690 return tab_widget->addWorkArea(buffer, *this);
694 void GuiView::addTabWorkArea()
696 TabWorkArea * twa = new TabWorkArea;
697 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
698 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
699 d.splitter_->addWidget(twa);
700 d.stack_widget_->setCurrentWidget(d.splitter_);
704 GuiWorkArea const * GuiView::currentWorkArea() const
706 return d.current_work_area_;
710 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
714 // Changing work area can result from opening a file so
715 // update the toc in any case.
718 d.current_work_area_ = wa;
719 for (int i = 0; i != d.splitter_->count(); ++i) {
720 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
726 void GuiView::removeWorkArea(GuiWorkArea * wa)
729 if (wa == d.current_work_area_) {
731 disconnectBufferView();
732 hideBufferDependent();
733 d.current_work_area_ = 0;
736 for (int i = 0; i != d.splitter_->count(); ++i) {
737 TabWorkArea * twa = d.tabWorkArea(i);
738 if (!twa->removeWorkArea(wa))
739 // Not found in this tab group.
742 // We found and removed the GuiWorkArea.
744 // No more WorkAreas in this tab group, so delete it.
749 if (d.current_work_area_)
750 // This means that we are not closing the current GuiWorkArea;
753 // Switch to the next GuiWorkArea in the found TabWorkArea.
754 d.current_work_area_ = twa->currentWorkArea();
758 if (d.splitter_->count() == 0)
759 // No more work area, switch to the background widget.
764 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
770 void GuiView::updateLayoutList()
773 d.layout_->updateContents(false);
777 void GuiView::updateToolbars()
779 if (d.current_work_area_) {
781 d.current_work_area_->bufferView().cursor().inMathed();
783 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
785 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
786 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
787 bool const mathmacrotemplate =
788 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
790 d.toolbars_->update(math, table, review, mathmacrotemplate);
792 d.toolbars_->update(false, false, false, false);
794 // update read-only status of open dialogs.
799 Buffer * GuiView::buffer()
801 if (d.current_work_area_)
802 return &d.current_work_area_->bufferView().buffer();
807 Buffer const * GuiView::buffer() const
809 if (d.current_work_area_)
810 return &d.current_work_area_->bufferView().buffer();
815 void GuiView::setBuffer(Buffer * newBuffer)
817 BOOST_ASSERT(newBuffer);
820 GuiWorkArea * wa = workArea(*newBuffer);
822 updateLabels(*newBuffer->masterBuffer());
823 wa = addWorkArea(*newBuffer);
825 //Disconnect the old buffer...there's no new one.
828 connectBuffer(*newBuffer);
829 connectBufferView(wa->bufferView());
830 setCurrentWorkArea(wa);
836 void GuiView::connectBuffer(Buffer & buf)
838 buf.setGuiDelegate(this);
842 void GuiView::disconnectBuffer()
844 if (d.current_work_area_)
845 d.current_work_area_->bufferView().setGuiDelegate(0);
849 void GuiView::connectBufferView(BufferView & bv)
851 bv.setGuiDelegate(this);
855 void GuiView::disconnectBufferView()
857 if (d.current_work_area_)
858 d.current_work_area_->bufferView().setGuiDelegate(0);
862 void GuiView::errors(string const & error_type)
864 ErrorList & el = buffer()->errorList(error_type);
866 showDialog("errorlist", error_type);
870 void GuiView::updateDialog(string const & name, string const & data)
872 if (!isDialogVisible(name))
875 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
876 if (it == d.dialogs_.end())
879 Dialog * const dialog = it->second.get();
880 if (dialog->isVisibleView())
881 dialog->updateData(data);
885 BufferView * GuiView::view()
887 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
891 void GuiView::updateToc()
893 updateDialog("toc", "");
897 void GuiView::updateEmbeddedFiles()
899 updateDialog("embedding", "");
903 void GuiView::autoSave()
905 LYXERR(Debug::INFO, "Running autoSave()");
908 view()->buffer().autoSave();
912 void GuiView::resetAutosaveTimers()
915 d.autosave_timeout_.restart();
919 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
923 Buffer * buf = buffer();
925 /* In LyX/Mac, when a dialog is open, the menus of the
926 application can still be accessed without giving focus to
927 the main window. In this case, we want to disable the menu
928 entries that are buffer-related.
930 Note that this code is not perfect, as bug 1941 attests:
931 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
933 if (cmd.origin == FuncRequest::MENU && !hasFocus())
937 case LFUN_BUFFER_WRITE:
938 enable = buf && (buf->isUnnamed() || !buf->isClean());
941 case LFUN_BUFFER_WRITE_AS:
945 case LFUN_TOOLBAR_TOGGLE:
946 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
949 case LFUN_DIALOG_TOGGLE:
950 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
951 // fall through to set "enable"
952 case LFUN_DIALOG_SHOW: {
953 string const name = cmd.getArg(0);
955 enable = name == "aboutlyx"
956 || name == "file" //FIXME: should be removed.
958 || name == "texinfo";
959 else if (name == "print")
960 enable = buf->isExportable("dvi")
961 && lyxrc.print_command != "none";
962 else if (name == "character") {
966 InsetCode ic = view()->cursor().inset().lyxCode();
967 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
970 else if (name == "latexlog")
971 enable = FileName(buf->logName()).isReadableFile();
972 else if (name == "spellchecker")
973 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
974 enable = !buf->isReadonly();
978 else if (name == "vclog")
979 enable = buf->lyxvc().inUse();
983 case LFUN_DIALOG_UPDATE: {
984 string const name = cmd.getArg(0);
986 enable = name == "prefs";
990 case LFUN_INSET_APPLY: {
995 string const name = cmd.getArg(0);
996 Inset * inset = getOpenInset(name);
998 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1000 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1001 // Every inset is supposed to handle this
1002 BOOST_ASSERT(false);
1006 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1007 flag |= getStatus(fr);
1009 enable = flag.enabled();
1021 flag.enabled(false);
1027 static FileName selectTemplateFile()
1029 FileDialog dlg(_("Select template file"));
1030 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1031 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1033 FileDialog::Result result =
1034 dlg.open(from_utf8(lyxrc.template_path),
1035 FileFilterList(_("LyX Documents (*.lyx)")),
1038 if (result.first == FileDialog::Later)
1040 if (result.second.empty())
1042 return FileName(to_utf8(result.second));
1046 void GuiView::newDocument(string const & filename, bool from_template)
1048 FileName initpath(lyxrc.document_path);
1049 Buffer * buf = buffer();
1051 FileName const trypath(buf->filePath());
1052 // If directory is writeable, use this as default.
1053 if (trypath.isDirWritable())
1057 string templatefile = from_template ?
1058 selectTemplateFile().absFilename() : string();
1060 if (filename.empty())
1061 b = newUnnamedFile(templatefile, initpath);
1063 b = newFile(filename, templatefile, true);
1067 // Ensure the cursor is correctly positionned on screen.
1068 view()->showCursor();
1072 void GuiView::insertLyXFile(docstring const & fname)
1074 BufferView * bv = view();
1079 FileName filename(to_utf8(fname));
1081 if (!filename.empty()) {
1082 bv->insertLyXFile(filename);
1086 // Launch a file browser
1088 string initpath = lyxrc.document_path;
1089 string const trypath = bv->buffer().filePath();
1090 // If directory is writeable, use this as default.
1091 if (FileName(trypath).isDirWritable())
1095 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1096 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1097 dlg.setButton2(_("Examples|#E#e"),
1098 from_utf8(addPath(package().system_support().absFilename(),
1101 FileDialog::Result result =
1102 dlg.open(from_utf8(initpath),
1103 FileFilterList(_("LyX Documents (*.lyx)")),
1106 if (result.first == FileDialog::Later)
1110 filename.set(to_utf8(result.second));
1112 // check selected filename
1113 if (filename.empty()) {
1114 // emit message signal.
1115 message(_("Canceled."));
1119 bv->insertLyXFile(filename);
1123 void GuiView::insertPlaintextFile(docstring const & fname,
1126 BufferView * bv = view();
1131 FileName filename(to_utf8(fname));
1133 if (!filename.empty()) {
1134 bv->insertPlaintextFile(filename, asParagraph);
1138 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1139 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1141 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1142 FileFilterList(), docstring());
1144 if (result.first == FileDialog::Later)
1148 filename.set(to_utf8(result.second));
1150 // check selected filename
1151 if (filename.empty()) {
1152 // emit message signal.
1153 message(_("Canceled."));
1157 bv->insertPlaintextFile(filename, asParagraph);
1161 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1163 FileName fname = b.fileName();
1164 FileName const oldname = fname;
1166 if (!newname.empty()) {
1168 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1170 // Switch to this Buffer.
1173 /// No argument? Ask user through dialog.
1175 FileDialog dlg(_("Choose a filename to save document as"),
1176 LFUN_BUFFER_WRITE_AS);
1177 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1178 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1180 if (!isLyXFilename(fname.absFilename()))
1181 fname.changeExtension(".lyx");
1183 FileFilterList const filter(_("LyX Documents (*.lyx)"));
1185 FileDialog::Result result =
1186 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1188 from_utf8(fname.onlyFileName()));
1190 if (result.first == FileDialog::Later)
1193 fname.set(to_utf8(result.second));
1198 if (!isLyXFilename(fname.absFilename()))
1199 fname.changeExtension(".lyx");
1202 if (FileName(fname).exists()) {
1203 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1204 docstring text = bformat(_("The document %1$s already "
1205 "exists.\n\nDo you want to "
1206 "overwrite that document?"),
1208 int const ret = Alert::prompt(_("Overwrite document?"),
1209 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1212 case 1: return renameBuffer(b, docstring());
1213 case 2: return false;
1217 // Ok, change the name of the buffer
1218 b.setFileName(fname.absFilename());
1220 bool unnamed = b.isUnnamed();
1221 b.setUnnamed(false);
1222 b.saveCheckSum(fname);
1224 if (!saveBuffer(b)) {
1225 b.setFileName(oldname.absFilename());
1226 b.setUnnamed(unnamed);
1227 b.saveCheckSum(oldname);
1235 bool GuiView::saveBuffer(Buffer & b)
1238 return renameBuffer(b, docstring());
1241 LyX::ref().session().lastFiles().add(b.fileName());
1245 // Switch to this Buffer.
1248 // FIXME: we don't tell the user *WHY* the save failed !!
1249 docstring const file = makeDisplayPath(b.absFileName(), 30);
1250 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1251 "Do you want to rename the document and "
1252 "try again?"), file);
1253 int const ret = Alert::prompt(_("Rename and save?"),
1254 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1257 if (!renameBuffer(b, docstring()))
1266 return saveBuffer(b);
1270 bool GuiView::closeBuffer()
1272 Buffer * buf = buffer();
1273 return buf && closeBuffer(*buf);
1277 bool GuiView::closeBuffer(Buffer & buf)
1279 if (buf.isClean() || buf.paragraphs().empty()) {
1280 theBufferList().release(&buf);
1283 // Switch to this Buffer.
1288 if (buf.isUnnamed())
1289 file = from_utf8(buf.fileName().onlyFileName());
1291 file = buf.fileName().displayName(30);
1293 docstring const text = bformat(_("The document %1$s has unsaved changes."
1294 "\n\nDo you want to save the document or discard the changes?"), file);
1295 int const ret = Alert::prompt(_("Save changed document?"),
1296 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1300 if (!saveBuffer(buf))
1304 // if we crash after this we could
1305 // have no autosave file but I guess
1306 // this is really improbable (Jug)
1307 removeAutosaveFile(buf.absFileName());
1313 // save file names to .lyx/session
1314 // if master/slave are both open, do not save slave since it
1315 // will be automatically loaded when the master is loaded
1316 if (buf.masterBuffer() == &buf)
1317 LyX::ref().session().lastOpened().add(buf.fileName());
1319 theBufferList().release(&buf);
1324 bool GuiView::quitWriteAll()
1326 while (!theBufferList().empty()) {
1327 Buffer * b = theBufferList().first();
1328 if (!closeBuffer(*b))
1335 bool GuiView::dispatch(FuncRequest const & cmd)
1337 BufferView * bv = view();
1338 // By default we won't need any update.
1340 bv->cursor().updateFlags(Update::None);
1342 switch(cmd.action) {
1343 case LFUN_BUFFER_SWITCH:
1344 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1347 case LFUN_BUFFER_NEXT:
1348 setBuffer(theBufferList().next(buffer()));
1351 case LFUN_BUFFER_PREVIOUS:
1352 setBuffer(theBufferList().previous(buffer()));
1355 case LFUN_COMMAND_EXECUTE: {
1356 bool const show_it = cmd.argument() != "off";
1357 d.toolbars_->showCommandBuffer(show_it);
1360 case LFUN_DROP_LAYOUTS_CHOICE:
1362 d.layout_->showPopup();
1365 case LFUN_MENU_OPEN:
1366 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument())))
1367 menu->exec(QCursor::pos());
1370 case LFUN_FILE_INSERT:
1371 insertLyXFile(cmd.argument());
1373 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1374 insertPlaintextFile(cmd.argument(), true);
1377 case LFUN_FILE_INSERT_PLAINTEXT:
1378 insertPlaintextFile(cmd.argument(), false);
1381 case LFUN_BUFFER_WRITE:
1383 saveBuffer(bv->buffer());
1386 case LFUN_BUFFER_WRITE_AS:
1388 renameBuffer(bv->buffer(), cmd.argument());
1391 case LFUN_BUFFER_WRITE_ALL: {
1392 Buffer * first = theBufferList().first();
1395 message(_("Saving all documents..."));
1396 // We cannot use a for loop as the buffer list cycles.
1402 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1403 b = theBufferList().next(b);
1404 } while (b != first);
1405 message(_("All documents saved."));
1409 case LFUN_TOOLBAR_TOGGLE: {
1410 string const name = cmd.getArg(0);
1411 bool const allowauto = cmd.getArg(1) == "allowauto";
1412 // it is possible to get current toolbar status like this,...
1413 // but I decide to obey the order of ToolbarBackend::flags
1414 // and disregard real toolbar status.
1415 // toolbars_->saveToolbarInfo();
1417 // toggle state on/off/auto
1418 d.toolbars_->toggleToolbarState(name, allowauto);
1422 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1424 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1428 if (tbi->flags & ToolbarInfo::ON)
1430 else if (tbi->flags & ToolbarInfo::OFF)
1432 else if (tbi->flags & ToolbarInfo::AUTO)
1435 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1436 _(tbi->gui_name), state));
1440 case LFUN_DIALOG_UPDATE: {
1441 string const name = to_utf8(cmd.argument());
1442 // Can only update a dialog connected to an existing inset
1443 Inset * inset = getOpenInset(name);
1445 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1446 inset->dispatch(view()->cursor(), fr);
1447 } else if (name == "paragraph") {
1448 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1449 } else if (name == "prefs") {
1450 updateDialog(name, string());
1455 case LFUN_DIALOG_TOGGLE: {
1456 if (isDialogVisible(cmd.getArg(0)))
1457 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1459 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1463 case LFUN_DIALOG_DISCONNECT_INSET:
1464 disconnectDialog(to_utf8(cmd.argument()));
1467 case LFUN_DIALOG_HIDE: {
1470 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1474 case LFUN_DIALOG_SHOW: {
1475 string const name = cmd.getArg(0);
1476 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1478 if (name == "character") {
1479 data = freefont2string();
1481 showDialog("character", data);
1482 } else if (name == "latexlog") {
1483 Buffer::LogType type;
1484 string const logfile = buffer()->logName(&type);
1486 case Buffer::latexlog:
1489 case Buffer::buildlog:
1493 data += Lexer::quoteString(logfile);
1494 showDialog("log", data);
1495 } else if (name == "vclog") {
1496 string const data = "vc " +
1497 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1498 showDialog("log", data);
1500 showDialog(name, data);
1504 case LFUN_INSET_APPLY: {
1505 string const name = cmd.getArg(0);
1506 Inset * inset = getOpenInset(name);
1508 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1509 inset->dispatch(view()->cursor(), fr);
1511 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1525 Buffer const * GuiView::updateInset(Inset const * inset)
1527 if (!d.current_work_area_)
1531 d.current_work_area_->scheduleRedraw();
1533 return &d.current_work_area_->bufferView().buffer();
1537 void GuiView::restartCursor()
1539 /* When we move around, or type, it's nice to be able to see
1540 * the cursor immediately after the keypress.
1542 if (d.current_work_area_)
1543 d.current_work_area_->startBlinkingCursor();
1545 // Take this occasion to update the toobars and layout list.
1552 // This list should be kept in sync with the list of insets in
1553 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1554 // dialog should have the same name as the inset.
1556 char const * const dialognames[] = {
1557 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1558 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1559 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1560 "mathdelimiter", "mathmatrix", "note", "paragraph",
1561 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1563 #ifdef HAVE_LIBAIKSAURUS
1567 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1569 char const * const * const end_dialognames =
1570 dialognames + (sizeof(dialognames) / sizeof(char *));
1574 cmpCStr(char const * name) : name_(name) {}
1575 bool operator()(char const * other) {
1576 return strcmp(other, name_) == 0;
1583 bool isValidName(string const & name)
1585 return find_if(dialognames, end_dialognames,
1586 cmpCStr(name.c_str())) != end_dialognames;
1592 void GuiView::resetDialogs()
1594 // Make sure that no LFUN uses any LyXView.
1595 theLyXFunc().setLyXView(0);
1596 d.toolbars_->init();
1597 guiApp->menus().fillMenuBar(this);
1599 d.layout_->updateContents(true);
1600 // Now update controls with current buffer.
1601 theLyXFunc().setLyXView(this);
1606 Dialog * GuiView::find_or_build(string const & name)
1608 if (!isValidName(name))
1611 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1613 if (it != d.dialogs_.end())
1614 return it->second.get();
1616 Dialog * dialog = build(name);
1617 d.dialogs_[name].reset(dialog);
1618 if (lyxrc.allow_geometry_session)
1619 dialog->restoreSession();
1624 void GuiView::showDialog(string const & name, string const & data,
1631 Dialog * dialog = find_or_build(name);
1633 dialog->showData(data);
1635 d.open_insets_[name] = inset;
1641 bool GuiView::isDialogVisible(string const & name) const
1643 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1644 if (it == d.dialogs_.end())
1646 return it->second.get()->isVisibleView();
1650 void GuiView::hideDialog(string const & name, Inset * inset)
1652 // Don't send the signal if we are quitting, because on MSVC it is
1653 // destructed before the cut stack in CutAndPaste.cpp, and this method
1654 // is called from some inset destructor if the cut stack is not empty
1659 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1660 if (it == d.dialogs_.end())
1663 if (inset && inset != getOpenInset(name))
1666 Dialog * const dialog = it->second.get();
1667 if (dialog->isVisibleView())
1669 d.open_insets_[name] = 0;
1673 void GuiView::disconnectDialog(string const & name)
1675 if (!isValidName(name))
1678 if (d.open_insets_.find(name) != d.open_insets_.end())
1679 d.open_insets_[name] = 0;
1683 Inset * GuiView::getOpenInset(string const & name) const
1685 if (!isValidName(name))
1688 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1689 return it == d.open_insets_.end() ? 0 : it->second;
1693 void GuiView::hideAll() const
1695 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1696 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1698 for(; it != end; ++it)
1699 it->second->hideView();
1703 void GuiView::hideBufferDependent() const
1705 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1706 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1708 for(; it != end; ++it) {
1709 Dialog * dialog = it->second.get();
1710 if (dialog->isBufferDependent())
1716 void GuiView::updateBufferDependent(bool switched) const
1718 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1719 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1721 for(; it != end; ++it) {
1722 Dialog * dialog = it->second.get();
1723 if (!dialog->isVisibleView())
1725 if (switched && dialog->isBufferDependent()) {
1726 if (dialog->initialiseParams(""))
1727 dialog->updateView();
1731 // A bit clunky, but the dialog will request
1732 // that the kernel provides it with the necessary
1734 dialog->updateDialog();
1740 void GuiView::checkStatus()
1742 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1743 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1745 for(; it != end; ++it) {
1746 Dialog * const dialog = it->second.get();
1747 if (dialog && dialog->isVisibleView())
1748 dialog->checkStatus();
1754 // will be replaced by a proper factory...
1755 Dialog * createGuiAbout(GuiView & lv);
1756 Dialog * createGuiBibitem(GuiView & lv);
1757 Dialog * createGuiBibtex(GuiView & lv);
1758 Dialog * createGuiBox(GuiView & lv);
1759 Dialog * createGuiBranch(GuiView & lv);
1760 Dialog * createGuiChanges(GuiView & lv);
1761 Dialog * createGuiCharacter(GuiView & lv);
1762 Dialog * createGuiCitation(GuiView & lv);
1763 Dialog * createGuiDelimiter(GuiView & lv);
1764 Dialog * createGuiDocument(GuiView & lv);
1765 Dialog * createGuiErrorList(GuiView & lv);
1766 Dialog * createGuiERT(GuiView & lv);
1767 Dialog * createGuiExternal(GuiView & lv);
1768 Dialog * createGuiFloat(GuiView & lv);
1769 Dialog * createGuiGraphics(GuiView & lv);
1770 Dialog * createGuiInclude(GuiView & lv);
1771 Dialog * createGuiIndex(GuiView & lv);
1772 Dialog * createGuiLabel(GuiView & lv);
1773 Dialog * createGuiListings(GuiView & lv);
1774 Dialog * createGuiLog(GuiView & lv);
1775 Dialog * createGuiMathMatrix(GuiView & lv);
1776 Dialog * createGuiNomenclature(GuiView & lv);
1777 Dialog * createGuiNote(GuiView & lv);
1778 Dialog * createGuiParagraph(GuiView & lv);
1779 Dialog * createGuiPreferences(GuiView & lv);
1780 Dialog * createGuiPrint(GuiView & lv);
1781 Dialog * createGuiRef(GuiView & lv);
1782 Dialog * createGuiSearch(GuiView & lv);
1783 Dialog * createGuiSendTo(GuiView & lv);
1784 Dialog * createGuiShowFile(GuiView & lv);
1785 Dialog * createGuiSpellchecker(GuiView & lv);
1786 Dialog * createGuiTabularCreate(GuiView & lv);
1787 Dialog * createGuiTabular(GuiView & lv);
1788 Dialog * createGuiTexInfo(GuiView & lv);
1789 Dialog * createGuiToc(GuiView & lv);
1790 Dialog * createGuiThesaurus(GuiView & lv);
1791 Dialog * createGuiHyperlink(GuiView & lv);
1792 Dialog * createGuiVSpace(GuiView & lv);
1793 Dialog * createGuiViewSource(GuiView & lv);
1794 Dialog * createGuiWrap(GuiView & lv);
1797 Dialog * GuiView::build(string const & name)
1799 BOOST_ASSERT(isValidName(name));
1801 if (name == "aboutlyx")
1802 return createGuiAbout(*this);
1803 if (name == "bibitem")
1804 return createGuiBibitem(*this);
1805 if (name == "bibtex")
1806 return createGuiBibtex(*this);
1808 return createGuiBox(*this);
1809 if (name == "branch")
1810 return createGuiBranch(*this);
1811 if (name == "changes")
1812 return createGuiChanges(*this);
1813 if (name == "character")
1814 return createGuiCharacter(*this);
1815 if (name == "citation")
1816 return createGuiCitation(*this);
1817 if (name == "document")
1818 return createGuiDocument(*this);
1819 if (name == "errorlist")
1820 return createGuiErrorList(*this);
1822 return createGuiERT(*this);
1823 if (name == "external")
1824 return createGuiExternal(*this);
1826 return createGuiShowFile(*this);
1827 if (name == "findreplace")
1828 return createGuiSearch(*this);
1829 if (name == "float")
1830 return createGuiFloat(*this);
1831 if (name == "graphics")
1832 return createGuiGraphics(*this);
1833 if (name == "include")
1834 return createGuiInclude(*this);
1835 if (name == "index")
1836 return createGuiIndex(*this);
1837 if (name == "nomenclature")
1838 return createGuiNomenclature(*this);
1839 if (name == "label")
1840 return createGuiLabel(*this);
1842 return createGuiLog(*this);
1843 if (name == "view-source")
1844 return createGuiViewSource(*this);
1845 if (name == "mathdelimiter")
1846 return createGuiDelimiter(*this);
1847 if (name == "mathmatrix")
1848 return createGuiMathMatrix(*this);
1850 return createGuiNote(*this);
1851 if (name == "paragraph")
1852 return createGuiParagraph(*this);
1853 if (name == "prefs")
1854 return createGuiPreferences(*this);
1855 if (name == "print")
1856 return createGuiPrint(*this);
1858 return createGuiRef(*this);
1859 if (name == "sendto")
1860 return createGuiSendTo(*this);
1861 if (name == "spellchecker")
1862 return createGuiSpellchecker(*this);
1863 if (name == "tabular")
1864 return createGuiTabular(*this);
1865 if (name == "tabularcreate")
1866 return createGuiTabularCreate(*this);
1867 if (name == "texinfo")
1868 return createGuiTexInfo(*this);
1869 #ifdef HAVE_LIBAIKSAURUS
1870 if (name == "thesaurus")
1871 return createGuiThesaurus(*this);
1874 return createGuiToc(*this);
1876 return createGuiHyperlink(*this);
1877 if (name == "vspace")
1878 return createGuiVSpace(*this);
1880 return createGuiWrap(*this);
1881 if (name == "listings")
1882 return createGuiListings(*this);
1888 } // namespace frontend
1891 #include "GuiView_moc.cpp"