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();
424 // Now take care of all other dialogs:
425 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
426 for (; it!= d.dialogs_.end(); ++it)
427 it->second->saveSession();
430 guiApp->unregisterView(id_);
431 if (guiApp->viewCount() > 0) {
432 // Just close the window and do nothing else if this is not the
434 close_event->accept();
440 // this is the place where we leave the frontend.
441 // it is the only point at which we start quitting.
442 close_event->accept();
443 // quit the event loop
448 void GuiView::dragEnterEvent(QDragEnterEvent * event)
450 if (event->mimeData()->hasUrls())
452 /// \todo Ask lyx-devel is this is enough:
453 /// if (event->mimeData()->hasFormat("text/plain"))
454 /// event->acceptProposedAction();
458 void GuiView::dropEvent(QDropEvent* event)
460 QList<QUrl> files = event->mimeData()->urls();
464 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
465 for (int i = 0; i != files.size(); ++i) {
466 string const file = support::os::internal_path(fromqstr(
467 files.at(i).toLocalFile()));
469 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
474 void GuiView::message(docstring const & str)
476 statusBar()->showMessage(toqstr(str));
477 d.statusbar_timer_.stop();
478 d.statusbar_timer_.start(3000);
482 void GuiView::smallSizedIcons()
484 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
488 void GuiView::normalSizedIcons()
490 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
494 void GuiView::bigSizedIcons()
496 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
500 void GuiView::clearMessage()
504 theLyXFunc().setLyXView(this);
505 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
506 d.statusbar_timer_.stop();
510 void GuiView::updateWindowTitle(GuiWorkArea * wa)
512 if (wa != d.current_work_area_)
514 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
515 setWindowIconText(wa->windowIconText());
519 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
522 disconnectBufferView();
523 connectBufferView(wa->bufferView());
524 connectBuffer(wa->bufferView().buffer());
525 d.current_work_area_ = wa;
526 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
527 this, SLOT(updateWindowTitle(GuiWorkArea *)));
528 updateWindowTitle(wa);
531 // Buffer-dependent dialogs should be updated or
532 // hidden. This should go here because some dialogs (eg ToC)
533 // require bv_->text.
534 updateBufferDependent(true);
541 void GuiView::updateStatusBar()
543 // let the user see the explicit message
544 if (d.statusbar_timer_.isActive())
547 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
551 bool GuiView::hasFocus() const
553 return qApp->activeWindow() == this;
557 bool GuiView::event(QEvent * e)
561 // Useful debug code:
562 //case QEvent::ActivationChange:
563 //case QEvent::WindowDeactivate:
564 //case QEvent::Paint:
565 //case QEvent::Enter:
566 //case QEvent::Leave:
567 //case QEvent::HoverEnter:
568 //case QEvent::HoverLeave:
569 //case QEvent::HoverMove:
570 //case QEvent::StatusTip:
571 //case QEvent::DragEnter:
572 //case QEvent::DragLeave:
576 case QEvent::WindowActivate: {
577 guiApp->setCurrentView(*this);
578 if (d.current_work_area_) {
579 BufferView & bv = d.current_work_area_->bufferView();
580 connectBufferView(bv);
581 connectBuffer(bv.buffer());
582 // The document structure, name and dialogs might have
583 // changed in another view.
584 updateBufferDependent(true);
586 setWindowTitle(qt_("LyX"));
587 setWindowIconText(qt_("LyX"));
589 return QMainWindow::event(e);
591 case QEvent::ShortcutOverride: {
592 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
593 if (!d.current_work_area_) {
594 theLyXFunc().setLyXView(this);
596 setKeySymbol(&sym, ke);
597 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
601 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
603 setKeySymbol(&sym, ke);
604 d.current_work_area_->processKeySym(sym, NoModifier);
610 return QMainWindow::event(e);
615 bool GuiView::focusNextPrevChild(bool /*next*/)
622 void GuiView::setBusy(bool yes)
624 if (d.current_work_area_) {
625 d.current_work_area_->setUpdatesEnabled(!yes);
627 d.current_work_area_->stopBlinkingCursor();
629 d.current_work_area_->startBlinkingCursor();
633 QApplication::setOverrideCursor(Qt::WaitCursor);
635 QApplication::restoreOverrideCursor();
639 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
641 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
643 if (tbinfo.flags & ToolbarInfo::TOP) {
645 addToolBarBreak(Qt::TopToolBarArea);
646 addToolBar(Qt::TopToolBarArea, toolBar);
649 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
650 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
651 #if (QT_VERSION >= 0x040202)
653 addToolBarBreak(Qt::BottomToolBarArea);
655 addToolBar(Qt::BottomToolBarArea, toolBar);
658 if (tbinfo.flags & ToolbarInfo::LEFT) {
659 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
660 #if (QT_VERSION >= 0x040202)
662 addToolBarBreak(Qt::LeftToolBarArea);
664 addToolBar(Qt::LeftToolBarArea, toolBar);
667 if (tbinfo.flags & ToolbarInfo::RIGHT) {
668 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
669 #if (QT_VERSION >= 0x040202)
671 addToolBarBreak(Qt::RightToolBarArea);
673 addToolBar(Qt::RightToolBarArea, toolBar);
676 // The following does not work so I cannot restore to exact toolbar location
678 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
679 toolBar->move(tbinfo.posx, tbinfo.posy);
686 GuiWorkArea * GuiView::workArea(Buffer & buffer)
688 for (int i = 0; i != d.splitter_->count(); ++i) {
689 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
697 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
700 // Automatically create a TabWorkArea if there are none yet.
701 if (!d.splitter_->count())
704 TabWorkArea * tab_widget = d.currentTabWorkArea();
705 return tab_widget->addWorkArea(buffer, *this);
709 void GuiView::addTabWorkArea()
711 TabWorkArea * twa = new TabWorkArea;
712 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
713 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
714 d.splitter_->addWidget(twa);
715 d.stack_widget_->setCurrentWidget(d.splitter_);
719 GuiWorkArea const * GuiView::currentWorkArea() const
721 return d.current_work_area_;
725 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
729 // Changing work area can result from opening a file so
730 // update the toc in any case.
733 d.current_work_area_ = wa;
734 for (int i = 0; i != d.splitter_->count(); ++i) {
735 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
741 void GuiView::removeWorkArea(GuiWorkArea * wa)
744 if (wa == d.current_work_area_) {
746 disconnectBufferView();
747 hideBufferDependent();
748 d.current_work_area_ = 0;
751 for (int i = 0; i != d.splitter_->count(); ++i) {
752 TabWorkArea * twa = d.tabWorkArea(i);
753 if (!twa->removeWorkArea(wa))
754 // Not found in this tab group.
757 // We found and removed the GuiWorkArea.
759 // No more WorkAreas in this tab group, so delete it.
764 if (d.current_work_area_)
765 // This means that we are not closing the current GuiWorkArea;
768 // Switch to the next GuiWorkArea in the found TabWorkArea.
769 d.current_work_area_ = twa->currentWorkArea();
773 if (d.splitter_->count() == 0)
774 // No more work area, switch to the background widget.
779 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
785 void GuiView::updateLayoutList()
788 d.layout_->updateContents(false);
792 void GuiView::updateToolbars()
794 if (d.current_work_area_) {
796 d.current_work_area_->bufferView().cursor().inMathed();
798 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
800 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
801 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
803 d.toolbars_->update(math, table, review);
805 d.toolbars_->update(false, false, false);
807 // update read-only status of open dialogs.
812 Buffer * GuiView::buffer()
814 if (d.current_work_area_)
815 return &d.current_work_area_->bufferView().buffer();
820 Buffer const * GuiView::buffer() const
822 if (d.current_work_area_)
823 return &d.current_work_area_->bufferView().buffer();
828 void GuiView::setBuffer(Buffer * newBuffer)
830 BOOST_ASSERT(newBuffer);
833 GuiWorkArea * wa = workArea(*newBuffer);
835 updateLabels(*newBuffer->masterBuffer());
836 wa = addWorkArea(*newBuffer);
838 //Disconnect the old buffer...there's no new one.
841 connectBuffer(*newBuffer);
842 connectBufferView(wa->bufferView());
843 setCurrentWorkArea(wa);
849 void GuiView::connectBuffer(Buffer & buf)
851 buf.setGuiDelegate(this);
855 void GuiView::disconnectBuffer()
857 if (d.current_work_area_)
858 d.current_work_area_->bufferView().setGuiDelegate(0);
862 void GuiView::connectBufferView(BufferView & bv)
864 bv.setGuiDelegate(this);
868 void GuiView::disconnectBufferView()
870 if (d.current_work_area_)
871 d.current_work_area_->bufferView().setGuiDelegate(0);
875 void GuiView::errors(string const & error_type)
877 ErrorList & el = buffer()->errorList(error_type);
879 showDialog("errorlist", error_type);
883 void GuiView::updateDialog(string const & name, string const & data)
885 if (!isDialogVisible(name))
888 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
889 if (it == d.dialogs_.end())
892 Dialog * const dialog = it->second.get();
893 if (dialog->isVisibleView())
894 dialog->updateData(data);
898 BufferView * GuiView::view()
900 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
904 void GuiView::updateToc()
906 updateDialog("toc", "");
910 void GuiView::updateEmbeddedFiles()
912 updateDialog("embedding", "");
916 void GuiView::autoSave()
918 LYXERR(Debug::INFO, "Running autoSave()");
921 view()->buffer().autoSave();
925 void GuiView::resetAutosaveTimers()
928 d.autosave_timeout_.restart();
932 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
936 Buffer * buf = buffer();
938 /* In LyX/Mac, when a dialog is open, the menus of the
939 application can still be accessed without giving focus to
940 the main window. In this case, we want to disable the menu
941 entries that are buffer-related.
943 Note that this code is not perfect, as bug 1941 attests:
944 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
946 if (cmd.origin == FuncRequest::MENU && !hasFocus())
950 case LFUN_BUFFER_WRITE:
951 enable = buf && (buf->isUnnamed() || !buf->isClean());
954 case LFUN_BUFFER_WRITE_AS:
958 case LFUN_TOOLBAR_TOGGLE:
959 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
962 case LFUN_DIALOG_TOGGLE:
963 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
964 // fall through to set "enable"
965 case LFUN_DIALOG_SHOW: {
966 string const name = cmd.getArg(0);
968 enable = name == "aboutlyx"
969 || name == "file" //FIXME: should be removed.
971 || name == "texinfo";
972 else if (name == "print")
973 enable = buf->isExportable("dvi")
974 && lyxrc.print_command != "none";
975 else if (name == "character") {
979 InsetCode ic = view()->cursor().inset().lyxCode();
980 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
983 else if (name == "latexlog")
984 enable = FileName(buf->logName()).isReadableFile();
985 else if (name == "spellchecker")
986 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
987 enable = !buf->isReadonly();
991 else if (name == "vclog")
992 enable = buf->lyxvc().inUse();
996 case LFUN_DIALOG_UPDATE: {
997 string const name = cmd.getArg(0);
999 enable = name == "prefs";
1003 case LFUN_INSET_APPLY: {
1008 string const name = cmd.getArg(0);
1009 Inset * inset = getOpenInset(name);
1011 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1013 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1014 // Every inset is supposed to handle this
1015 BOOST_ASSERT(false);
1019 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1020 flag |= getStatus(fr);
1022 enable = flag.enabled();
1034 flag.enabled(false);
1040 static FileName selectTemplateFile()
1042 FileDialog dlg(_("Select template file"));
1043 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1044 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1046 FileDialog::Result result =
1047 dlg.open(from_utf8(lyxrc.template_path),
1048 FileFilterList(_("LyX Documents (*.lyx)")),
1051 if (result.first == FileDialog::Later)
1053 if (result.second.empty())
1055 return FileName(to_utf8(result.second));
1059 void GuiView::newDocument(string const & filename, bool from_template)
1061 FileName initpath(lyxrc.document_path);
1062 Buffer * buf = buffer();
1064 FileName const trypath(buf->filePath());
1065 // If directory is writeable, use this as default.
1066 if (trypath.isDirWritable())
1070 string templatefile = from_template ?
1071 selectTemplateFile().absFilename() : string();
1073 if (filename.empty())
1074 b = newUnnamedFile(templatefile, initpath);
1076 b = newFile(filename, templatefile, true);
1083 void GuiView::insertLyXFile(docstring const & fname)
1085 BufferView * bv = view();
1090 FileName filename(to_utf8(fname));
1092 if (!filename.empty()) {
1093 bv->insertLyXFile(filename);
1097 // Launch a file browser
1099 string initpath = lyxrc.document_path;
1100 string const trypath = bv->buffer().filePath();
1101 // If directory is writeable, use this as default.
1102 if (FileName(trypath).isDirWritable())
1106 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1107 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1108 dlg.setButton2(_("Examples|#E#e"),
1109 from_utf8(addPath(package().system_support().absFilename(),
1112 FileDialog::Result result =
1113 dlg.open(from_utf8(initpath),
1114 FileFilterList(_("LyX Documents (*.lyx)")),
1117 if (result.first == FileDialog::Later)
1121 filename.set(to_utf8(result.second));
1123 // check selected filename
1124 if (filename.empty()) {
1125 // emit message signal.
1126 message(_("Canceled."));
1130 bv->insertLyXFile(filename);
1134 void GuiView::insertPlaintextFile(docstring const & fname,
1137 BufferView * bv = view();
1142 FileName filename(to_utf8(fname));
1144 if (!filename.empty()) {
1145 bv->insertPlaintextFile(filename, asParagraph);
1149 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1150 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1152 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1153 FileFilterList(), docstring());
1155 if (result.first == FileDialog::Later)
1159 filename.set(to_utf8(result.second));
1161 // check selected filename
1162 if (filename.empty()) {
1163 // emit message signal.
1164 message(_("Canceled."));
1168 bv->insertPlaintextFile(filename, asParagraph);
1172 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1174 FileName fname = b.fileName();
1175 FileName const oldname = fname;
1177 if (!newname.empty()) {
1179 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1181 // Switch to this Buffer.
1184 /// No argument? Ask user through dialog.
1186 FileDialog dlg(_("Choose a filename to save document as"),
1187 LFUN_BUFFER_WRITE_AS);
1188 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1189 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1191 if (!support::isLyXFilename(fname.absFilename()))
1192 fname.changeExtension(".lyx");
1194 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1196 FileDialog::Result result =
1197 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1199 from_utf8(fname.onlyFileName()));
1201 if (result.first == FileDialog::Later)
1204 fname.set(to_utf8(result.second));
1209 if (!support::isLyXFilename(fname.absFilename()))
1210 fname.changeExtension(".lyx");
1213 if (FileName(fname).exists()) {
1214 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1215 docstring text = bformat(_("The document %1$s already "
1216 "exists.\n\nDo you want to "
1217 "overwrite that document?"),
1219 int const ret = Alert::prompt(_("Overwrite document?"),
1220 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1223 case 1: return renameBuffer(b, docstring());
1224 case 2: return false;
1228 // Ok, change the name of the buffer
1229 b.setFileName(fname.absFilename());
1231 bool unnamed = b.isUnnamed();
1232 b.setUnnamed(false);
1233 b.saveCheckSum(fname);
1235 if (!saveBuffer(b)) {
1236 b.setFileName(oldname.absFilename());
1237 b.setUnnamed(unnamed);
1238 b.saveCheckSum(oldname);
1246 bool GuiView::saveBuffer(Buffer & b)
1249 return renameBuffer(b, docstring());
1252 LyX::ref().session().lastFiles().add(b.fileName());
1256 // Switch to this Buffer.
1259 // FIXME: we don't tell the user *WHY* the save failed !!
1260 docstring const file = makeDisplayPath(b.absFileName(), 30);
1261 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1262 "Do you want to rename the document and "
1263 "try again?"), file);
1264 int const ret = Alert::prompt(_("Rename and save?"),
1265 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1268 if (!renameBuffer(b, docstring()))
1277 return saveBuffer(b);
1281 bool GuiView::closeBuffer()
1283 Buffer * buf = buffer();
1284 return buf && closeBuffer(*buf);
1288 bool GuiView::closeBuffer(Buffer & buf)
1290 if (buf.isClean() || buf.paragraphs().empty()) {
1291 theBufferList().release(&buf);
1294 // Switch to this Buffer.
1299 if (buf.isUnnamed())
1300 file = from_utf8(buf.fileName().onlyFileName());
1302 file = buf.fileName().displayName(30);
1304 docstring const text = bformat(_("The document %1$s has unsaved changes."
1305 "\n\nDo you want to save the document or discard the changes?"), file);
1306 int const ret = Alert::prompt(_("Save changed document?"),
1307 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1311 if (!saveBuffer(buf))
1315 // if we crash after this we could
1316 // have no autosave file but I guess
1317 // this is really improbable (Jug)
1318 removeAutosaveFile(buf.absFileName());
1324 // save file names to .lyx/session
1325 // if master/slave are both open, do not save slave since it
1326 // will be automatically loaded when the master is loaded
1327 if (buf.masterBuffer() == &buf)
1328 LyX::ref().session().lastOpened().add(buf.fileName());
1330 theBufferList().release(&buf);
1335 bool GuiView::quitWriteAll()
1337 while (!theBufferList().empty()) {
1338 Buffer * b = theBufferList().first();
1339 if (!closeBuffer(*b))
1346 bool GuiView::dispatch(FuncRequest const & cmd)
1348 BufferView * bv = view();
1349 // By default we won't need any update.
1351 bv->cursor().updateFlags(Update::None);
1353 switch(cmd.action) {
1354 case LFUN_BUFFER_SWITCH:
1355 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1358 case LFUN_BUFFER_NEXT:
1359 setBuffer(theBufferList().next(buffer()));
1362 case LFUN_BUFFER_PREVIOUS:
1363 setBuffer(theBufferList().previous(buffer()));
1366 case LFUN_COMMAND_EXECUTE: {
1367 bool const show_it = cmd.argument() != "off";
1368 d.toolbars_->showCommandBuffer(show_it);
1371 case LFUN_DROP_LAYOUTS_CHOICE:
1373 d.layout_->showPopup();
1376 case LFUN_MENU_OPEN:
1377 d.menubar_->openByName(toqstr(cmd.argument()));
1380 case LFUN_FILE_INSERT:
1381 insertLyXFile(cmd.argument());
1383 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1384 insertPlaintextFile(cmd.argument(), true);
1387 case LFUN_FILE_INSERT_PLAINTEXT:
1388 insertPlaintextFile(cmd.argument(), false);
1391 case LFUN_BUFFER_WRITE:
1393 saveBuffer(bv->buffer());
1396 case LFUN_BUFFER_WRITE_AS:
1398 renameBuffer(bv->buffer(), cmd.argument());
1401 case LFUN_BUFFER_WRITE_ALL: {
1402 Buffer * first = theBufferList().first();
1405 message(_("Saving all documents..."));
1406 // We cannot use a for loop as the buffer list cycles.
1412 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1413 b = theBufferList().next(b);
1414 } while (b != first);
1415 message(_("All documents saved."));
1419 case LFUN_TOOLBAR_TOGGLE: {
1420 string const name = cmd.getArg(0);
1421 bool const allowauto = cmd.getArg(1) == "allowauto";
1422 // it is possible to get current toolbar status like this,...
1423 // but I decide to obey the order of ToolbarBackend::flags
1424 // and disregard real toolbar status.
1425 // toolbars_->saveToolbarInfo();
1427 // toggle state on/off/auto
1428 d.toolbars_->toggleToolbarState(name, allowauto);
1432 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1434 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1438 if (tbi->flags & ToolbarInfo::ON)
1440 else if (tbi->flags & ToolbarInfo::OFF)
1442 else if (tbi->flags & ToolbarInfo::AUTO)
1445 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1446 _(tbi->gui_name), state));
1450 case LFUN_DIALOG_UPDATE: {
1451 string const name = to_utf8(cmd.argument());
1452 // Can only update a dialog connected to an existing inset
1453 Inset * inset = getOpenInset(name);
1455 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1456 inset->dispatch(view()->cursor(), fr);
1457 } else if (name == "paragraph") {
1458 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1459 } else if (name == "prefs") {
1460 updateDialog(name, string());
1465 case LFUN_DIALOG_TOGGLE: {
1466 if (isDialogVisible(cmd.getArg(0)))
1467 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1469 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1473 case LFUN_DIALOG_DISCONNECT_INSET:
1474 disconnectDialog(to_utf8(cmd.argument()));
1477 case LFUN_DIALOG_HIDE: {
1480 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1484 case LFUN_DIALOG_SHOW: {
1485 string const name = cmd.getArg(0);
1486 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1488 if (name == "character") {
1489 data = freefont2string();
1491 showDialog("character", data);
1492 } else if (name == "latexlog") {
1493 Buffer::LogType type;
1494 string const logfile = buffer()->logName(&type);
1496 case Buffer::latexlog:
1499 case Buffer::buildlog:
1503 data += Lexer::quoteString(logfile);
1504 showDialog("log", data);
1505 } else if (name == "vclog") {
1506 string const data = "vc " +
1507 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1508 showDialog("log", data);
1510 showDialog(name, data);
1514 case LFUN_INSET_APPLY: {
1515 string const name = cmd.getArg(0);
1516 Inset * inset = getOpenInset(name);
1518 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1519 inset->dispatch(view()->cursor(), fr);
1521 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1535 Buffer const * GuiView::updateInset(Inset const * inset)
1537 if (!d.current_work_area_)
1541 d.current_work_area_->scheduleRedraw();
1543 return &d.current_work_area_->bufferView().buffer();
1547 void GuiView::restartCursor()
1549 /* When we move around, or type, it's nice to be able to see
1550 * the cursor immediately after the keypress.
1552 if (d.current_work_area_)
1553 d.current_work_area_->startBlinkingCursor();
1555 // Take this occasion to update the toobars and layout list.
1562 // This list should be kept in sync with the list of insets in
1563 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1564 // dialog should have the same name as the inset.
1566 char const * const dialognames[] = {
1567 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1568 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1569 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1570 "mathdelimiter", "mathmatrix", "note", "paragraph",
1571 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1573 #ifdef HAVE_LIBAIKSAURUS
1577 "texinfo", "toc", "href", "view-source", "latex-progress", "vspace", "wrap", "listings" };
1579 char const * const * const end_dialognames =
1580 dialognames + (sizeof(dialognames) / sizeof(char *));
1584 cmpCStr(char const * name) : name_(name) {}
1585 bool operator()(char const * other) {
1586 return strcmp(other, name_) == 0;
1593 bool isValidName(string const & name)
1595 return std::find_if(dialognames, end_dialognames,
1596 cmpCStr(name.c_str())) != end_dialognames;
1602 void GuiView::resetDialogs()
1604 // Make sure that no LFUN uses any LyXView.
1605 theLyXFunc().setLyXView(0);
1606 d.toolbars_->init();
1609 d.layout_->updateContents(true);
1610 // Now update controls with current buffer.
1611 theLyXFunc().setLyXView(this);
1616 Dialog * GuiView::find_or_build(string const & name)
1618 if (!isValidName(name))
1621 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1623 if (it != d.dialogs_.end())
1624 return it->second.get();
1626 Dialog * dialog = build(name);
1627 d.dialogs_[name].reset(dialog);
1628 if (lyxrc.allow_geometry_session)
1629 dialog->restoreSession();
1634 void GuiView::showDialog(string const & name, string const & data,
1641 Dialog * dialog = find_or_build(name);
1643 dialog->showData(data);
1645 d.open_insets_[name] = inset;
1651 bool GuiView::isDialogVisible(string const & name) const
1653 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1654 if (it == d.dialogs_.end())
1656 return it->second.get()->isVisibleView();
1660 void GuiView::hideDialog(string const & name, Inset * inset)
1662 // Don't send the signal if we are quitting, because on MSVC it is
1663 // destructed before the cut stack in CutAndPaste.cpp, and this method
1664 // is called from some inset destructor if the cut stack is not empty
1669 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1670 if (it == d.dialogs_.end())
1673 if (inset && inset != getOpenInset(name))
1676 Dialog * const dialog = it->second.get();
1677 if (dialog->isVisibleView())
1679 d.open_insets_[name] = 0;
1683 void GuiView::disconnectDialog(string const & name)
1685 if (!isValidName(name))
1688 if (d.open_insets_.find(name) != d.open_insets_.end())
1689 d.open_insets_[name] = 0;
1693 Inset * GuiView::getOpenInset(string const & name) const
1695 if (!isValidName(name))
1698 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1699 return it == d.open_insets_.end() ? 0 : it->second;
1703 void GuiView::hideAll() const
1705 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1706 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1708 for(; it != end; ++it)
1709 it->second->hideView();
1713 void GuiView::hideBufferDependent() const
1715 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1716 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1718 for(; it != end; ++it) {
1719 Dialog * dialog = it->second.get();
1720 if (dialog->isBufferDependent())
1726 void GuiView::updateBufferDependent(bool switched) const
1728 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1729 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1731 for(; it != end; ++it) {
1732 Dialog * dialog = it->second.get();
1733 if (!dialog->isVisibleView())
1735 if (switched && dialog->isBufferDependent()) {
1736 if (dialog->initialiseParams(""))
1737 dialog->updateView();
1741 // A bit clunky, but the dialog will request
1742 // that the kernel provides it with the necessary
1744 dialog->updateDialog();
1750 void GuiView::checkStatus()
1752 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1753 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1755 for(; it != end; ++it) {
1756 Dialog * const dialog = it->second.get();
1757 if (dialog && dialog->isVisibleView())
1758 dialog->checkStatus();
1764 // will be replaced by a proper factory...
1765 Dialog * createGuiAbout(GuiView & lv);
1766 Dialog * createGuiBibitem(GuiView & lv);
1767 Dialog * createGuiBibtex(GuiView & lv);
1768 Dialog * createGuiBox(GuiView & lv);
1769 Dialog * createGuiBranch(GuiView & lv);
1770 Dialog * createGuiChanges(GuiView & lv);
1771 Dialog * createGuiCharacter(GuiView & lv);
1772 Dialog * createGuiCitation(GuiView & lv);
1773 Dialog * createGuiDelimiter(GuiView & lv);
1774 Dialog * createGuiDocument(GuiView & lv);
1775 Dialog * createGuiErrorList(GuiView & lv);
1776 Dialog * createGuiERT(GuiView & lv);
1777 Dialog * createGuiExternal(GuiView & lv);
1778 Dialog * createGuiFloat(GuiView & lv);
1779 Dialog * createGuiGraphics(GuiView & lv);
1780 Dialog * createGuiInclude(GuiView & lv);
1781 Dialog * createGuiIndex(GuiView & lv);
1782 Dialog * createGuiLabel(GuiView & lv);
1783 Dialog * createGuiListings(GuiView & lv);
1784 Dialog * createGuiLog(GuiView & lv);
1785 Dialog * createGuiMathMatrix(GuiView & lv);
1786 Dialog * createGuiNomenclature(GuiView & lv);
1787 Dialog * createGuiNote(GuiView & lv);
1788 Dialog * createGuiParagraph(GuiView & lv);
1789 Dialog * createGuiPreferences(GuiView & lv);
1790 Dialog * createGuiPrint(GuiView & lv);
1791 Dialog * createGuiRef(GuiView & lv);
1792 Dialog * createGuiSearch(GuiView & lv);
1793 Dialog * createGuiSendTo(GuiView & lv);
1794 Dialog * createGuiShowFile(GuiView & lv);
1795 Dialog * createGuiSpellchecker(GuiView & lv);
1796 Dialog * createGuiTabularCreate(GuiView & lv);
1797 Dialog * createGuiTabular(GuiView & lv);
1798 Dialog * createGuiTexInfo(GuiView & lv);
1799 Dialog * createGuiToc(GuiView & lv);
1800 Dialog * createGuiThesaurus(GuiView & lv);
1801 Dialog * createGuiHyperlink(GuiView & lv);
1802 Dialog * createGuiVSpace(GuiView & lv);
1803 Dialog * createGuiViewSource(GuiView & lv);
1804 Dialog * createGuiProgress(GuiView & lv);
1805 Dialog * createGuiWrap(GuiView & lv);
1808 Dialog * GuiView::build(string const & name)
1810 BOOST_ASSERT(isValidName(name));
1812 if (name == "aboutlyx")
1813 return createGuiAbout(*this);
1814 if (name == "bibitem")
1815 return createGuiBibitem(*this);
1816 if (name == "bibtex")
1817 return createGuiBibtex(*this);
1819 return createGuiBox(*this);
1820 if (name == "branch")
1821 return createGuiBranch(*this);
1822 if (name == "changes")
1823 return createGuiChanges(*this);
1824 if (name == "character")
1825 return createGuiCharacter(*this);
1826 if (name == "citation")
1827 return createGuiCitation(*this);
1828 if (name == "document")
1829 return createGuiDocument(*this);
1830 if (name == "errorlist")
1831 return createGuiErrorList(*this);
1833 return createGuiERT(*this);
1834 if (name == "external")
1835 return createGuiExternal(*this);
1837 return createGuiShowFile(*this);
1838 if (name == "findreplace")
1839 return createGuiSearch(*this);
1840 if (name == "float")
1841 return createGuiFloat(*this);
1842 if (name == "graphics")
1843 return createGuiGraphics(*this);
1844 if (name == "include")
1845 return createGuiInclude(*this);
1846 if (name == "index")
1847 return createGuiIndex(*this);
1848 if (name == "nomenclature")
1849 return createGuiNomenclature(*this);
1850 if (name == "label")
1851 return createGuiLabel(*this);
1853 return createGuiLog(*this);
1854 if (name == "view-source")
1855 return createGuiViewSource(*this);
1856 if (name == "latex-progress")
1857 return createGuiProgress(*this);
1858 if (name == "mathdelimiter")
1859 return createGuiDelimiter(*this);
1860 if (name == "mathmatrix")
1861 return createGuiMathMatrix(*this);
1863 return createGuiNote(*this);
1864 if (name == "paragraph")
1865 return createGuiParagraph(*this);
1866 if (name == "prefs")
1867 return createGuiPreferences(*this);
1868 if (name == "print")
1869 return createGuiPrint(*this);
1871 return createGuiRef(*this);
1872 if (name == "sendto")
1873 return createGuiSendTo(*this);
1874 if (name == "spellchecker")
1875 return createGuiSpellchecker(*this);
1876 if (name == "tabular")
1877 return createGuiTabular(*this);
1878 if (name == "tabularcreate")
1879 return createGuiTabularCreate(*this);
1880 if (name == "texinfo")
1881 return createGuiTexInfo(*this);
1882 #ifdef HAVE_LIBAIKSAURUS
1883 if (name == "thesaurus")
1884 return createGuiThesaurus(*this);
1887 return createGuiToc(*this);
1889 return createGuiHyperlink(*this);
1890 if (name == "vspace")
1891 return createGuiVSpace(*this);
1893 return createGuiWrap(*this);
1894 if (name == "listings")
1895 return createGuiListings(*this);
1901 } // namespace frontend
1904 #include "GuiView_moc.cpp"