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>
96 using namespace lyx::support;
100 extern bool quitting;
106 class BackgroundWidget : public QWidget
111 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
112 /// The text to be written on top of the pixmap
113 QString const text = lyx_version ? lyx_version : qt_("unknown version");
114 splash_ = QPixmap(":/images/banner.png");
116 QPainter pain(&splash_);
117 pain.setPen(QColor(255, 255, 0));
119 // The font used to display the version info
120 font.setStyleHint(QFont::SansSerif);
121 font.setWeight(QFont::Bold);
122 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
124 pain.drawText(260, 270, text);
127 void paintEvent(QPaintEvent *)
129 int x = (width() - splash_.width()) / 2;
130 int y = (height() - splash_.height()) / 2;
132 pain.drawPixmap(x, y, splash_);
142 typedef boost::shared_ptr<Dialog> DialogPtr;
144 struct GuiView::GuiViewPrivate
147 : current_work_area_(0), layout_(0),
148 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
150 // hardcode here the platform specific icon size
151 smallIconSize = 14; // scaling problems
152 normalIconSize = 20; // ok, default
153 bigIconSize = 26; // better for some math icons
155 splitter_ = new QSplitter;
156 bg_widget_ = new BackgroundWidget;
157 stack_widget_ = new QStackedWidget;
158 stack_widget_->addWidget(bg_widget_);
159 stack_widget_->addWidget(splitter_);
167 delete stack_widget_;
172 QMenu * toolBarPopup(GuiView * parent)
174 // FIXME: translation
175 QMenu * menu = new QMenu(parent);
176 QActionGroup * iconSizeGroup = new QActionGroup(parent);
178 QAction * smallIcons = new QAction(iconSizeGroup);
179 smallIcons->setText(qt_("Small-sized icons"));
180 smallIcons->setCheckable(true);
181 QObject::connect(smallIcons, SIGNAL(triggered()),
182 parent, SLOT(smallSizedIcons()));
183 menu->addAction(smallIcons);
185 QAction * normalIcons = new QAction(iconSizeGroup);
186 normalIcons->setText(qt_("Normal-sized icons"));
187 normalIcons->setCheckable(true);
188 QObject::connect(normalIcons, SIGNAL(triggered()),
189 parent, SLOT(normalSizedIcons()));
190 menu->addAction(normalIcons);
192 QAction * bigIcons = new QAction(iconSizeGroup);
193 bigIcons->setText(qt_("Big-sized icons"));
194 bigIcons->setCheckable(true);
195 QObject::connect(bigIcons, SIGNAL(triggered()),
196 parent, SLOT(bigSizedIcons()));
197 menu->addAction(bigIcons);
199 unsigned int cur = parent->iconSize().width();
200 if ( cur == parent->d.smallIconSize)
201 smallIcons->setChecked(true);
202 else if (cur == parent->d.normalIconSize)
203 normalIcons->setChecked(true);
204 else if (cur == parent->d.bigIconSize)
205 bigIcons->setChecked(true);
212 stack_widget_->setCurrentWidget(bg_widget_);
213 bg_widget_->setUpdatesEnabled(true);
216 TabWorkArea * tabWorkArea(int i)
218 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
221 TabWorkArea * currentTabWorkArea()
223 if (splitter_->count() == 1)
224 // The first TabWorkArea is always the first one, if any.
225 return tabWorkArea(0);
227 TabWorkArea * tab_widget = 0;
228 for (int i = 0; i != splitter_->count(); ++i) {
229 QWidget * w = splitter_->widget(i);
232 tab_widget = dynamic_cast<TabWorkArea *>(w);
241 GuiWorkArea * current_work_area_;
242 QSplitter * splitter_;
243 QStackedWidget * stack_widget_;
244 BackgroundWidget * bg_widget_;
246 GuiMenubar * menubar_;
248 GuiToolbars * toolbars_;
249 /// The main layout box.
251 * \warning Don't Delete! The layout box is actually owned by
252 * whichever toolbar contains it. All the GuiView class needs is a
253 * means of accessing it.
255 * FIXME: replace that with a proper model so that we are not limited
256 * to only one dialog.
258 GuiLayoutBox * layout_;
261 map<string, Inset *> open_insets_;
264 map<string, DialogPtr> dialogs_;
266 unsigned int smallIconSize;
267 unsigned int normalIconSize;
268 unsigned int bigIconSize;
270 QTimer statusbar_timer_;
271 /// are we quitting by the menu?
272 bool quitting_by_menu_;
273 /// auto-saving of buffers
274 Timeout autosave_timeout_;
275 /// flag against a race condition due to multiclicks, see bug #1119
280 GuiView::GuiView(int id)
281 : d(*new GuiViewPrivate), id_(id)
283 // GuiToolbars *must* be initialised before GuiMenubar.
284 d.toolbars_ = new GuiToolbars(*this);
285 d.menubar_ = new GuiMenubar(this, menubackend);
287 setCentralWidget(d.stack_widget_);
289 // Start autosave timer
290 if (lyxrc.autosave) {
291 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
292 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
293 d.autosave_timeout_.start();
295 connect(&d.statusbar_timer_, SIGNAL(timeout()),
296 this, SLOT(clearMessage()));
298 // Qt bug? signal lastWindowClosed does not work
299 setAttribute(Qt::WA_QuitOnClose, false);
300 setAttribute(Qt::WA_DeleteOnClose, true);
302 // assign an icon to main form. We do not do it under Qt/Mac,
303 // since the icon is provided in the application bundle.
304 setWindowIcon(QPixmap(":/images/lyx.png"));
308 setAcceptDrops(true);
310 statusBar()->setSizeGripEnabled(true);
312 // Forbid too small unresizable window because it can happen
313 // with some window manager under X11.
314 setMinimumSize(300, 200);
316 if (!lyxrc.allow_geometry_session)
317 // No session handling, default to a sane size.
318 setGeometry(50, 50, 690, 510);
320 // Now take care of session management.
322 QString const key = "view-" + QString::number(id_);
324 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
325 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
329 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
330 setGeometry(50, 50, 690, 510);
332 setIconSize(settings.value(key + "/icon_size").toSize());
342 void GuiView::close()
344 d.quitting_by_menu_ = true;
345 d.current_work_area_ = 0;
346 for (int i = 0; i != d.splitter_->count(); ++i) {
347 TabWorkArea * twa = d.tabWorkArea(i);
351 QMainWindow::close();
352 d.quitting_by_menu_ = false;
356 void GuiView::setFocus()
358 if (d.current_work_area_)
359 d.current_work_area_->setFocus();
365 QMenu * GuiView::createPopupMenu()
367 return d.toolBarPopup(this);
371 void GuiView::showEvent(QShowEvent * e)
373 LYXERR(Debug::GUI, "Passed Geometry "
374 << size().height() << "x" << size().width()
375 << "+" << pos().x() << "+" << pos().y());
377 if (d.splitter_->count() == 0)
378 // No work area, switch to the background widget.
381 QMainWindow::showEvent(e);
385 void GuiView::closeEvent(QCloseEvent * close_event)
387 // we may have been called through the close window button
388 // which bypasses the LFUN machinery.
389 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
390 if (!quitWriteAll()) {
391 close_event->ignore();
396 // Make sure that no LFUN use this close to be closed View.
397 theLyXFunc().setLyXView(0);
398 // Make sure the timer time out will not trigger a statusbar update.
399 d.statusbar_timer_.stop();
401 if (lyxrc.allow_geometry_session) {
403 QString const key = "view-" + QString::number(id_);
405 settings.setValue(key + "/pos", pos());
406 settings.setValue(key + "/size", size());
408 settings.setValue(key + "/geometry", saveGeometry());
410 settings.setValue(key + "/icon_size", iconSize());
411 d.toolbars_->saveToolbarInfo();
412 // Now take care of all other dialogs:
413 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
414 for (; it!= d.dialogs_.end(); ++it)
415 it->second->saveSession();
418 guiApp->unregisterView(id_);
419 if (guiApp->viewCount() > 0) {
420 // Just close the window and do nothing else if this is not the
422 close_event->accept();
428 // this is the place where we leave the frontend.
429 // it is the only point at which we start quitting.
430 close_event->accept();
431 // quit the event loop
436 void GuiView::dragEnterEvent(QDragEnterEvent * event)
438 if (event->mimeData()->hasUrls())
440 /// \todo Ask lyx-devel is this is enough:
441 /// if (event->mimeData()->hasFormat("text/plain"))
442 /// event->acceptProposedAction();
446 void GuiView::dropEvent(QDropEvent* event)
448 QList<QUrl> files = event->mimeData()->urls();
452 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
453 for (int i = 0; i != files.size(); ++i) {
454 string const file = os::internal_path(fromqstr(
455 files.at(i).toLocalFile()));
457 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
462 void GuiView::message(docstring const & str)
464 statusBar()->showMessage(toqstr(str));
465 d.statusbar_timer_.stop();
466 d.statusbar_timer_.start(3000);
470 void GuiView::smallSizedIcons()
472 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
476 void GuiView::normalSizedIcons()
478 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
482 void GuiView::bigSizedIcons()
484 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
488 void GuiView::clearMessage()
492 theLyXFunc().setLyXView(this);
493 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
494 d.statusbar_timer_.stop();
498 void GuiView::updateWindowTitle(GuiWorkArea * wa)
500 if (wa != d.current_work_area_)
502 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
503 setWindowIconText(wa->windowIconText());
507 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
510 disconnectBufferView();
511 connectBufferView(wa->bufferView());
512 connectBuffer(wa->bufferView().buffer());
513 d.current_work_area_ = wa;
514 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
515 this, SLOT(updateWindowTitle(GuiWorkArea *)));
516 updateWindowTitle(wa);
519 // Buffer-dependent dialogs should be updated or
520 // hidden. This should go here because some dialogs (eg ToC)
521 // require bv_->text.
522 updateBufferDependent(true);
529 void GuiView::updateStatusBar()
531 // let the user see the explicit message
532 if (d.statusbar_timer_.isActive())
535 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
539 bool GuiView::hasFocus() const
541 return qApp->activeWindow() == this;
545 bool GuiView::event(QEvent * e)
549 // Useful debug code:
550 //case QEvent::ActivationChange:
551 //case QEvent::WindowDeactivate:
552 //case QEvent::Paint:
553 //case QEvent::Enter:
554 //case QEvent::Leave:
555 //case QEvent::HoverEnter:
556 //case QEvent::HoverLeave:
557 //case QEvent::HoverMove:
558 //case QEvent::StatusTip:
559 //case QEvent::DragEnter:
560 //case QEvent::DragLeave:
564 case QEvent::WindowActivate: {
565 guiApp->setCurrentView(*this);
566 if (d.current_work_area_) {
567 BufferView & bv = d.current_work_area_->bufferView();
568 connectBufferView(bv);
569 connectBuffer(bv.buffer());
570 // The document structure, name and dialogs might have
571 // changed in another view.
572 updateBufferDependent(true);
574 setWindowTitle(qt_("LyX"));
575 setWindowIconText(qt_("LyX"));
577 return QMainWindow::event(e);
579 case QEvent::ShortcutOverride: {
580 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
581 if (!d.current_work_area_) {
582 theLyXFunc().setLyXView(this);
584 setKeySymbol(&sym, ke);
585 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
589 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
591 setKeySymbol(&sym, ke);
592 d.current_work_area_->processKeySym(sym, NoModifier);
598 return QMainWindow::event(e);
603 bool GuiView::focusNextPrevChild(bool /*next*/)
610 void GuiView::setBusy(bool yes)
612 if (d.current_work_area_) {
613 d.current_work_area_->setUpdatesEnabled(!yes);
615 d.current_work_area_->stopBlinkingCursor();
617 d.current_work_area_->startBlinkingCursor();
621 QApplication::setOverrideCursor(Qt::WaitCursor);
623 QApplication::restoreOverrideCursor();
627 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
629 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
631 if (tbinfo.flags & ToolbarInfo::TOP) {
633 addToolBarBreak(Qt::TopToolBarArea);
634 addToolBar(Qt::TopToolBarArea, toolBar);
637 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
638 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
639 #if (QT_VERSION >= 0x040202)
641 addToolBarBreak(Qt::BottomToolBarArea);
643 addToolBar(Qt::BottomToolBarArea, toolBar);
646 if (tbinfo.flags & ToolbarInfo::LEFT) {
647 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
648 #if (QT_VERSION >= 0x040202)
650 addToolBarBreak(Qt::LeftToolBarArea);
652 addToolBar(Qt::LeftToolBarArea, toolBar);
655 if (tbinfo.flags & ToolbarInfo::RIGHT) {
656 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
657 #if (QT_VERSION >= 0x040202)
659 addToolBarBreak(Qt::RightToolBarArea);
661 addToolBar(Qt::RightToolBarArea, toolBar);
664 // The following does not work so I cannot restore to exact toolbar location
666 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
667 toolBar->move(tbinfo.posx, tbinfo.posy);
674 GuiWorkArea * GuiView::workArea(Buffer & buffer)
676 for (int i = 0; i != d.splitter_->count(); ++i) {
677 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
685 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
688 // Automatically create a TabWorkArea if there are none yet.
689 if (!d.splitter_->count())
692 TabWorkArea * tab_widget = d.currentTabWorkArea();
693 return tab_widget->addWorkArea(buffer, *this);
697 void GuiView::addTabWorkArea()
699 TabWorkArea * twa = new TabWorkArea;
700 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
701 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
702 d.splitter_->addWidget(twa);
703 d.stack_widget_->setCurrentWidget(d.splitter_);
707 GuiWorkArea const * GuiView::currentWorkArea() const
709 return d.current_work_area_;
713 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
717 // Changing work area can result from opening a file so
718 // update the toc in any case.
721 d.current_work_area_ = wa;
722 for (int i = 0; i != d.splitter_->count(); ++i) {
723 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
729 void GuiView::removeWorkArea(GuiWorkArea * wa)
732 if (wa == d.current_work_area_) {
734 disconnectBufferView();
735 hideBufferDependent();
736 d.current_work_area_ = 0;
739 for (int i = 0; i != d.splitter_->count(); ++i) {
740 TabWorkArea * twa = d.tabWorkArea(i);
741 if (!twa->removeWorkArea(wa))
742 // Not found in this tab group.
745 // We found and removed the GuiWorkArea.
747 // No more WorkAreas in this tab group, so delete it.
752 if (d.current_work_area_)
753 // This means that we are not closing the current GuiWorkArea;
756 // Switch to the next GuiWorkArea in the found TabWorkArea.
757 d.current_work_area_ = twa->currentWorkArea();
761 if (d.splitter_->count() == 0)
762 // No more work area, switch to the background widget.
767 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
773 void GuiView::updateLayoutList()
776 d.layout_->updateContents(false);
780 void GuiView::updateToolbars()
782 if (d.current_work_area_) {
784 d.current_work_area_->bufferView().cursor().inMathed();
786 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
788 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
789 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
790 bool const mathmacrotemplate =
791 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
793 d.toolbars_->update(math, table, review, mathmacrotemplate);
795 d.toolbars_->update(false, false, false, false);
797 // update read-only status of open dialogs.
802 Buffer * GuiView::buffer()
804 if (d.current_work_area_)
805 return &d.current_work_area_->bufferView().buffer();
810 Buffer const * GuiView::buffer() const
812 if (d.current_work_area_)
813 return &d.current_work_area_->bufferView().buffer();
818 void GuiView::setBuffer(Buffer * newBuffer)
820 BOOST_ASSERT(newBuffer);
823 GuiWorkArea * wa = workArea(*newBuffer);
825 updateLabels(*newBuffer->masterBuffer());
826 wa = addWorkArea(*newBuffer);
828 //Disconnect the old buffer...there's no new one.
831 connectBuffer(*newBuffer);
832 connectBufferView(wa->bufferView());
833 setCurrentWorkArea(wa);
839 void GuiView::connectBuffer(Buffer & buf)
841 buf.setGuiDelegate(this);
845 void GuiView::disconnectBuffer()
847 if (d.current_work_area_)
848 d.current_work_area_->bufferView().setGuiDelegate(0);
852 void GuiView::connectBufferView(BufferView & bv)
854 bv.setGuiDelegate(this);
858 void GuiView::disconnectBufferView()
860 if (d.current_work_area_)
861 d.current_work_area_->bufferView().setGuiDelegate(0);
865 void GuiView::errors(string const & error_type)
867 ErrorList & el = buffer()->errorList(error_type);
869 showDialog("errorlist", error_type);
873 void GuiView::updateDialog(string const & name, string const & data)
875 if (!isDialogVisible(name))
878 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
879 if (it == d.dialogs_.end())
882 Dialog * const dialog = it->second.get();
883 if (dialog->isVisibleView())
884 dialog->updateData(data);
888 BufferView * GuiView::view()
890 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
894 void GuiView::updateToc()
896 updateDialog("toc", "");
900 void GuiView::updateEmbeddedFiles()
902 updateDialog("embedding", "");
906 void GuiView::autoSave()
908 LYXERR(Debug::INFO, "Running autoSave()");
911 view()->buffer().autoSave();
915 void GuiView::resetAutosaveTimers()
918 d.autosave_timeout_.restart();
922 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
926 Buffer * buf = buffer();
928 /* In LyX/Mac, when a dialog is open, the menus of the
929 application can still be accessed without giving focus to
930 the main window. In this case, we want to disable the menu
931 entries that are buffer-related.
933 Note that this code is not perfect, as bug 1941 attests:
934 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
936 if (cmd.origin == FuncRequest::MENU && !hasFocus())
940 case LFUN_BUFFER_WRITE:
941 enable = buf && (buf->isUnnamed() || !buf->isClean());
944 case LFUN_BUFFER_WRITE_AS:
948 case LFUN_TOOLBAR_TOGGLE:
949 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
952 case LFUN_DIALOG_TOGGLE:
953 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
954 // fall through to set "enable"
955 case LFUN_DIALOG_SHOW: {
956 string const name = cmd.getArg(0);
958 enable = name == "aboutlyx"
959 || name == "file" //FIXME: should be removed.
961 || name == "texinfo";
962 else if (name == "print")
963 enable = buf->isExportable("dvi")
964 && lyxrc.print_command != "none";
965 else if (name == "character") {
969 InsetCode ic = view()->cursor().inset().lyxCode();
970 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
973 else if (name == "latexlog")
974 enable = FileName(buf->logName()).isReadableFile();
975 else if (name == "spellchecker")
976 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
977 enable = !buf->isReadonly();
981 else if (name == "vclog")
982 enable = buf->lyxvc().inUse();
986 case LFUN_DIALOG_UPDATE: {
987 string const name = cmd.getArg(0);
989 enable = name == "prefs";
993 case LFUN_INSET_APPLY: {
998 string const name = cmd.getArg(0);
999 Inset * inset = getOpenInset(name);
1001 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1003 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1004 // Every inset is supposed to handle this
1005 BOOST_ASSERT(false);
1009 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1010 flag |= getStatus(fr);
1012 enable = flag.enabled();
1024 flag.enabled(false);
1030 static FileName selectTemplateFile()
1032 FileDialog dlg(_("Select template file"));
1033 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1034 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1036 FileDialog::Result result =
1037 dlg.open(from_utf8(lyxrc.template_path),
1038 FileFilterList(_("LyX Documents (*.lyx)")),
1041 if (result.first == FileDialog::Later)
1043 if (result.second.empty())
1045 return FileName(to_utf8(result.second));
1049 void GuiView::newDocument(string const & filename, bool from_template)
1051 FileName initpath(lyxrc.document_path);
1052 Buffer * buf = buffer();
1054 FileName const trypath(buf->filePath());
1055 // If directory is writeable, use this as default.
1056 if (trypath.isDirWritable())
1060 string templatefile = from_template ?
1061 selectTemplateFile().absFilename() : string();
1063 if (filename.empty())
1064 b = newUnnamedFile(templatefile, initpath);
1066 b = newFile(filename, templatefile, true);
1070 // Ensure the cursor is correctly positionned on screen.
1071 view()->showCursor();
1075 void GuiView::insertLyXFile(docstring const & fname)
1077 BufferView * bv = view();
1082 FileName filename(to_utf8(fname));
1084 if (!filename.empty()) {
1085 bv->insertLyXFile(filename);
1089 // Launch a file browser
1091 string initpath = lyxrc.document_path;
1092 string const trypath = bv->buffer().filePath();
1093 // If directory is writeable, use this as default.
1094 if (FileName(trypath).isDirWritable())
1098 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1099 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1100 dlg.setButton2(_("Examples|#E#e"),
1101 from_utf8(addPath(package().system_support().absFilename(),
1104 FileDialog::Result result =
1105 dlg.open(from_utf8(initpath),
1106 FileFilterList(_("LyX Documents (*.lyx)")),
1109 if (result.first == FileDialog::Later)
1113 filename.set(to_utf8(result.second));
1115 // check selected filename
1116 if (filename.empty()) {
1117 // emit message signal.
1118 message(_("Canceled."));
1122 bv->insertLyXFile(filename);
1126 void GuiView::insertPlaintextFile(docstring const & fname,
1129 BufferView * bv = view();
1134 FileName filename(to_utf8(fname));
1136 if (!filename.empty()) {
1137 bv->insertPlaintextFile(filename, asParagraph);
1141 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1142 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1144 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1145 FileFilterList(), docstring());
1147 if (result.first == FileDialog::Later)
1151 filename.set(to_utf8(result.second));
1153 // check selected filename
1154 if (filename.empty()) {
1155 // emit message signal.
1156 message(_("Canceled."));
1160 bv->insertPlaintextFile(filename, asParagraph);
1164 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1166 FileName fname = b.fileName();
1167 FileName const oldname = fname;
1169 if (!newname.empty()) {
1171 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1173 // Switch to this Buffer.
1176 /// No argument? Ask user through dialog.
1178 FileDialog dlg(_("Choose a filename to save document as"),
1179 LFUN_BUFFER_WRITE_AS);
1180 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1181 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1183 if (!isLyXFilename(fname.absFilename()))
1184 fname.changeExtension(".lyx");
1186 FileFilterList const filter(_("LyX Documents (*.lyx)"));
1188 FileDialog::Result result =
1189 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1191 from_utf8(fname.onlyFileName()));
1193 if (result.first == FileDialog::Later)
1196 fname.set(to_utf8(result.second));
1201 if (!isLyXFilename(fname.absFilename()))
1202 fname.changeExtension(".lyx");
1205 if (FileName(fname).exists()) {
1206 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1207 docstring text = bformat(_("The document %1$s already "
1208 "exists.\n\nDo you want to "
1209 "overwrite that document?"),
1211 int const ret = Alert::prompt(_("Overwrite document?"),
1212 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1215 case 1: return renameBuffer(b, docstring());
1216 case 2: return false;
1220 // Ok, change the name of the buffer
1221 b.setFileName(fname.absFilename());
1223 bool unnamed = b.isUnnamed();
1224 b.setUnnamed(false);
1225 b.saveCheckSum(fname);
1227 if (!saveBuffer(b)) {
1228 b.setFileName(oldname.absFilename());
1229 b.setUnnamed(unnamed);
1230 b.saveCheckSum(oldname);
1238 bool GuiView::saveBuffer(Buffer & b)
1241 return renameBuffer(b, docstring());
1244 LyX::ref().session().lastFiles().add(b.fileName());
1248 // Switch to this Buffer.
1251 // FIXME: we don't tell the user *WHY* the save failed !!
1252 docstring const file = makeDisplayPath(b.absFileName(), 30);
1253 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1254 "Do you want to rename the document and "
1255 "try again?"), file);
1256 int const ret = Alert::prompt(_("Rename and save?"),
1257 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1260 if (!renameBuffer(b, docstring()))
1269 return saveBuffer(b);
1273 bool GuiView::closeBuffer()
1275 Buffer * buf = buffer();
1276 return buf && closeBuffer(*buf);
1280 bool GuiView::closeBuffer(Buffer & buf)
1282 if (buf.isClean() || buf.paragraphs().empty()) {
1283 theBufferList().release(&buf);
1286 // Switch to this Buffer.
1291 if (buf.isUnnamed())
1292 file = from_utf8(buf.fileName().onlyFileName());
1294 file = buf.fileName().displayName(30);
1296 docstring const text = bformat(_("The document %1$s has unsaved changes."
1297 "\n\nDo you want to save the document or discard the changes?"), file);
1298 int const ret = Alert::prompt(_("Save changed document?"),
1299 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1303 if (!saveBuffer(buf))
1307 // if we crash after this we could
1308 // have no autosave file but I guess
1309 // this is really improbable (Jug)
1310 removeAutosaveFile(buf.absFileName());
1316 // save file names to .lyx/session
1317 // if master/slave are both open, do not save slave since it
1318 // will be automatically loaded when the master is loaded
1319 if (buf.masterBuffer() == &buf)
1320 LyX::ref().session().lastOpened().add(buf.fileName());
1322 theBufferList().release(&buf);
1327 bool GuiView::quitWriteAll()
1329 while (!theBufferList().empty()) {
1330 Buffer * b = theBufferList().first();
1331 if (!closeBuffer(*b))
1338 bool GuiView::dispatch(FuncRequest const & cmd)
1340 BufferView * bv = view();
1341 // By default we won't need any update.
1343 bv->cursor().updateFlags(Update::None);
1345 switch(cmd.action) {
1346 case LFUN_BUFFER_SWITCH:
1347 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1350 case LFUN_BUFFER_NEXT:
1351 setBuffer(theBufferList().next(buffer()));
1354 case LFUN_BUFFER_PREVIOUS:
1355 setBuffer(theBufferList().previous(buffer()));
1358 case LFUN_COMMAND_EXECUTE: {
1359 bool const show_it = cmd.argument() != "off";
1360 d.toolbars_->showCommandBuffer(show_it);
1363 case LFUN_DROP_LAYOUTS_CHOICE:
1365 d.layout_->showPopup();
1368 case LFUN_MENU_OPEN:
1369 d.menubar_->openByName(toqstr(cmd.argument()));
1372 case LFUN_FILE_INSERT:
1373 insertLyXFile(cmd.argument());
1375 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1376 insertPlaintextFile(cmd.argument(), true);
1379 case LFUN_FILE_INSERT_PLAINTEXT:
1380 insertPlaintextFile(cmd.argument(), false);
1383 case LFUN_BUFFER_WRITE:
1385 saveBuffer(bv->buffer());
1388 case LFUN_BUFFER_WRITE_AS:
1390 renameBuffer(bv->buffer(), cmd.argument());
1393 case LFUN_BUFFER_WRITE_ALL: {
1394 Buffer * first = theBufferList().first();
1397 message(_("Saving all documents..."));
1398 // We cannot use a for loop as the buffer list cycles.
1404 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1405 b = theBufferList().next(b);
1406 } while (b != first);
1407 message(_("All documents saved."));
1411 case LFUN_TOOLBAR_TOGGLE: {
1412 string const name = cmd.getArg(0);
1413 bool const allowauto = cmd.getArg(1) == "allowauto";
1414 // it is possible to get current toolbar status like this,...
1415 // but I decide to obey the order of ToolbarBackend::flags
1416 // and disregard real toolbar status.
1417 // toolbars_->saveToolbarInfo();
1419 // toggle state on/off/auto
1420 d.toolbars_->toggleToolbarState(name, allowauto);
1424 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1426 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1430 if (tbi->flags & ToolbarInfo::ON)
1432 else if (tbi->flags & ToolbarInfo::OFF)
1434 else if (tbi->flags & ToolbarInfo::AUTO)
1437 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1438 _(tbi->gui_name), state));
1442 case LFUN_DIALOG_UPDATE: {
1443 string const name = to_utf8(cmd.argument());
1444 // Can only update a dialog connected to an existing inset
1445 Inset * inset = getOpenInset(name);
1447 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1448 inset->dispatch(view()->cursor(), fr);
1449 } else if (name == "paragraph") {
1450 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1451 } else if (name == "prefs") {
1452 updateDialog(name, string());
1457 case LFUN_DIALOG_TOGGLE: {
1458 if (isDialogVisible(cmd.getArg(0)))
1459 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1461 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1465 case LFUN_DIALOG_DISCONNECT_INSET:
1466 disconnectDialog(to_utf8(cmd.argument()));
1469 case LFUN_DIALOG_HIDE: {
1472 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1476 case LFUN_DIALOG_SHOW: {
1477 string const name = cmd.getArg(0);
1478 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1480 if (name == "character") {
1481 data = freefont2string();
1483 showDialog("character", data);
1484 } else if (name == "latexlog") {
1485 Buffer::LogType type;
1486 string const logfile = buffer()->logName(&type);
1488 case Buffer::latexlog:
1491 case Buffer::buildlog:
1495 data += Lexer::quoteString(logfile);
1496 showDialog("log", data);
1497 } else if (name == "vclog") {
1498 string const data = "vc " +
1499 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1500 showDialog("log", data);
1502 showDialog(name, data);
1506 case LFUN_INSET_APPLY: {
1507 string const name = cmd.getArg(0);
1508 Inset * inset = getOpenInset(name);
1510 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1511 inset->dispatch(view()->cursor(), fr);
1513 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1527 Buffer const * GuiView::updateInset(Inset const * inset)
1529 if (!d.current_work_area_)
1533 d.current_work_area_->scheduleRedraw();
1535 return &d.current_work_area_->bufferView().buffer();
1539 void GuiView::restartCursor()
1541 /* When we move around, or type, it's nice to be able to see
1542 * the cursor immediately after the keypress.
1544 if (d.current_work_area_)
1545 d.current_work_area_->startBlinkingCursor();
1547 // Take this occasion to update the toobars and layout list.
1554 // This list should be kept in sync with the list of insets in
1555 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1556 // dialog should have the same name as the inset.
1558 char const * const dialognames[] = {
1559 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1560 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1561 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1562 "mathdelimiter", "mathmatrix", "note", "paragraph",
1563 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1565 #ifdef HAVE_LIBAIKSAURUS
1569 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1571 char const * const * const end_dialognames =
1572 dialognames + (sizeof(dialognames) / sizeof(char *));
1576 cmpCStr(char const * name) : name_(name) {}
1577 bool operator()(char const * other) {
1578 return strcmp(other, name_) == 0;
1585 bool isValidName(string const & name)
1587 return find_if(dialognames, end_dialognames,
1588 cmpCStr(name.c_str())) != end_dialognames;
1594 void GuiView::resetDialogs()
1596 // Make sure that no LFUN uses any LyXView.
1597 theLyXFunc().setLyXView(0);
1598 d.toolbars_->init();
1601 d.layout_->updateContents(true);
1602 // Now update controls with current buffer.
1603 theLyXFunc().setLyXView(this);
1608 Dialog * GuiView::find_or_build(string const & name)
1610 if (!isValidName(name))
1613 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1615 if (it != d.dialogs_.end())
1616 return it->second.get();
1618 Dialog * dialog = build(name);
1619 d.dialogs_[name].reset(dialog);
1620 if (lyxrc.allow_geometry_session)
1621 dialog->restoreSession();
1626 void GuiView::showDialog(string const & name, string const & data,
1633 Dialog * dialog = find_or_build(name);
1635 dialog->showData(data);
1637 d.open_insets_[name] = inset;
1643 bool GuiView::isDialogVisible(string const & name) const
1645 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1646 if (it == d.dialogs_.end())
1648 return it->second.get()->isVisibleView();
1652 void GuiView::hideDialog(string const & name, Inset * inset)
1654 // Don't send the signal if we are quitting, because on MSVC it is
1655 // destructed before the cut stack in CutAndPaste.cpp, and this method
1656 // is called from some inset destructor if the cut stack is not empty
1661 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1662 if (it == d.dialogs_.end())
1665 if (inset && inset != getOpenInset(name))
1668 Dialog * const dialog = it->second.get();
1669 if (dialog->isVisibleView())
1671 d.open_insets_[name] = 0;
1675 void GuiView::disconnectDialog(string const & name)
1677 if (!isValidName(name))
1680 if (d.open_insets_.find(name) != d.open_insets_.end())
1681 d.open_insets_[name] = 0;
1685 Inset * GuiView::getOpenInset(string const & name) const
1687 if (!isValidName(name))
1690 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1691 return it == d.open_insets_.end() ? 0 : it->second;
1695 void GuiView::hideAll() const
1697 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1698 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1700 for(; it != end; ++it)
1701 it->second->hideView();
1705 void GuiView::hideBufferDependent() const
1707 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1708 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1710 for(; it != end; ++it) {
1711 Dialog * dialog = it->second.get();
1712 if (dialog->isBufferDependent())
1718 void GuiView::updateBufferDependent(bool switched) const
1720 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1721 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1723 for(; it != end; ++it) {
1724 Dialog * dialog = it->second.get();
1725 if (!dialog->isVisibleView())
1727 if (switched && dialog->isBufferDependent()) {
1728 if (dialog->initialiseParams(""))
1729 dialog->updateView();
1733 // A bit clunky, but the dialog will request
1734 // that the kernel provides it with the necessary
1736 dialog->updateDialog();
1742 void GuiView::checkStatus()
1744 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1745 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1747 for(; it != end; ++it) {
1748 Dialog * const dialog = it->second.get();
1749 if (dialog && dialog->isVisibleView())
1750 dialog->checkStatus();
1756 // will be replaced by a proper factory...
1757 Dialog * createGuiAbout(GuiView & lv);
1758 Dialog * createGuiBibitem(GuiView & lv);
1759 Dialog * createGuiBibtex(GuiView & lv);
1760 Dialog * createGuiBox(GuiView & lv);
1761 Dialog * createGuiBranch(GuiView & lv);
1762 Dialog * createGuiChanges(GuiView & lv);
1763 Dialog * createGuiCharacter(GuiView & lv);
1764 Dialog * createGuiCitation(GuiView & lv);
1765 Dialog * createGuiDelimiter(GuiView & lv);
1766 Dialog * createGuiDocument(GuiView & lv);
1767 Dialog * createGuiErrorList(GuiView & lv);
1768 Dialog * createGuiERT(GuiView & lv);
1769 Dialog * createGuiExternal(GuiView & lv);
1770 Dialog * createGuiFloat(GuiView & lv);
1771 Dialog * createGuiGraphics(GuiView & lv);
1772 Dialog * createGuiInclude(GuiView & lv);
1773 Dialog * createGuiIndex(GuiView & lv);
1774 Dialog * createGuiLabel(GuiView & lv);
1775 Dialog * createGuiListings(GuiView & lv);
1776 Dialog * createGuiLog(GuiView & lv);
1777 Dialog * createGuiMathMatrix(GuiView & lv);
1778 Dialog * createGuiNomenclature(GuiView & lv);
1779 Dialog * createGuiNote(GuiView & lv);
1780 Dialog * createGuiParagraph(GuiView & lv);
1781 Dialog * createGuiPreferences(GuiView & lv);
1782 Dialog * createGuiPrint(GuiView & lv);
1783 Dialog * createGuiRef(GuiView & lv);
1784 Dialog * createGuiSearch(GuiView & lv);
1785 Dialog * createGuiSendTo(GuiView & lv);
1786 Dialog * createGuiShowFile(GuiView & lv);
1787 Dialog * createGuiSpellchecker(GuiView & lv);
1788 Dialog * createGuiTabularCreate(GuiView & lv);
1789 Dialog * createGuiTabular(GuiView & lv);
1790 Dialog * createGuiTexInfo(GuiView & lv);
1791 Dialog * createGuiToc(GuiView & lv);
1792 Dialog * createGuiThesaurus(GuiView & lv);
1793 Dialog * createGuiHyperlink(GuiView & lv);
1794 Dialog * createGuiVSpace(GuiView & lv);
1795 Dialog * createGuiViewSource(GuiView & lv);
1796 Dialog * createGuiWrap(GuiView & lv);
1799 Dialog * GuiView::build(string const & name)
1801 BOOST_ASSERT(isValidName(name));
1803 if (name == "aboutlyx")
1804 return createGuiAbout(*this);
1805 if (name == "bibitem")
1806 return createGuiBibitem(*this);
1807 if (name == "bibtex")
1808 return createGuiBibtex(*this);
1810 return createGuiBox(*this);
1811 if (name == "branch")
1812 return createGuiBranch(*this);
1813 if (name == "changes")
1814 return createGuiChanges(*this);
1815 if (name == "character")
1816 return createGuiCharacter(*this);
1817 if (name == "citation")
1818 return createGuiCitation(*this);
1819 if (name == "document")
1820 return createGuiDocument(*this);
1821 if (name == "errorlist")
1822 return createGuiErrorList(*this);
1824 return createGuiERT(*this);
1825 if (name == "external")
1826 return createGuiExternal(*this);
1828 return createGuiShowFile(*this);
1829 if (name == "findreplace")
1830 return createGuiSearch(*this);
1831 if (name == "float")
1832 return createGuiFloat(*this);
1833 if (name == "graphics")
1834 return createGuiGraphics(*this);
1835 if (name == "include")
1836 return createGuiInclude(*this);
1837 if (name == "index")
1838 return createGuiIndex(*this);
1839 if (name == "nomenclature")
1840 return createGuiNomenclature(*this);
1841 if (name == "label")
1842 return createGuiLabel(*this);
1844 return createGuiLog(*this);
1845 if (name == "view-source")
1846 return createGuiViewSource(*this);
1847 if (name == "mathdelimiter")
1848 return createGuiDelimiter(*this);
1849 if (name == "mathmatrix")
1850 return createGuiMathMatrix(*this);
1852 return createGuiNote(*this);
1853 if (name == "paragraph")
1854 return createGuiParagraph(*this);
1855 if (name == "prefs")
1856 return createGuiPreferences(*this);
1857 if (name == "print")
1858 return createGuiPrint(*this);
1860 return createGuiRef(*this);
1861 if (name == "sendto")
1862 return createGuiSendTo(*this);
1863 if (name == "spellchecker")
1864 return createGuiSpellchecker(*this);
1865 if (name == "tabular")
1866 return createGuiTabular(*this);
1867 if (name == "tabularcreate")
1868 return createGuiTabularCreate(*this);
1869 if (name == "texinfo")
1870 return createGuiTexInfo(*this);
1871 #ifdef HAVE_LIBAIKSAURUS
1872 if (name == "thesaurus")
1873 return createGuiThesaurus(*this);
1876 return createGuiToc(*this);
1878 return createGuiHyperlink(*this);
1879 if (name == "vspace")
1880 return createGuiVSpace(*this);
1882 return createGuiWrap(*this);
1883 if (name == "listings")
1884 return createGuiListings(*this);
1890 } // namespace frontend
1893 #include "GuiView_moc.cpp"