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)
1056 FileName initpath(lyxrc.document_path);
1057 Buffer * buf = buffer();
1059 FileName const trypath(buf->filePath());
1060 // If directory is writeable, use this as default.
1061 if (trypath.isDirWritable())
1065 string templatefile = from_template ?
1066 selectTemplateFile().absFilename() : string();
1068 if (filename.empty())
1069 b = newUnnamedFile(templatefile, initpath);
1071 b = newFile(filename, templatefile, true);
1078 void GuiView::insertLyXFile(docstring const & fname)
1080 BufferView * bv = view();
1085 FileName filename(to_utf8(fname));
1087 if (!filename.empty()) {
1088 bv->insertLyXFile(filename);
1092 // Launch a file browser
1094 string initpath = lyxrc.document_path;
1095 string const trypath = bv->buffer().filePath();
1096 // If directory is writeable, use this as default.
1097 if (FileName(trypath).isDirWritable())
1101 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1102 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1103 dlg.setButton2(_("Examples|#E#e"),
1104 from_utf8(addPath(package().system_support().absFilename(),
1107 FileDialog::Result result =
1108 dlg.open(from_utf8(initpath),
1109 FileFilterList(_("LyX Documents (*.lyx)")),
1112 if (result.first == FileDialog::Later)
1116 filename.set(to_utf8(result.second));
1118 // check selected filename
1119 if (filename.empty()) {
1120 // emit message signal.
1121 message(_("Canceled."));
1125 bv->insertLyXFile(filename);
1129 void GuiView::insertPlaintextFile(docstring const & fname,
1132 BufferView * bv = view();
1137 FileName filename(to_utf8(fname));
1139 if (!filename.empty()) {
1140 bv->insertPlaintextFile(filename, asParagraph);
1144 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1145 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1147 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1148 FileFilterList(), docstring());
1150 if (result.first == FileDialog::Later)
1154 filename.set(to_utf8(result.second));
1156 // check selected filename
1157 if (filename.empty()) {
1158 // emit message signal.
1159 message(_("Canceled."));
1163 bv->insertPlaintextFile(filename, asParagraph);
1167 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1169 FileName fname = b.fileName();
1170 FileName const oldname = fname;
1172 if (!newname.empty()) {
1174 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1176 // Switch to this Buffer.
1179 /// No argument? Ask user through dialog.
1181 FileDialog dlg(_("Choose a filename to save document as"),
1182 LFUN_BUFFER_WRITE_AS);
1183 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1184 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1186 if (!support::isLyXFilename(fname.absFilename()))
1187 fname.changeExtension(".lyx");
1189 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1191 FileDialog::Result result =
1192 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1194 from_utf8(fname.onlyFileName()));
1196 if (result.first == FileDialog::Later)
1199 fname.set(to_utf8(result.second));
1204 if (!support::isLyXFilename(fname.absFilename()))
1205 fname.changeExtension(".lyx");
1208 if (FileName(fname).exists()) {
1209 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1210 docstring text = bformat(_("The document %1$s already "
1211 "exists.\n\nDo you want to "
1212 "overwrite that document?"),
1214 int const ret = Alert::prompt(_("Overwrite document?"),
1215 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1218 case 1: return renameBuffer(b, docstring());
1219 case 2: return false;
1223 // Ok, change the name of the buffer
1224 b.setFileName(fname.absFilename());
1226 bool unnamed = b.isUnnamed();
1227 b.setUnnamed(false);
1228 b.saveCheckSum(fname);
1230 if (!saveBuffer(b)) {
1231 b.setFileName(oldname.absFilename());
1232 b.setUnnamed(unnamed);
1233 b.saveCheckSum(oldname);
1241 bool GuiView::saveBuffer(Buffer & b)
1244 return renameBuffer(b, docstring());
1247 LyX::ref().session().lastFiles().add(b.fileName());
1251 // Switch to this Buffer.
1254 // FIXME: we don't tell the user *WHY* the save failed !!
1255 docstring const file = makeDisplayPath(b.absFileName(), 30);
1256 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1257 "Do you want to rename the document and "
1258 "try again?"), file);
1259 int const ret = Alert::prompt(_("Rename and save?"),
1260 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1263 if (!renameBuffer(b, docstring()))
1272 return saveBuffer(b);
1276 bool GuiView::closeBuffer()
1278 Buffer * buf = buffer();
1279 return buf && closeBuffer(*buf);
1283 bool GuiView::closeBuffer(Buffer & buf)
1285 if (buf.isClean() || buf.paragraphs().empty()) {
1286 theBufferList().release(&buf);
1289 // Switch to this Buffer.
1294 if (buf.isUnnamed())
1295 file = from_utf8(buf.fileName().onlyFileName());
1297 file = buf.fileName().displayName(30);
1299 docstring const text = bformat(_("The document %1$s has unsaved changes."
1300 "\n\nDo you want to save the document or discard the changes?"), file);
1301 int const ret = Alert::prompt(_("Save changed document?"),
1302 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1306 if (!saveBuffer(buf))
1310 // if we crash after this we could
1311 // have no autosave file but I guess
1312 // this is really improbable (Jug)
1313 removeAutosaveFile(buf.absFileName());
1319 // save file names to .lyx/session
1320 // if master/slave are both open, do not save slave since it
1321 // will be automatically loaded when the master is loaded
1322 if (buf.masterBuffer() == &buf)
1323 LyX::ref().session().lastOpened().add(buf.fileName());
1325 theBufferList().release(&buf);
1330 bool GuiView::quitWriteAll()
1332 while (!theBufferList().empty()) {
1333 Buffer * b = theBufferList().first();
1334 if (!closeBuffer(*b))
1341 bool GuiView::dispatch(FuncRequest const & cmd)
1343 BufferView * bv = view();
1344 // By default we won't need any update.
1346 bv->cursor().updateFlags(Update::None);
1348 switch(cmd.action) {
1349 case LFUN_BUFFER_SWITCH:
1350 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1353 case LFUN_BUFFER_NEXT:
1354 setBuffer(theBufferList().next(buffer()));
1357 case LFUN_BUFFER_PREVIOUS:
1358 setBuffer(theBufferList().previous(buffer()));
1361 case LFUN_COMMAND_EXECUTE: {
1362 bool const show_it = cmd.argument() != "off";
1363 d.toolbars_->showCommandBuffer(show_it);
1366 case LFUN_DROP_LAYOUTS_CHOICE:
1368 d.layout_->showPopup();
1371 case LFUN_MENU_OPEN:
1372 d.menubar_->openByName(toqstr(cmd.argument()));
1375 case LFUN_FILE_INSERT:
1376 insertLyXFile(cmd.argument());
1378 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1379 insertPlaintextFile(cmd.argument(), true);
1382 case LFUN_FILE_INSERT_PLAINTEXT:
1383 insertPlaintextFile(cmd.argument(), false);
1386 case LFUN_BUFFER_WRITE:
1388 saveBuffer(bv->buffer());
1391 case LFUN_BUFFER_WRITE_AS:
1393 renameBuffer(bv->buffer(), cmd.argument());
1396 case LFUN_BUFFER_WRITE_ALL: {
1397 Buffer * first = theBufferList().first();
1400 message(_("Saving all documents..."));
1401 // We cannot use a for loop as the buffer list cycles.
1407 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1408 b = theBufferList().next(b);
1409 } while (b != first);
1410 message(_("All documents saved."));
1414 case LFUN_TOOLBAR_TOGGLE: {
1415 string const name = cmd.getArg(0);
1416 bool const allowauto = cmd.getArg(1) == "allowauto";
1417 // it is possible to get current toolbar status like this,...
1418 // but I decide to obey the order of ToolbarBackend::flags
1419 // and disregard real toolbar status.
1420 // toolbars_->saveToolbarInfo();
1422 // toggle state on/off/auto
1423 d.toolbars_->toggleToolbarState(name, allowauto);
1427 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1429 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1433 if (tbi->flags & ToolbarInfo::ON)
1435 else if (tbi->flags & ToolbarInfo::OFF)
1437 else if (tbi->flags & ToolbarInfo::AUTO)
1440 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1441 _(tbi->gui_name), state));
1445 case LFUN_DIALOG_UPDATE: {
1446 string const name = to_utf8(cmd.argument());
1447 // Can only update a dialog connected to an existing inset
1448 Inset * inset = getOpenInset(name);
1450 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1451 inset->dispatch(view()->cursor(), fr);
1452 } else if (name == "paragraph") {
1453 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1454 } else if (name == "prefs") {
1455 updateDialog(name, string());
1460 case LFUN_DIALOG_TOGGLE: {
1461 if (isDialogVisible(cmd.getArg(0)))
1462 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1464 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1468 case LFUN_DIALOG_DISCONNECT_INSET:
1469 disconnectDialog(to_utf8(cmd.argument()));
1472 case LFUN_DIALOG_HIDE: {
1475 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1479 case LFUN_DIALOG_SHOW: {
1480 string const name = cmd.getArg(0);
1481 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1483 if (name == "character") {
1484 data = freefont2string();
1486 showDialog("character", data);
1487 } else if (name == "latexlog") {
1488 Buffer::LogType type;
1489 string const logfile = buffer()->logName(&type);
1491 case Buffer::latexlog:
1494 case Buffer::buildlog:
1498 data += Lexer::quoteString(logfile);
1499 showDialog("log", data);
1500 } else if (name == "vclog") {
1501 string const data = "vc " +
1502 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1503 showDialog("log", data);
1505 showDialog(name, data);
1509 case LFUN_INSET_APPLY: {
1510 string const name = cmd.getArg(0);
1511 Inset * inset = getOpenInset(name);
1513 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1514 inset->dispatch(view()->cursor(), fr);
1516 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1530 Buffer const * GuiView::updateInset(Inset const * inset)
1532 if (!d.current_work_area_)
1536 d.current_work_area_->scheduleRedraw();
1538 return &d.current_work_area_->bufferView().buffer();
1542 void GuiView::restartCursor()
1544 /* When we move around, or type, it's nice to be able to see
1545 * the cursor immediately after the keypress.
1547 if (d.current_work_area_)
1548 d.current_work_area_->startBlinkingCursor();
1550 // Take this occasion to update the toobars and layout list.
1557 // This list should be kept in sync with the list of insets in
1558 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1559 // dialog should have the same name as the inset.
1561 char const * const dialognames[] = {
1562 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1563 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1564 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1565 "mathdelimiter", "mathmatrix", "note", "paragraph",
1566 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1568 #ifdef HAVE_LIBAIKSAURUS
1572 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1574 char const * const * const end_dialognames =
1575 dialognames + (sizeof(dialognames) / sizeof(char *));
1579 cmpCStr(char const * name) : name_(name) {}
1580 bool operator()(char const * other) {
1581 return strcmp(other, name_) == 0;
1588 bool isValidName(string const & name)
1590 return std::find_if(dialognames, end_dialognames,
1591 cmpCStr(name.c_str())) != end_dialognames;
1597 void GuiView::resetDialogs()
1599 // Make sure that no LFUN uses any LyXView.
1600 theLyXFunc().setLyXView(0);
1601 d.toolbars_->init();
1604 d.layout_->updateContents(true);
1605 // Now update controls with current buffer.
1606 theLyXFunc().setLyXView(this);
1611 Dialog * GuiView::find_or_build(string const & name)
1613 if (!isValidName(name))
1616 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1618 if (it != d.dialogs_.end())
1619 return it->second.get();
1621 d.dialogs_[name].reset(build(name));
1622 return d.dialogs_[name].get();
1626 void GuiView::showDialog(string const & name, string const & data,
1633 Dialog * dialog = find_or_build(name);
1635 dialog->showData(data);
1637 d.open_insets_[name] = inset;
1643 bool GuiView::isDialogVisible(string const & name) const
1645 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1646 if (it == d.dialogs_.end())
1648 return it->second.get()->isVisibleView();
1652 void GuiView::hideDialog(string const & name, Inset * inset)
1654 // Don't send the signal if we are quitting, because on MSVC it is
1655 // destructed before the cut stack in CutAndPaste.cpp, and this method
1656 // is called from some inset destructor if the cut stack is not empty
1661 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1662 if (it == d.dialogs_.end())
1665 if (inset && inset != getOpenInset(name))
1668 Dialog * const dialog = it->second.get();
1669 if (dialog->isVisibleView())
1671 d.open_insets_[name] = 0;
1675 void GuiView::disconnectDialog(string const & name)
1677 if (!isValidName(name))
1680 if (d.open_insets_.find(name) != d.open_insets_.end())
1681 d.open_insets_[name] = 0;
1685 Inset * GuiView::getOpenInset(string const & name) const
1687 if (!isValidName(name))
1690 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1691 return it == d.open_insets_.end() ? 0 : it->second;
1695 void GuiView::hideAll() const
1697 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1698 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1700 for(; it != end; ++it)
1705 void GuiView::hideBufferDependent() const
1707 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1708 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1710 for(; it != end; ++it) {
1711 Dialog * dialog = it->second.get();
1712 if (dialog->isBufferDependent())
1718 void GuiView::updateBufferDependent(bool switched) const
1720 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1721 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1723 for(; it != end; ++it) {
1724 Dialog * dialog = it->second.get();
1725 if (!dialog->isVisibleView())
1727 if (switched && dialog->isBufferDependent()) {
1728 if (dialog->initialiseParams(""))
1729 dialog->updateView();
1733 // A bit clunky, but the dialog will request
1734 // that the kernel provides it with the necessary
1736 dialog->updateDialog();
1742 void GuiView::checkStatus()
1744 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1745 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1747 for(; it != end; ++it) {
1748 Dialog * const dialog = it->second.get();
1749 if (dialog && dialog->isVisibleView())
1750 dialog->checkStatus();
1756 // will be replaced by a proper factory...
1757 Dialog * createGuiAbout(GuiView & lv);
1758 Dialog * createGuiBibitem(GuiView & lv);
1759 Dialog * createGuiBibtex(GuiView & lv);
1760 Dialog * createGuiBox(GuiView & lv);
1761 Dialog * createGuiBranch(GuiView & lv);
1762 Dialog * createGuiChanges(GuiView & lv);
1763 Dialog * createGuiCharacter(GuiView & lv);
1764 Dialog * createGuiCitation(GuiView & lv);
1765 Dialog * createGuiDelimiter(GuiView & lv);
1766 Dialog * createGuiDocument(GuiView & lv);
1767 Dialog * createGuiErrorList(GuiView & lv);
1768 Dialog * createGuiERT(GuiView & lv);
1769 Dialog * createGuiExternal(GuiView & lv);
1770 Dialog * createGuiFloat(GuiView & lv);
1771 Dialog * createGuiGraphics(GuiView & lv);
1772 Dialog * createGuiInclude(GuiView & lv);
1773 Dialog * createGuiIndex(GuiView & lv);
1774 Dialog * createGuiLabel(GuiView & lv);
1775 Dialog * createGuiListings(GuiView & lv);
1776 Dialog * createGuiLog(GuiView & lv);
1777 Dialog * createGuiMathMatrix(GuiView & lv);
1778 Dialog * createGuiNomenclature(GuiView & lv);
1779 Dialog * createGuiNote(GuiView & lv);
1780 Dialog * createGuiParagraph(GuiView & lv);
1781 Dialog * createGuiPreferences(GuiView & lv);
1782 Dialog * createGuiPrint(GuiView & lv);
1783 Dialog * createGuiRef(GuiView & lv);
1784 Dialog * createGuiSearch(GuiView & lv);
1785 Dialog * createGuiSendTo(GuiView & lv);
1786 Dialog * createGuiShowFile(GuiView & lv);
1787 Dialog * createGuiSpellchecker(GuiView & lv);
1788 Dialog * createGuiTabularCreate(GuiView & lv);
1789 Dialog * createGuiTabular(GuiView & lv);
1790 Dialog * createGuiTexInfo(GuiView & lv);
1791 Dialog * createGuiToc(GuiView & lv);
1792 Dialog * createGuiThesaurus(GuiView & lv);
1793 Dialog * createGuiHyperlink(GuiView & lv);
1794 Dialog * createGuiVSpace(GuiView & lv);
1795 Dialog * createGuiViewSource(GuiView & lv);
1796 Dialog * createGuiWrap(GuiView & lv);
1799 Dialog * GuiView::build(string const & name)
1801 BOOST_ASSERT(isValidName(name));
1803 if (name == "aboutlyx")
1804 return createGuiAbout(*this);
1805 if (name == "bibitem")
1806 return createGuiBibitem(*this);
1807 if (name == "bibtex")
1808 return createGuiBibtex(*this);
1810 return createGuiBox(*this);
1811 if (name == "branch")
1812 return createGuiBranch(*this);
1813 if (name == "changes")
1814 return createGuiChanges(*this);
1815 if (name == "character")
1816 return createGuiCharacter(*this);
1817 if (name == "citation")
1818 return createGuiCitation(*this);
1819 if (name == "document")
1820 return createGuiDocument(*this);
1821 if (name == "errorlist")
1822 return createGuiErrorList(*this);
1824 return createGuiERT(*this);
1825 if (name == "external")
1826 return createGuiExternal(*this);
1828 return createGuiShowFile(*this);
1829 if (name == "findreplace")
1830 return createGuiSearch(*this);
1831 if (name == "float")
1832 return createGuiFloat(*this);
1833 if (name == "graphics")
1834 return createGuiGraphics(*this);
1835 if (name == "include")
1836 return createGuiInclude(*this);
1837 if (name == "index")
1838 return createGuiIndex(*this);
1839 if (name == "nomenclature")
1840 return createGuiNomenclature(*this);
1841 if (name == "label")
1842 return createGuiLabel(*this);
1844 return createGuiLog(*this);
1845 if (name == "view-source")
1846 return createGuiViewSource(*this);
1847 if (name == "mathdelimiter")
1848 return createGuiDelimiter(*this);
1849 if (name == "mathmatrix")
1850 return createGuiMathMatrix(*this);
1852 return createGuiNote(*this);
1853 if (name == "paragraph")
1854 return createGuiParagraph(*this);
1855 if (name == "prefs")
1856 return createGuiPreferences(*this);
1857 if (name == "print")
1858 return createGuiPrint(*this);
1860 return createGuiRef(*this);
1861 if (name == "sendto")
1862 return createGuiSendTo(*this);
1863 if (name == "spellchecker")
1864 return createGuiSpellchecker(*this);
1865 if (name == "tabular")
1866 return createGuiTabular(*this);
1867 if (name == "tabularcreate")
1868 return createGuiTabularCreate(*this);
1869 if (name == "texinfo")
1870 return createGuiTexInfo(*this);
1871 #ifdef HAVE_LIBAIKSAURUS
1872 if (name == "thesaurus")
1873 return createGuiThesaurus(*this);
1876 return createGuiToc(*this);
1878 return createGuiHyperlink(*this);
1879 if (name == "vspace")
1880 return createGuiVSpace(*this);
1882 return createGuiWrap(*this);
1883 if (name == "listings")
1884 return createGuiListings(*this);
1890 } // namespace frontend
1893 #include "GuiView_moc.cpp"