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 void GuiView::insertLyXFile(docstring const & fname)
1037 BufferView * bv = view();
1042 FileName filename(to_utf8(fname));
1044 if (!filename.empty()) {
1045 bv->insertLyXFile(filename);
1049 // Launch a file browser
1051 string initpath = lyxrc.document_path;
1052 string const trypath = bv->buffer().filePath();
1053 // If directory is writeable, use this as default.
1054 if (FileName(trypath).isDirWritable())
1058 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1059 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1060 dlg.setButton2(_("Examples|#E#e"),
1061 from_utf8(addPath(package().system_support().absFilename(),
1064 FileDialog::Result result =
1065 dlg.open(from_utf8(initpath),
1066 FileFilterList(_("LyX Documents (*.lyx)")),
1069 if (result.first == FileDialog::Later)
1073 filename.set(to_utf8(result.second));
1075 // check selected filename
1076 if (filename.empty()) {
1077 // emit message signal.
1078 message(_("Canceled."));
1082 bv->insertLyXFile(filename);
1086 void GuiView::insertPlaintextFile(docstring const & fname,
1089 BufferView * bv = view();
1094 FileName filename(to_utf8(fname));
1096 if (!filename.empty()) {
1097 bv->insertPlaintextFile(filename, asParagraph);
1101 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1102 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1104 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1105 FileFilterList(), docstring());
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->insertPlaintextFile(filename, asParagraph);
1124 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1126 FileName fname = b.fileName();
1127 FileName const oldname = fname;
1129 if (!newname.empty()) {
1131 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1133 // Switch to this Buffer.
1136 /// No argument? Ask user through dialog.
1138 FileDialog dlg(_("Choose a filename to save document as"),
1139 LFUN_BUFFER_WRITE_AS);
1140 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1141 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1143 if (!support::isLyXFilename(fname.absFilename()))
1144 fname.changeExtension(".lyx");
1146 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1148 FileDialog::Result result =
1149 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1151 from_utf8(fname.onlyFileName()));
1153 if (result.first == FileDialog::Later)
1156 fname.set(to_utf8(result.second));
1161 if (!support::isLyXFilename(fname.absFilename()))
1162 fname.changeExtension(".lyx");
1165 if (FileName(fname).exists()) {
1166 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1167 docstring text = bformat(_("The document %1$s already "
1168 "exists.\n\nDo you want to "
1169 "overwrite that document?"),
1171 int const ret = Alert::prompt(_("Overwrite document?"),
1172 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1175 case 1: return renameBuffer(b, docstring());
1176 case 2: return false;
1180 // Ok, change the name of the buffer
1181 b.setFileName(fname.absFilename());
1183 bool unnamed = b.isUnnamed();
1184 b.setUnnamed(false);
1185 b.saveCheckSum(fname);
1187 if (!saveBuffer(b)) {
1188 b.setFileName(oldname.absFilename());
1189 b.setUnnamed(unnamed);
1190 b.saveCheckSum(oldname);
1198 bool GuiView::saveBuffer(Buffer & b)
1201 return renameBuffer(b, docstring());
1204 LyX::ref().session().lastFiles().add(b.fileName());
1208 // Switch to this Buffer.
1211 // FIXME: we don't tell the user *WHY* the save failed !!
1212 docstring const file = makeDisplayPath(b.absFileName(), 30);
1213 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1214 "Do you want to rename the document and "
1215 "try again?"), file);
1216 int const ret = Alert::prompt(_("Rename and save?"),
1217 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1220 if (!renameBuffer(b, docstring()))
1229 return saveBuffer(b);
1233 bool GuiView::closeBuffer()
1235 Buffer * buf = buffer();
1236 return buf && closeBuffer(*buf);
1240 bool GuiView::closeBuffer(Buffer & buf)
1242 if (buf.isClean() || buf.paragraphs().empty()) {
1243 theBufferList().release(&buf);
1246 // Switch to this Buffer.
1251 if (buf.isUnnamed())
1252 file = from_utf8(buf.fileName().onlyFileName());
1254 file = buf.fileName().displayName(30);
1256 docstring const text = bformat(_("The document %1$s has unsaved changes."
1257 "\n\nDo you want to save the document or discard the changes?"), file);
1258 int const ret = Alert::prompt(_("Save changed document?"),
1259 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1263 if (!saveBuffer(buf))
1267 // if we crash after this we could
1268 // have no autosave file but I guess
1269 // this is really improbable (Jug)
1270 removeAutosaveFile(buf.absFileName());
1276 // save file names to .lyx/session
1277 // if master/slave are both open, do not save slave since it
1278 // will be automatically loaded when the master is loaded
1279 if (buf.masterBuffer() == &buf)
1280 LyX::ref().session().lastOpened().add(buf.fileName());
1282 theBufferList().release(&buf);
1287 bool GuiView::quitWriteAll()
1289 while (!theBufferList().empty()) {
1290 Buffer * b = theBufferList().first();
1291 if (!closeBuffer(*b))
1298 bool GuiView::dispatch(FuncRequest const & cmd)
1300 BufferView * bv = view();
1301 // By default we won't need any update.
1303 bv->cursor().updateFlags(Update::None);
1305 switch(cmd.action) {
1306 case LFUN_BUFFER_SWITCH:
1307 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1310 case LFUN_BUFFER_NEXT:
1311 setBuffer(theBufferList().next(buffer()));
1314 case LFUN_BUFFER_PREVIOUS:
1315 setBuffer(theBufferList().previous(buffer()));
1318 case LFUN_COMMAND_EXECUTE: {
1319 bool const show_it = cmd.argument() != "off";
1320 d.toolbars_->showCommandBuffer(show_it);
1323 case LFUN_DROP_LAYOUTS_CHOICE:
1325 d.layout_->showPopup();
1328 case LFUN_MENU_OPEN:
1329 d.menubar_->openByName(toqstr(cmd.argument()));
1332 case LFUN_FILE_INSERT:
1333 insertLyXFile(cmd.argument());
1335 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1336 insertPlaintextFile(cmd.argument(), true);
1339 case LFUN_FILE_INSERT_PLAINTEXT:
1340 insertPlaintextFile(cmd.argument(), false);
1343 case LFUN_BUFFER_WRITE:
1345 saveBuffer(bv->buffer());
1348 case LFUN_BUFFER_WRITE_AS:
1350 renameBuffer(bv->buffer(), cmd.argument());
1353 case LFUN_BUFFER_WRITE_ALL: {
1354 Buffer * first = theBufferList().first();
1357 message(_("Saving all documents..."));
1358 // We cannot use a for loop as the buffer list cycles.
1364 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1365 b = theBufferList().next(b);
1366 } while (b != first);
1367 message(_("All documents saved."));
1371 case LFUN_TOOLBAR_TOGGLE: {
1372 string const name = cmd.getArg(0);
1373 bool const allowauto = cmd.getArg(1) == "allowauto";
1374 // it is possible to get current toolbar status like this,...
1375 // but I decide to obey the order of ToolbarBackend::flags
1376 // and disregard real toolbar status.
1377 // toolbars_->saveToolbarInfo();
1379 // toggle state on/off/auto
1380 d.toolbars_->toggleToolbarState(name, allowauto);
1384 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1386 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1390 if (tbi->flags & ToolbarInfo::ON)
1392 else if (tbi->flags & ToolbarInfo::OFF)
1394 else if (tbi->flags & ToolbarInfo::AUTO)
1397 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1398 _(tbi->gui_name), state));
1402 case LFUN_DIALOG_UPDATE: {
1403 string const name = to_utf8(cmd.argument());
1404 // Can only update a dialog connected to an existing inset
1405 Inset * inset = getOpenInset(name);
1407 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1408 inset->dispatch(view()->cursor(), fr);
1409 } else if (name == "paragraph") {
1410 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1411 } else if (name == "prefs") {
1412 updateDialog(name, string());
1417 case LFUN_DIALOG_TOGGLE: {
1418 if (isDialogVisible(cmd.getArg(0)))
1419 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1421 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1425 case LFUN_DIALOG_DISCONNECT_INSET:
1426 disconnectDialog(to_utf8(cmd.argument()));
1429 case LFUN_DIALOG_HIDE: {
1432 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1436 case LFUN_DIALOG_SHOW: {
1437 string const name = cmd.getArg(0);
1438 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1440 if (name == "character") {
1441 data = freefont2string();
1443 showDialog("character", data);
1444 } else if (name == "latexlog") {
1445 Buffer::LogType type;
1446 string const logfile = buffer()->logName(&type);
1448 case Buffer::latexlog:
1451 case Buffer::buildlog:
1455 data += Lexer::quoteString(logfile);
1456 showDialog("log", data);
1457 } else if (name == "vclog") {
1458 string const data = "vc " +
1459 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1460 showDialog("log", data);
1462 showDialog(name, data);
1466 case LFUN_INSET_APPLY: {
1467 string const name = cmd.getArg(0);
1468 Inset * inset = getOpenInset(name);
1470 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1471 inset->dispatch(view()->cursor(), fr);
1473 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1487 Buffer const * GuiView::updateInset(Inset const * inset)
1489 if (!d.current_work_area_)
1493 d.current_work_area_->scheduleRedraw();
1495 return &d.current_work_area_->bufferView().buffer();
1499 void GuiView::restartCursor()
1501 /* When we move around, or type, it's nice to be able to see
1502 * the cursor immediately after the keypress.
1504 if (d.current_work_area_)
1505 d.current_work_area_->startBlinkingCursor();
1507 // Take this occasion to update the toobars and layout list.
1514 // This list should be kept in sync with the list of insets in
1515 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1516 // dialog should have the same name as the inset.
1518 char const * const dialognames[] = {
1519 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1520 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1521 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1522 "mathdelimiter", "mathmatrix", "note", "paragraph",
1523 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1525 #ifdef HAVE_LIBAIKSAURUS
1529 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1531 char const * const * const end_dialognames =
1532 dialognames + (sizeof(dialognames) / sizeof(char *));
1536 cmpCStr(char const * name) : name_(name) {}
1537 bool operator()(char const * other) {
1538 return strcmp(other, name_) == 0;
1545 bool isValidName(string const & name)
1547 return std::find_if(dialognames, end_dialognames,
1548 cmpCStr(name.c_str())) != end_dialognames;
1554 void GuiView::resetDialogs()
1556 // Make sure that no LFUN uses any LyXView.
1557 theLyXFunc().setLyXView(0);
1558 d.toolbars_->init();
1561 d.layout_->updateContents(true);
1562 // Now update controls with current buffer.
1563 theLyXFunc().setLyXView(this);
1568 Dialog * GuiView::find_or_build(string const & name)
1570 if (!isValidName(name))
1573 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1575 if (it != d.dialogs_.end())
1576 return it->second.get();
1578 d.dialogs_[name].reset(build(name));
1579 return d.dialogs_[name].get();
1583 void GuiView::showDialog(string const & name, string const & data,
1590 Dialog * dialog = find_or_build(name);
1592 dialog->showData(data);
1594 d.open_insets_[name] = inset;
1600 bool GuiView::isDialogVisible(string const & name) const
1602 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1603 if (it == d.dialogs_.end())
1605 return it->second.get()->isVisibleView();
1609 void GuiView::hideDialog(string const & name, Inset * inset)
1611 // Don't send the signal if we are quitting, because on MSVC it is
1612 // destructed before the cut stack in CutAndPaste.cpp, and this method
1613 // is called from some inset destructor if the cut stack is not empty
1618 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1619 if (it == d.dialogs_.end())
1622 if (inset && inset != getOpenInset(name))
1625 Dialog * const dialog = it->second.get();
1626 if (dialog->isVisibleView())
1628 d.open_insets_[name] = 0;
1632 void GuiView::disconnectDialog(string const & name)
1634 if (!isValidName(name))
1637 if (d.open_insets_.find(name) != d.open_insets_.end())
1638 d.open_insets_[name] = 0;
1642 Inset * GuiView::getOpenInset(string const & name) const
1644 if (!isValidName(name))
1647 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1648 return it == d.open_insets_.end() ? 0 : it->second;
1652 void GuiView::hideAll() const
1654 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1655 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1657 for(; it != end; ++it)
1662 void GuiView::hideBufferDependent() const
1664 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1665 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1667 for(; it != end; ++it) {
1668 Dialog * dialog = it->second.get();
1669 if (dialog->isBufferDependent())
1675 void GuiView::updateBufferDependent(bool switched) const
1677 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1678 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1680 for(; it != end; ++it) {
1681 Dialog * dialog = it->second.get();
1682 if (switched && dialog->isBufferDependent()) {
1683 if (dialog->isVisibleView() && dialog->initialiseParams(""))
1684 dialog->updateView();
1688 // A bit clunky, but the dialog will request
1689 // that the kernel provides it with the necessary
1691 dialog->updateDialog();
1697 void GuiView::checkStatus()
1699 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1700 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1702 for(; it != end; ++it) {
1703 Dialog * const dialog = it->second.get();
1704 if (dialog && dialog->isVisibleView())
1705 dialog->checkStatus();
1711 // will be replaced by a proper factory...
1712 Dialog * createGuiAbout(GuiView & lv);
1713 Dialog * createGuiBibitem(GuiView & lv);
1714 Dialog * createGuiBibtex(GuiView & lv);
1715 Dialog * createGuiBox(GuiView & lv);
1716 Dialog * createGuiBranch(GuiView & lv);
1717 Dialog * createGuiChanges(GuiView & lv);
1718 Dialog * createGuiCharacter(GuiView & lv);
1719 Dialog * createGuiCitation(GuiView & lv);
1720 Dialog * createGuiDelimiter(GuiView & lv);
1721 Dialog * createGuiDocument(GuiView & lv);
1722 Dialog * createGuiErrorList(GuiView & lv);
1723 Dialog * createGuiERT(GuiView & lv);
1724 Dialog * createGuiExternal(GuiView & lv);
1725 Dialog * createGuiFloat(GuiView & lv);
1726 Dialog * createGuiGraphics(GuiView & lv);
1727 Dialog * createGuiInclude(GuiView & lv);
1728 Dialog * createGuiIndex(GuiView & lv);
1729 Dialog * createGuiLabel(GuiView & lv);
1730 Dialog * createGuiListings(GuiView & lv);
1731 Dialog * createGuiLog(GuiView & lv);
1732 Dialog * createGuiMathMatrix(GuiView & lv);
1733 Dialog * createGuiNomenclature(GuiView & lv);
1734 Dialog * createGuiNote(GuiView & lv);
1735 Dialog * createGuiParagraph(GuiView & lv);
1736 Dialog * createGuiPreferences(GuiView & lv);
1737 Dialog * createGuiPrint(GuiView & lv);
1738 Dialog * createGuiRef(GuiView & lv);
1739 Dialog * createGuiSearch(GuiView & lv);
1740 Dialog * createGuiSendTo(GuiView & lv);
1741 Dialog * createGuiShowFile(GuiView & lv);
1742 Dialog * createGuiSpellchecker(GuiView & lv);
1743 Dialog * createGuiTabularCreate(GuiView & lv);
1744 Dialog * createGuiTabular(GuiView & lv);
1745 Dialog * createGuiTexInfo(GuiView & lv);
1746 Dialog * createGuiToc(GuiView & lv);
1747 Dialog * createGuiThesaurus(GuiView & lv);
1748 Dialog * createGuiHyperlink(GuiView & lv);
1749 Dialog * createGuiVSpace(GuiView & lv);
1750 Dialog * createGuiViewSource(GuiView & lv);
1751 Dialog * createGuiWrap(GuiView & lv);
1754 Dialog * GuiView::build(string const & name)
1756 BOOST_ASSERT(isValidName(name));
1758 if (name == "aboutlyx")
1759 return createGuiAbout(*this);
1760 if (name == "bibitem")
1761 return createGuiBibitem(*this);
1762 if (name == "bibtex")
1763 return createGuiBibtex(*this);
1765 return createGuiBox(*this);
1766 if (name == "branch")
1767 return createGuiBranch(*this);
1768 if (name == "changes")
1769 return createGuiChanges(*this);
1770 if (name == "character")
1771 return createGuiCharacter(*this);
1772 if (name == "citation")
1773 return createGuiCitation(*this);
1774 if (name == "document")
1775 return createGuiDocument(*this);
1776 if (name == "errorlist")
1777 return createGuiErrorList(*this);
1779 return createGuiERT(*this);
1780 if (name == "external")
1781 return createGuiExternal(*this);
1783 return createGuiShowFile(*this);
1784 if (name == "findreplace")
1785 return createGuiSearch(*this);
1786 if (name == "float")
1787 return createGuiFloat(*this);
1788 if (name == "graphics")
1789 return createGuiGraphics(*this);
1790 if (name == "include")
1791 return createGuiInclude(*this);
1792 if (name == "index")
1793 return createGuiIndex(*this);
1794 if (name == "nomenclature")
1795 return createGuiNomenclature(*this);
1796 if (name == "label")
1797 return createGuiLabel(*this);
1799 return createGuiLog(*this);
1800 if (name == "view-source")
1801 return createGuiViewSource(*this);
1802 if (name == "mathdelimiter")
1803 return createGuiDelimiter(*this);
1804 if (name == "mathmatrix")
1805 return createGuiMathMatrix(*this);
1807 return createGuiNote(*this);
1808 if (name == "paragraph")
1809 return createGuiParagraph(*this);
1810 if (name == "prefs")
1811 return createGuiPreferences(*this);
1812 if (name == "print")
1813 return createGuiPrint(*this);
1815 return createGuiRef(*this);
1816 if (name == "sendto")
1817 return createGuiSendTo(*this);
1818 if (name == "spellchecker")
1819 return createGuiSpellchecker(*this);
1820 if (name == "tabular")
1821 return createGuiTabular(*this);
1822 if (name == "tabularcreate")
1823 return createGuiTabularCreate(*this);
1824 if (name == "texinfo")
1825 return createGuiTexInfo(*this);
1826 #ifdef HAVE_LIBAIKSAURUS
1827 if (name == "thesaurus")
1828 return createGuiThesaurus(*this);
1831 return createGuiToc(*this);
1833 return createGuiHyperlink(*this);
1834 if (name == "vspace")
1835 return createGuiVSpace(*this);
1837 return createGuiWrap(*this);
1838 if (name == "listings")
1839 return createGuiListings(*this);
1845 } // namespace frontend
1848 #include "GuiView_moc.cpp"