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"
26 #include "GuiProgress.h"
28 #include "qt_helpers.h"
30 #include "frontends/alert.h"
32 #include "buffer_funcs.h"
34 #include "BufferList.h"
35 #include "BufferParams.h"
36 #include "BufferView.h"
38 #include "support/debug.h"
39 #include "ErrorList.h"
40 #include "FuncRequest.h"
41 #include "support/gettext.h"
49 #include "MenuBackend.h"
50 #include "Paragraph.h"
51 #include "TextClass.h"
53 #include "ToolbarBackend.h"
56 #include "support/FileFilterList.h"
57 #include "support/FileName.h"
58 #include "support/filetools.h"
59 #include "support/lstrings.h"
60 #include "support/os.h"
61 #include "support/Package.h"
62 #include "support/Timeout.h"
65 #include <QApplication>
66 #include <QCloseEvent>
68 #include <QDesktopWidget>
69 #include <QDragEnterEvent>
76 #include <QPushButton>
80 #include <QStackedWidget>
86 #include <boost/assert.hpp>
87 #include <boost/bind.hpp>
89 #ifdef HAVE_SYS_TIME_H
90 # include <sys/time.h>
102 extern bool quitting;
106 using support::addPath;
107 using support::bformat;
108 using support::FileFilterList;
109 using support::FileName;
110 using support::makeAbsPath;
111 using support::makeDisplayPath;
112 using support::package;
113 using support::removeAutosaveFile;
118 class BackgroundWidget : public QWidget
123 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
124 /// The text to be written on top of the pixmap
125 QString const text = lyx_version ? lyx_version : qt_("unknown version");
126 splash_ = QPixmap(":/images/banner.png");
128 QPainter pain(&splash_);
129 pain.setPen(QColor(255, 255, 0));
131 // The font used to display the version info
132 font.setStyleHint(QFont::SansSerif);
133 font.setWeight(QFont::Bold);
134 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
136 pain.drawText(260, 270, text);
139 void paintEvent(QPaintEvent *)
141 int x = (width() - splash_.width()) / 2;
142 int y = (height() - splash_.height()) / 2;
144 pain.drawPixmap(x, y, splash_);
154 typedef boost::shared_ptr<Dialog> DialogPtr;
156 struct GuiView::GuiViewPrivate
159 : current_work_area_(0), layout_(0),
160 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
162 // hardcode here the platform specific icon size
163 smallIconSize = 14; // scaling problems
164 normalIconSize = 20; // ok, default
165 bigIconSize = 26; // better for some math icons
167 splitter_ = new QSplitter;
168 bg_widget_ = new BackgroundWidget;
169 stack_widget_ = new QStackedWidget;
170 stack_widget_->addWidget(bg_widget_);
171 stack_widget_->addWidget(splitter_);
179 delete stack_widget_;
184 QMenu * toolBarPopup(GuiView * parent)
186 // FIXME: translation
187 QMenu * menu = new QMenu(parent);
188 QActionGroup * iconSizeGroup = new QActionGroup(parent);
190 QAction * smallIcons = new QAction(iconSizeGroup);
191 smallIcons->setText(qt_("Small-sized icons"));
192 smallIcons->setCheckable(true);
193 QObject::connect(smallIcons, SIGNAL(triggered()),
194 parent, SLOT(smallSizedIcons()));
195 menu->addAction(smallIcons);
197 QAction * normalIcons = new QAction(iconSizeGroup);
198 normalIcons->setText(qt_("Normal-sized icons"));
199 normalIcons->setCheckable(true);
200 QObject::connect(normalIcons, SIGNAL(triggered()),
201 parent, SLOT(normalSizedIcons()));
202 menu->addAction(normalIcons);
204 QAction * bigIcons = new QAction(iconSizeGroup);
205 bigIcons->setText(qt_("Big-sized icons"));
206 bigIcons->setCheckable(true);
207 QObject::connect(bigIcons, SIGNAL(triggered()),
208 parent, SLOT(bigSizedIcons()));
209 menu->addAction(bigIcons);
211 unsigned int cur = parent->iconSize().width();
212 if ( cur == parent->d.smallIconSize)
213 smallIcons->setChecked(true);
214 else if (cur == parent->d.normalIconSize)
215 normalIcons->setChecked(true);
216 else if (cur == parent->d.bigIconSize)
217 bigIcons->setChecked(true);
224 stack_widget_->setCurrentWidget(bg_widget_);
225 bg_widget_->setUpdatesEnabled(true);
228 TabWorkArea * tabWorkArea(int i)
230 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
233 TabWorkArea * currentTabWorkArea()
235 if (splitter_->count() == 1)
236 // The first TabWorkArea is always the first one, if any.
237 return tabWorkArea(0);
239 TabWorkArea * tab_widget = 0;
240 for (int i = 0; i != splitter_->count(); ++i) {
241 QWidget * w = splitter_->widget(i);
244 tab_widget = dynamic_cast<TabWorkArea *>(w);
253 GuiWorkArea * current_work_area_;
254 QSplitter * splitter_;
255 QStackedWidget * stack_widget_;
256 BackgroundWidget * bg_widget_;
258 GuiMenubar * menubar_;
260 GuiToolbars * toolbars_;
261 /// The main layout box.
263 * \warning Don't Delete! The layout box is actually owned by
264 * whichever toolbar contains it. All the GuiView class needs is a
265 * means of accessing it.
267 * FIXME: replace that with a proper model so that we are not limited
268 * to only one dialog.
270 GuiLayoutBox * layout_;
273 std::map<std::string, Inset *> open_insets_;
276 std::map<std::string, DialogPtr> dialogs_;
278 unsigned int smallIconSize;
279 unsigned int normalIconSize;
280 unsigned int bigIconSize;
282 QTimer statusbar_timer_;
283 /// are we quitting by the menu?
284 bool quitting_by_menu_;
285 /// auto-saving of buffers
286 Timeout autosave_timeout_;
287 /// flag against a race condition due to multiclicks, see bug #1119
292 GuiView::GuiView(int id)
293 : d(*new GuiViewPrivate), id_(id)
295 // GuiToolbars *must* be initialised before GuiMenubar.
296 d.toolbars_ = new GuiToolbars(*this);
297 d.menubar_ = new GuiMenubar(this, menubackend);
299 setCentralWidget(d.stack_widget_);
301 // Start autosave timer
302 if (lyxrc.autosave) {
303 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
304 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
305 d.autosave_timeout_.start();
307 connect(&d.statusbar_timer_, SIGNAL(timeout()),
308 this, SLOT(clearMessage()));
310 // Qt bug? signal lastWindowClosed does not work
311 setAttribute(Qt::WA_QuitOnClose, false);
312 setAttribute(Qt::WA_DeleteOnClose, true);
314 // assign an icon to main form. We do not do it under Qt/Mac,
315 // since the icon is provided in the application bundle.
316 setWindowIcon(QPixmap(":/images/lyx.png"));
320 setAcceptDrops(true);
322 statusBar()->setSizeGripEnabled(true);
324 // Forbid too small unresizable window because it can happen
325 // with some window manager under X11.
326 setMinimumSize(300, 200);
328 if (!lyxrc.allow_geometry_session)
329 // No session handling, default to a sane size.
330 setGeometry(50, 50, 690, 510);
332 // Now take care of session management.
334 QString const key = "view-" + QString::number(id_);
336 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
337 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
341 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
342 setGeometry(50, 50, 690, 510);
344 setIconSize(settings.value(key + "/icon_size").toSize());
354 void GuiView::close()
356 d.quitting_by_menu_ = true;
357 d.current_work_area_ = 0;
358 for (int i = 0; i != d.splitter_->count(); ++i) {
359 TabWorkArea * twa = d.tabWorkArea(i);
363 QMainWindow::close();
364 d.quitting_by_menu_ = false;
368 void GuiView::setFocus()
370 if (d.current_work_area_)
371 d.current_work_area_->setFocus();
377 QMenu * GuiView::createPopupMenu()
379 return d.toolBarPopup(this);
383 void GuiView::showEvent(QShowEvent * e)
385 LYXERR(Debug::GUI, "Passed Geometry "
386 << size().height() << "x" << size().width()
387 << "+" << pos().x() << "+" << pos().y());
389 if (d.splitter_->count() == 0)
390 // No work area, switch to the background widget.
393 QMainWindow::showEvent(e);
397 void GuiView::closeEvent(QCloseEvent * close_event)
399 // we may have been called through the close window button
400 // which bypasses the LFUN machinery.
401 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
402 if (!quitWriteAll()) {
403 close_event->ignore();
408 // Make sure that no LFUN use this close to be closed View.
409 theLyXFunc().setLyXView(0);
410 // Make sure the timer time out will not trigger a statusbar update.
411 d.statusbar_timer_.stop();
413 if (lyxrc.allow_geometry_session) {
415 QString const key = "view-" + QString::number(id_);
417 settings.setValue(key + "/pos", pos());
418 settings.setValue(key + "/size", size());
420 settings.setValue(key + "/geometry", saveGeometry());
422 settings.setValue(key + "/icon_size", iconSize());
423 d.toolbars_->saveToolbarInfo();
426 guiApp->unregisterView(id_);
427 if (guiApp->viewCount() > 0) {
428 // Just close the window and do nothing else if this is not the
430 close_event->accept();
436 // this is the place where we leave the frontend.
437 // it is the only point at which we start quitting.
438 close_event->accept();
439 // quit the event loop
444 void GuiView::dragEnterEvent(QDragEnterEvent * event)
446 if (event->mimeData()->hasUrls())
448 /// \todo Ask lyx-devel is this is enough:
449 /// if (event->mimeData()->hasFormat("text/plain"))
450 /// event->acceptProposedAction();
454 void GuiView::dropEvent(QDropEvent* event)
456 QList<QUrl> files = event->mimeData()->urls();
460 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
461 for (int i = 0; i != files.size(); ++i) {
462 string const file = support::os::internal_path(fromqstr(
463 files.at(i).toLocalFile()));
465 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
470 void GuiView::message(docstring const & str)
472 statusBar()->showMessage(toqstr(str));
473 d.statusbar_timer_.stop();
474 d.statusbar_timer_.start(3000);
478 void GuiView::smallSizedIcons()
480 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
484 void GuiView::normalSizedIcons()
486 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
490 void GuiView::bigSizedIcons()
492 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
496 void GuiView::clearMessage()
500 theLyXFunc().setLyXView(this);
501 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
502 d.statusbar_timer_.stop();
506 void GuiView::updateWindowTitle(GuiWorkArea * wa)
508 if (wa != d.current_work_area_)
510 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
511 setWindowIconText(wa->windowIconText());
515 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
518 disconnectBufferView();
519 connectBufferView(wa->bufferView());
520 connectBuffer(wa->bufferView().buffer());
521 d.current_work_area_ = wa;
522 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
523 this, SLOT(updateWindowTitle(GuiWorkArea *)));
524 updateWindowTitle(wa);
527 // Buffer-dependent dialogs should be updated or
528 // hidden. This should go here because some dialogs (eg ToC)
529 // require bv_->text.
530 updateBufferDependent(true);
537 void GuiView::updateStatusBar()
539 // let the user see the explicit message
540 if (d.statusbar_timer_.isActive())
543 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
547 bool GuiView::hasFocus() const
549 return qApp->activeWindow() == this;
553 bool GuiView::event(QEvent * e)
557 // Useful debug code:
558 //case QEvent::ActivationChange:
559 //case QEvent::WindowDeactivate:
560 //case QEvent::Paint:
561 //case QEvent::Enter:
562 //case QEvent::Leave:
563 //case QEvent::HoverEnter:
564 //case QEvent::HoverLeave:
565 //case QEvent::HoverMove:
566 //case QEvent::StatusTip:
567 //case QEvent::DragEnter:
568 //case QEvent::DragLeave:
572 case QEvent::WindowActivate: {
573 guiApp->setCurrentView(*this);
574 if (d.current_work_area_) {
575 BufferView & bv = d.current_work_area_->bufferView();
576 connectBufferView(bv);
577 connectBuffer(bv.buffer());
578 // The document structure, name and dialogs might have
579 // changed in another view.
580 updateBufferDependent(true);
582 setWindowTitle(qt_("LyX"));
583 setWindowIconText(qt_("LyX"));
585 return QMainWindow::event(e);
587 case QEvent::ShortcutOverride: {
588 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
589 if (!d.current_work_area_) {
590 theLyXFunc().setLyXView(this);
592 setKeySymbol(&sym, ke);
593 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
597 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
599 setKeySymbol(&sym, ke);
600 d.current_work_area_->processKeySym(sym, NoModifier);
606 return QMainWindow::event(e);
611 bool GuiView::focusNextPrevChild(bool /*next*/)
618 void GuiView::setBusy(bool yes)
620 if (d.current_work_area_) {
621 d.current_work_area_->setUpdatesEnabled(!yes);
623 d.current_work_area_->stopBlinkingCursor();
625 d.current_work_area_->startBlinkingCursor();
629 QApplication::setOverrideCursor(Qt::WaitCursor);
631 QApplication::restoreOverrideCursor();
635 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
637 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
639 if (tbinfo.flags & ToolbarInfo::TOP) {
641 addToolBarBreak(Qt::TopToolBarArea);
642 addToolBar(Qt::TopToolBarArea, toolBar);
645 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
646 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
647 #if (QT_VERSION >= 0x040202)
649 addToolBarBreak(Qt::BottomToolBarArea);
651 addToolBar(Qt::BottomToolBarArea, toolBar);
654 if (tbinfo.flags & ToolbarInfo::LEFT) {
655 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
656 #if (QT_VERSION >= 0x040202)
658 addToolBarBreak(Qt::LeftToolBarArea);
660 addToolBar(Qt::LeftToolBarArea, toolBar);
663 if (tbinfo.flags & ToolbarInfo::RIGHT) {
664 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
665 #if (QT_VERSION >= 0x040202)
667 addToolBarBreak(Qt::RightToolBarArea);
669 addToolBar(Qt::RightToolBarArea, toolBar);
672 // The following does not work so I cannot restore to exact toolbar location
674 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
675 toolBar->move(tbinfo.posx, tbinfo.posy);
682 GuiWorkArea * GuiView::workArea(Buffer & buffer)
684 for (int i = 0; i != d.splitter_->count(); ++i) {
685 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
693 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
696 // Automatically create a TabWorkArea if there are none yet.
697 if (!d.splitter_->count())
700 TabWorkArea * tab_widget = d.currentTabWorkArea();
701 return tab_widget->addWorkArea(buffer, *this);
705 void GuiView::addTabWorkArea()
707 TabWorkArea * twa = new TabWorkArea;
708 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
709 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
710 d.splitter_->addWidget(twa);
711 d.stack_widget_->setCurrentWidget(d.splitter_);
715 GuiWorkArea const * GuiView::currentWorkArea() const
717 return d.current_work_area_;
721 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
725 // Changing work area can result from opening a file so
726 // update the toc in any case.
729 d.current_work_area_ = wa;
730 for (int i = 0; i != d.splitter_->count(); ++i) {
731 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
737 void GuiView::removeWorkArea(GuiWorkArea * wa)
740 if (wa == d.current_work_area_) {
742 disconnectBufferView();
743 hideBufferDependent();
744 d.current_work_area_ = 0;
747 for (int i = 0; i != d.splitter_->count(); ++i) {
748 TabWorkArea * twa = d.tabWorkArea(i);
749 if (!twa->removeWorkArea(wa))
750 // Not found in this tab group.
753 // We found and removed the GuiWorkArea.
755 // No more WorkAreas in this tab group, so delete it.
760 if (d.current_work_area_)
761 // This means that we are not closing the current GuiWorkArea;
764 // Switch to the next GuiWorkArea in the found TabWorkArea.
765 d.current_work_area_ = twa->currentWorkArea();
769 if (d.splitter_->count() == 0)
770 // No more work area, switch to the background widget.
775 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
781 void GuiView::updateLayoutList()
784 d.layout_->updateContents(false);
788 void GuiView::updateToolbars()
790 if (d.current_work_area_) {
792 d.current_work_area_->bufferView().cursor().inMathed();
794 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
796 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
797 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
799 d.toolbars_->update(math, table, review);
801 d.toolbars_->update(false, false, false);
803 // update read-only status of open dialogs.
808 Buffer * GuiView::buffer()
810 if (d.current_work_area_)
811 return &d.current_work_area_->bufferView().buffer();
816 Buffer const * GuiView::buffer() const
818 if (d.current_work_area_)
819 return &d.current_work_area_->bufferView().buffer();
824 void GuiView::setBuffer(Buffer * newBuffer)
826 BOOST_ASSERT(newBuffer);
829 GuiWorkArea * wa = workArea(*newBuffer);
831 updateLabels(*newBuffer->masterBuffer());
832 wa = addWorkArea(*newBuffer);
834 //Disconnect the old buffer...there's no new one.
837 connectBuffer(*newBuffer);
838 connectBufferView(wa->bufferView());
839 setCurrentWorkArea(wa);
845 void GuiView::connectBuffer(Buffer & buf)
847 buf.setGuiDelegate(this);
851 void GuiView::disconnectBuffer()
853 if (d.current_work_area_)
854 d.current_work_area_->bufferView().setGuiDelegate(0);
858 void GuiView::connectBufferView(BufferView & bv)
860 bv.setGuiDelegate(this);
864 void GuiView::disconnectBufferView()
866 if (d.current_work_area_)
867 d.current_work_area_->bufferView().setGuiDelegate(0);
871 void GuiView::errors(string const & error_type)
873 ErrorList & el = buffer()->errorList(error_type);
875 showDialog("errorlist", error_type);
879 void GuiView::updateDialog(string const & name, string const & data)
881 if (!isDialogVisible(name))
884 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
885 if (it == d.dialogs_.end())
888 Dialog * const dialog = it->second.get();
889 if (dialog->isVisibleView())
890 dialog->updateData(data);
894 BufferView * GuiView::view()
896 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
900 void GuiView::updateToc()
902 updateDialog("toc", "");
906 void GuiView::updateEmbeddedFiles()
908 updateDialog("embedding", "");
912 void GuiView::autoSave()
914 LYXERR(Debug::INFO, "Running autoSave()");
917 view()->buffer().autoSave();
921 void GuiView::resetAutosaveTimers()
924 d.autosave_timeout_.restart();
928 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
932 Buffer * buf = buffer();
934 /* In LyX/Mac, when a dialog is open, the menus of the
935 application can still be accessed without giving focus to
936 the main window. In this case, we want to disable the menu
937 entries that are buffer-related.
939 Note that this code is not perfect, as bug 1941 attests:
940 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
942 if (cmd.origin == FuncRequest::MENU && !hasFocus())
946 case LFUN_BUFFER_WRITE:
947 enable = buf && (buf->isUnnamed() || !buf->isClean());
950 case LFUN_BUFFER_WRITE_AS:
954 case LFUN_TOOLBAR_TOGGLE:
955 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
958 case LFUN_DIALOG_TOGGLE:
959 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
960 // fall through to set "enable"
961 case LFUN_DIALOG_SHOW: {
962 string const name = cmd.getArg(0);
964 enable = name == "aboutlyx"
965 || name == "file" //FIXME: should be removed.
967 || name == "texinfo";
968 else if (name == "print")
969 enable = buf->isExportable("dvi")
970 && lyxrc.print_command != "none";
971 else if (name == "character") {
975 InsetCode ic = view()->cursor().inset().lyxCode();
976 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
979 else if (name == "latexlog")
980 enable = FileName(buf->logName()).isReadableFile();
981 else if (name == "spellchecker")
982 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
983 enable = !buf->isReadonly();
987 else if (name == "vclog")
988 enable = buf->lyxvc().inUse();
992 case LFUN_DIALOG_UPDATE: {
993 string const name = cmd.getArg(0);
995 enable = name == "prefs";
999 case LFUN_INSET_APPLY: {
1004 string const name = cmd.getArg(0);
1005 Inset * inset = getOpenInset(name);
1007 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1009 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1010 // Every inset is supposed to handle this
1011 BOOST_ASSERT(false);
1015 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1016 flag |= getStatus(fr);
1018 enable = flag.enabled();
1030 flag.enabled(false);
1036 static FileName selectTemplateFile()
1038 FileDialog dlg(_("Select template file"));
1039 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1040 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1042 FileDialog::Result result =
1043 dlg.open(from_utf8(lyxrc.template_path),
1044 FileFilterList(_("LyX Documents (*.lyx)")),
1047 if (result.first == FileDialog::Later)
1049 if (result.second.empty())
1051 return FileName(to_utf8(result.second));
1055 void GuiView::newDocument(string const & filename, bool from_template)
1057 FileName initpath(lyxrc.document_path);
1058 Buffer * buf = buffer();
1060 FileName const trypath(buf->filePath());
1061 // If directory is writeable, use this as default.
1062 if (trypath.isDirWritable())
1066 string templatefile = from_template ?
1067 selectTemplateFile().absFilename() : string();
1069 if (filename.empty())
1070 b = newUnnamedFile(templatefile, initpath);
1072 b = newFile(filename, templatefile, true);
1079 void GuiView::insertLyXFile(docstring const & fname)
1081 BufferView * bv = view();
1086 FileName filename(to_utf8(fname));
1088 if (!filename.empty()) {
1089 bv->insertLyXFile(filename);
1093 // Launch a file browser
1095 string initpath = lyxrc.document_path;
1096 string const trypath = bv->buffer().filePath();
1097 // If directory is writeable, use this as default.
1098 if (FileName(trypath).isDirWritable())
1102 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1103 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1104 dlg.setButton2(_("Examples|#E#e"),
1105 from_utf8(addPath(package().system_support().absFilename(),
1108 FileDialog::Result result =
1109 dlg.open(from_utf8(initpath),
1110 FileFilterList(_("LyX Documents (*.lyx)")),
1113 if (result.first == FileDialog::Later)
1117 filename.set(to_utf8(result.second));
1119 // check selected filename
1120 if (filename.empty()) {
1121 // emit message signal.
1122 message(_("Canceled."));
1126 bv->insertLyXFile(filename);
1130 void GuiView::insertPlaintextFile(docstring const & fname,
1133 BufferView * bv = view();
1138 FileName filename(to_utf8(fname));
1140 if (!filename.empty()) {
1141 bv->insertPlaintextFile(filename, asParagraph);
1145 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1146 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1148 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1149 FileFilterList(), docstring());
1151 if (result.first == FileDialog::Later)
1155 filename.set(to_utf8(result.second));
1157 // check selected filename
1158 if (filename.empty()) {
1159 // emit message signal.
1160 message(_("Canceled."));
1164 bv->insertPlaintextFile(filename, asParagraph);
1168 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1170 FileName fname = b.fileName();
1171 FileName const oldname = fname;
1173 if (!newname.empty()) {
1175 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1177 // Switch to this Buffer.
1180 /// No argument? Ask user through dialog.
1182 FileDialog dlg(_("Choose a filename to save document as"),
1183 LFUN_BUFFER_WRITE_AS);
1184 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1185 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1187 if (!support::isLyXFilename(fname.absFilename()))
1188 fname.changeExtension(".lyx");
1190 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1192 FileDialog::Result result =
1193 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1195 from_utf8(fname.onlyFileName()));
1197 if (result.first == FileDialog::Later)
1200 fname.set(to_utf8(result.second));
1205 if (!support::isLyXFilename(fname.absFilename()))
1206 fname.changeExtension(".lyx");
1209 if (FileName(fname).exists()) {
1210 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1211 docstring text = bformat(_("The document %1$s already "
1212 "exists.\n\nDo you want to "
1213 "overwrite that document?"),
1215 int const ret = Alert::prompt(_("Overwrite document?"),
1216 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1219 case 1: return renameBuffer(b, docstring());
1220 case 2: return false;
1224 // Ok, change the name of the buffer
1225 b.setFileName(fname.absFilename());
1227 bool unnamed = b.isUnnamed();
1228 b.setUnnamed(false);
1229 b.saveCheckSum(fname);
1231 if (!saveBuffer(b)) {
1232 b.setFileName(oldname.absFilename());
1233 b.setUnnamed(unnamed);
1234 b.saveCheckSum(oldname);
1242 bool GuiView::saveBuffer(Buffer & b)
1245 return renameBuffer(b, docstring());
1248 LyX::ref().session().lastFiles().add(b.fileName());
1252 // Switch to this Buffer.
1255 // FIXME: we don't tell the user *WHY* the save failed !!
1256 docstring const file = makeDisplayPath(b.absFileName(), 30);
1257 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1258 "Do you want to rename the document and "
1259 "try again?"), file);
1260 int const ret = Alert::prompt(_("Rename and save?"),
1261 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1264 if (!renameBuffer(b, docstring()))
1273 return saveBuffer(b);
1277 bool GuiView::closeBuffer()
1279 Buffer * buf = buffer();
1280 return buf && closeBuffer(*buf);
1284 bool GuiView::closeBuffer(Buffer & buf)
1286 if (buf.isClean() || buf.paragraphs().empty()) {
1287 theBufferList().release(&buf);
1290 // Switch to this Buffer.
1295 if (buf.isUnnamed())
1296 file = from_utf8(buf.fileName().onlyFileName());
1298 file = buf.fileName().displayName(30);
1300 docstring const text = bformat(_("The document %1$s has unsaved changes."
1301 "\n\nDo you want to save the document or discard the changes?"), file);
1302 int const ret = Alert::prompt(_("Save changed document?"),
1303 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1307 if (!saveBuffer(buf))
1311 // if we crash after this we could
1312 // have no autosave file but I guess
1313 // this is really improbable (Jug)
1314 removeAutosaveFile(buf.absFileName());
1320 // save file names to .lyx/session
1321 // if master/slave are both open, do not save slave since it
1322 // will be automatically loaded when the master is loaded
1323 if (buf.masterBuffer() == &buf)
1324 LyX::ref().session().lastOpened().add(buf.fileName());
1326 theBufferList().release(&buf);
1331 bool GuiView::quitWriteAll()
1333 while (!theBufferList().empty()) {
1334 Buffer * b = theBufferList().first();
1335 if (!closeBuffer(*b))
1342 bool GuiView::dispatch(FuncRequest const & cmd)
1344 BufferView * bv = view();
1345 // By default we won't need any update.
1347 bv->cursor().updateFlags(Update::None);
1349 switch(cmd.action) {
1350 case LFUN_BUFFER_SWITCH:
1351 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1354 case LFUN_BUFFER_NEXT:
1355 setBuffer(theBufferList().next(buffer()));
1358 case LFUN_BUFFER_PREVIOUS:
1359 setBuffer(theBufferList().previous(buffer()));
1362 case LFUN_COMMAND_EXECUTE: {
1363 bool const show_it = cmd.argument() != "off";
1364 d.toolbars_->showCommandBuffer(show_it);
1367 case LFUN_DROP_LAYOUTS_CHOICE:
1369 d.layout_->showPopup();
1372 case LFUN_MENU_OPEN:
1373 d.menubar_->openByName(toqstr(cmd.argument()));
1376 case LFUN_FILE_INSERT:
1377 insertLyXFile(cmd.argument());
1379 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1380 insertPlaintextFile(cmd.argument(), true);
1383 case LFUN_FILE_INSERT_PLAINTEXT:
1384 insertPlaintextFile(cmd.argument(), false);
1387 case LFUN_BUFFER_WRITE:
1389 saveBuffer(bv->buffer());
1392 case LFUN_BUFFER_WRITE_AS:
1394 renameBuffer(bv->buffer(), cmd.argument());
1397 case LFUN_BUFFER_WRITE_ALL: {
1398 Buffer * first = theBufferList().first();
1401 message(_("Saving all documents..."));
1402 // We cannot use a for loop as the buffer list cycles.
1408 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1409 b = theBufferList().next(b);
1410 } while (b != first);
1411 message(_("All documents saved."));
1415 case LFUN_TOOLBAR_TOGGLE: {
1416 string const name = cmd.getArg(0);
1417 bool const allowauto = cmd.getArg(1) == "allowauto";
1418 // it is possible to get current toolbar status like this,...
1419 // but I decide to obey the order of ToolbarBackend::flags
1420 // and disregard real toolbar status.
1421 // toolbars_->saveToolbarInfo();
1423 // toggle state on/off/auto
1424 d.toolbars_->toggleToolbarState(name, allowauto);
1428 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1430 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1434 if (tbi->flags & ToolbarInfo::ON)
1436 else if (tbi->flags & ToolbarInfo::OFF)
1438 else if (tbi->flags & ToolbarInfo::AUTO)
1441 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1442 _(tbi->gui_name), state));
1446 case LFUN_DIALOG_UPDATE: {
1447 string const name = to_utf8(cmd.argument());
1448 // Can only update a dialog connected to an existing inset
1449 Inset * inset = getOpenInset(name);
1451 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1452 inset->dispatch(view()->cursor(), fr);
1453 } else if (name == "paragraph") {
1454 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1455 } else if (name == "prefs") {
1456 updateDialog(name, string());
1461 case LFUN_DIALOG_TOGGLE: {
1462 if (isDialogVisible(cmd.getArg(0)))
1463 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1465 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1469 case LFUN_DIALOG_DISCONNECT_INSET:
1470 disconnectDialog(to_utf8(cmd.argument()));
1473 case LFUN_DIALOG_HIDE: {
1476 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1480 case LFUN_DIALOG_SHOW: {
1481 string const name = cmd.getArg(0);
1482 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1484 if (name == "character") {
1485 data = freefont2string();
1487 showDialog("character", data);
1488 } else if (name == "latexlog") {
1489 Buffer::LogType type;
1490 string const logfile = buffer()->logName(&type);
1492 case Buffer::latexlog:
1495 case Buffer::buildlog:
1499 data += Lexer::quoteString(logfile);
1500 showDialog("log", data);
1501 } else if (name == "vclog") {
1502 string const data = "vc " +
1503 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1504 showDialog("log", data);
1506 showDialog(name, data);
1510 case LFUN_INSET_APPLY: {
1511 string const name = cmd.getArg(0);
1512 Inset * inset = getOpenInset(name);
1514 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1515 inset->dispatch(view()->cursor(), fr);
1517 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1531 Buffer const * GuiView::updateInset(Inset const * inset)
1533 if (!d.current_work_area_)
1537 d.current_work_area_->scheduleRedraw();
1539 return &d.current_work_area_->bufferView().buffer();
1543 void GuiView::restartCursor()
1545 /* When we move around, or type, it's nice to be able to see
1546 * the cursor immediately after the keypress.
1548 if (d.current_work_area_)
1549 d.current_work_area_->startBlinkingCursor();
1551 // Take this occasion to update the toobars and layout list.
1558 // This list should be kept in sync with the list of insets in
1559 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1560 // dialog should have the same name as the inset.
1562 char const * const dialognames[] = {
1563 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1564 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1565 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1566 "mathdelimiter", "mathmatrix", "note", "paragraph",
1567 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1569 #ifdef HAVE_LIBAIKSAURUS
1573 "texinfo", "toc", "href", "view-source", "latex-progress", "vspace", "wrap", "listings" };
1575 char const * const * const end_dialognames =
1576 dialognames + (sizeof(dialognames) / sizeof(char *));
1580 cmpCStr(char const * name) : name_(name) {}
1581 bool operator()(char const * other) {
1582 return strcmp(other, name_) == 0;
1589 bool isValidName(string const & name)
1591 return std::find_if(dialognames, end_dialognames,
1592 cmpCStr(name.c_str())) != end_dialognames;
1598 void GuiView::resetDialogs()
1600 // Make sure that no LFUN uses any LyXView.
1601 theLyXFunc().setLyXView(0);
1602 d.toolbars_->init();
1605 d.layout_->updateContents(true);
1606 // Now update controls with current buffer.
1607 theLyXFunc().setLyXView(this);
1612 Dialog * GuiView::find_or_build(string const & name)
1614 if (!isValidName(name))
1617 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1619 if (it != d.dialogs_.end())
1620 return it->second.get();
1622 d.dialogs_[name].reset(build(name));
1623 return d.dialogs_[name].get();
1627 void GuiView::showDialog(string const & name, string const & data,
1634 Dialog * dialog = find_or_build(name);
1636 dialog->showData(data);
1638 d.open_insets_[name] = inset;
1644 bool GuiView::isDialogVisible(string const & name) const
1646 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1647 if (it == d.dialogs_.end())
1649 return it->second.get()->isVisibleView();
1653 void GuiView::hideDialog(string const & name, Inset * inset)
1655 // Don't send the signal if we are quitting, because on MSVC it is
1656 // destructed before the cut stack in CutAndPaste.cpp, and this method
1657 // is called from some inset destructor if the cut stack is not empty
1662 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1663 if (it == d.dialogs_.end())
1666 if (inset && inset != getOpenInset(name))
1669 Dialog * const dialog = it->second.get();
1670 if (dialog->isVisibleView())
1672 d.open_insets_[name] = 0;
1676 void GuiView::disconnectDialog(string const & name)
1678 if (!isValidName(name))
1681 if (d.open_insets_.find(name) != d.open_insets_.end())
1682 d.open_insets_[name] = 0;
1686 Inset * GuiView::getOpenInset(string const & name) const
1688 if (!isValidName(name))
1691 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1692 return it == d.open_insets_.end() ? 0 : it->second;
1696 void GuiView::hideAll() const
1698 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1699 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1701 for(; it != end; ++it)
1706 void GuiView::hideBufferDependent() const
1708 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1709 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1711 for(; it != end; ++it) {
1712 Dialog * dialog = it->second.get();
1713 if (dialog->isBufferDependent())
1719 void GuiView::updateBufferDependent(bool switched) const
1721 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1722 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1724 for(; it != end; ++it) {
1725 Dialog * dialog = it->second.get();
1726 if (!dialog->isVisibleView())
1728 if (switched && dialog->isBufferDependent()) {
1729 if (dialog->initialiseParams(""))
1730 dialog->updateView();
1734 // A bit clunky, but the dialog will request
1735 // that the kernel provides it with the necessary
1737 dialog->updateDialog();
1743 void GuiView::checkStatus()
1745 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1746 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1748 for(; it != end; ++it) {
1749 Dialog * const dialog = it->second.get();
1750 if (dialog && dialog->isVisibleView())
1751 dialog->checkStatus();
1757 // will be replaced by a proper factory...
1758 Dialog * createGuiAbout(GuiView & lv);
1759 Dialog * createGuiBibitem(GuiView & lv);
1760 Dialog * createGuiBibtex(GuiView & lv);
1761 Dialog * createGuiBox(GuiView & lv);
1762 Dialog * createGuiBranch(GuiView & lv);
1763 Dialog * createGuiChanges(GuiView & lv);
1764 Dialog * createGuiCharacter(GuiView & lv);
1765 Dialog * createGuiCitation(GuiView & lv);
1766 Dialog * createGuiDelimiter(GuiView & lv);
1767 Dialog * createGuiDocument(GuiView & lv);
1768 Dialog * createGuiErrorList(GuiView & lv);
1769 Dialog * createGuiERT(GuiView & lv);
1770 Dialog * createGuiExternal(GuiView & lv);
1771 Dialog * createGuiFloat(GuiView & lv);
1772 Dialog * createGuiGraphics(GuiView & lv);
1773 Dialog * createGuiInclude(GuiView & lv);
1774 Dialog * createGuiIndex(GuiView & lv);
1775 Dialog * createGuiLabel(GuiView & lv);
1776 Dialog * createGuiListings(GuiView & lv);
1777 Dialog * createGuiLog(GuiView & lv);
1778 Dialog * createGuiMathMatrix(GuiView & lv);
1779 Dialog * createGuiNomenclature(GuiView & lv);
1780 Dialog * createGuiNote(GuiView & lv);
1781 Dialog * createGuiParagraph(GuiView & lv);
1782 Dialog * createGuiPreferences(GuiView & lv);
1783 Dialog * createGuiPrint(GuiView & lv);
1784 Dialog * createGuiRef(GuiView & lv);
1785 Dialog * createGuiSearch(GuiView & lv);
1786 Dialog * createGuiSendTo(GuiView & lv);
1787 Dialog * createGuiShowFile(GuiView & lv);
1788 Dialog * createGuiSpellchecker(GuiView & lv);
1789 Dialog * createGuiTabularCreate(GuiView & lv);
1790 Dialog * createGuiTabular(GuiView & lv);
1791 Dialog * createGuiTexInfo(GuiView & lv);
1792 Dialog * createGuiToc(GuiView & lv);
1793 Dialog * createGuiThesaurus(GuiView & lv);
1794 Dialog * createGuiHyperlink(GuiView & lv);
1795 Dialog * createGuiVSpace(GuiView & lv);
1796 Dialog * createGuiViewSource(GuiView & lv);
1797 Dialog * createGuiProgress(GuiView & lv);
1798 Dialog * createGuiWrap(GuiView & lv);
1801 Dialog * GuiView::build(string const & name)
1803 BOOST_ASSERT(isValidName(name));
1805 if (name == "aboutlyx")
1806 return createGuiAbout(*this);
1807 if (name == "bibitem")
1808 return createGuiBibitem(*this);
1809 if (name == "bibtex")
1810 return createGuiBibtex(*this);
1812 return createGuiBox(*this);
1813 if (name == "branch")
1814 return createGuiBranch(*this);
1815 if (name == "changes")
1816 return createGuiChanges(*this);
1817 if (name == "character")
1818 return createGuiCharacter(*this);
1819 if (name == "citation")
1820 return createGuiCitation(*this);
1821 if (name == "document")
1822 return createGuiDocument(*this);
1823 if (name == "errorlist")
1824 return createGuiErrorList(*this);
1826 return createGuiERT(*this);
1827 if (name == "external")
1828 return createGuiExternal(*this);
1830 return createGuiShowFile(*this);
1831 if (name == "findreplace")
1832 return createGuiSearch(*this);
1833 if (name == "float")
1834 return createGuiFloat(*this);
1835 if (name == "graphics")
1836 return createGuiGraphics(*this);
1837 if (name == "include")
1838 return createGuiInclude(*this);
1839 if (name == "index")
1840 return createGuiIndex(*this);
1841 if (name == "nomenclature")
1842 return createGuiNomenclature(*this);
1843 if (name == "label")
1844 return createGuiLabel(*this);
1846 return createGuiLog(*this);
1847 if (name == "view-source")
1848 return createGuiViewSource(*this);
1849 if (name == "latex-progress")
1850 return createGuiProgress(*this);
1851 if (name == "mathdelimiter")
1852 return createGuiDelimiter(*this);
1853 if (name == "mathmatrix")
1854 return createGuiMathMatrix(*this);
1856 return createGuiNote(*this);
1857 if (name == "paragraph")
1858 return createGuiParagraph(*this);
1859 if (name == "prefs")
1860 return createGuiPreferences(*this);
1861 if (name == "print")
1862 return createGuiPrint(*this);
1864 return createGuiRef(*this);
1865 if (name == "sendto")
1866 return createGuiSendTo(*this);
1867 if (name == "spellchecker")
1868 return createGuiSpellchecker(*this);
1869 if (name == "tabular")
1870 return createGuiTabular(*this);
1871 if (name == "tabularcreate")
1872 return createGuiTabularCreate(*this);
1873 if (name == "texinfo")
1874 return createGuiTexInfo(*this);
1875 #ifdef HAVE_LIBAIKSAURUS
1876 if (name == "thesaurus")
1877 return createGuiThesaurus(*this);
1880 return createGuiToc(*this);
1882 return createGuiHyperlink(*this);
1883 if (name == "vspace")
1884 return createGuiVSpace(*this);
1886 return createGuiWrap(*this);
1887 if (name == "listings")
1888 return createGuiListings(*this);
1894 } // namespace frontend
1897 #include "GuiView_moc.cpp"