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 "GuiToolbar.h"
24 #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 "Paragraph.h"
49 #include "TextClass.h"
51 #include "ToolbarBackend.h"
54 #include "support/FileFilterList.h"
55 #include "support/FileName.h"
56 #include "support/filetools.h"
57 #include "support/ForkedCalls.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_;
171 QMenu * toolBarPopup(GuiView * parent)
173 // FIXME: translation
174 QMenu * menu = new QMenu(parent);
175 QActionGroup * iconSizeGroup = new QActionGroup(parent);
177 QAction * smallIcons = new QAction(iconSizeGroup);
178 smallIcons->setText(qt_("Small-sized icons"));
179 smallIcons->setCheckable(true);
180 QObject::connect(smallIcons, SIGNAL(triggered()),
181 parent, SLOT(smallSizedIcons()));
182 menu->addAction(smallIcons);
184 QAction * normalIcons = new QAction(iconSizeGroup);
185 normalIcons->setText(qt_("Normal-sized icons"));
186 normalIcons->setCheckable(true);
187 QObject::connect(normalIcons, SIGNAL(triggered()),
188 parent, SLOT(normalSizedIcons()));
189 menu->addAction(normalIcons);
191 QAction * bigIcons = new QAction(iconSizeGroup);
192 bigIcons->setText(qt_("Big-sized icons"));
193 bigIcons->setCheckable(true);
194 QObject::connect(bigIcons, SIGNAL(triggered()),
195 parent, SLOT(bigSizedIcons()));
196 menu->addAction(bigIcons);
198 unsigned int cur = parent->iconSize().width();
199 if ( cur == parent->d.smallIconSize)
200 smallIcons->setChecked(true);
201 else if (cur == parent->d.normalIconSize)
202 normalIcons->setChecked(true);
203 else if (cur == parent->d.bigIconSize)
204 bigIcons->setChecked(true);
211 stack_widget_->setCurrentWidget(bg_widget_);
212 bg_widget_->setUpdatesEnabled(true);
215 TabWorkArea * tabWorkArea(int i)
217 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
220 TabWorkArea * currentTabWorkArea()
222 if (splitter_->count() == 1)
223 // The first TabWorkArea is always the first one, if any.
224 return tabWorkArea(0);
226 TabWorkArea * tab_widget = 0;
227 for (int i = 0; i != splitter_->count(); ++i) {
228 QWidget * w = splitter_->widget(i);
231 tab_widget = dynamic_cast<TabWorkArea *>(w);
240 GuiWorkArea * current_work_area_;
241 QSplitter * splitter_;
242 QStackedWidget * stack_widget_;
243 BackgroundWidget * bg_widget_;
245 GuiToolbars * toolbars_;
246 /// The main layout box.
248 * \warning Don't Delete! The layout box is actually owned by
249 * whichever toolbar contains it. All the GuiView class needs is a
250 * means of accessing it.
252 * FIXME: replace that with a proper model so that we are not limited
253 * to only one dialog.
255 GuiLayoutBox * layout_;
258 map<string, Inset *> open_insets_;
261 map<string, DialogPtr> dialogs_;
263 unsigned int smallIconSize;
264 unsigned int normalIconSize;
265 unsigned int bigIconSize;
267 QTimer statusbar_timer_;
268 /// are we quitting by the menu?
269 bool quitting_by_menu_;
270 /// auto-saving of buffers
271 Timeout autosave_timeout_;
272 /// flag against a race condition due to multiclicks, see bug #1119
277 GuiView::GuiView(int id)
278 : d(*new GuiViewPrivate), id_(id)
280 // GuiToolbars *must* be initialised before the menu bar.
281 d.toolbars_ = new GuiToolbars(*this);
283 // Fill up the menu bar.
284 guiApp->menus().fillMenuBar(this);
286 setCentralWidget(d.stack_widget_);
288 // Start autosave timer
289 if (lyxrc.autosave) {
290 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
291 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
292 d.autosave_timeout_.start();
294 connect(&d.statusbar_timer_, SIGNAL(timeout()),
295 this, SLOT(clearMessage()));
297 // Qt bug? signal lastWindowClosed does not work
298 setAttribute(Qt::WA_QuitOnClose, false);
299 setAttribute(Qt::WA_DeleteOnClose, true);
301 // assign an icon to main form. We do not do it under Qt/Mac,
302 // since the icon is provided in the application bundle.
303 setWindowIcon(QPixmap(":/images/lyx.png"));
307 setAcceptDrops(true);
309 statusBar()->setSizeGripEnabled(true);
311 // Forbid too small unresizable window because it can happen
312 // with some window manager under X11.
313 setMinimumSize(300, 200);
315 if (!lyxrc.allow_geometry_session)
316 // No session handling, default to a sane size.
317 setGeometry(50, 50, 690, 510);
319 // Now take care of session management.
321 QString const key = "view-" + QString::number(id_);
323 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
324 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
328 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
329 setGeometry(50, 50, 690, 510);
331 setIconSize(settings.value(key + "/icon_size").toSize());
341 void GuiView::close()
343 d.quitting_by_menu_ = true;
344 d.current_work_area_ = 0;
345 for (int i = 0; i != d.splitter_->count(); ++i) {
346 TabWorkArea * twa = d.tabWorkArea(i);
350 QMainWindow::close();
351 d.quitting_by_menu_ = false;
355 void GuiView::setFocus()
357 if (d.current_work_area_)
358 d.current_work_area_->setFocus();
364 QMenu * GuiView::createPopupMenu()
366 return d.toolBarPopup(this);
370 void GuiView::showEvent(QShowEvent * e)
372 LYXERR(Debug::GUI, "Passed Geometry "
373 << size().height() << "x" << size().width()
374 << "+" << pos().x() << "+" << pos().y());
376 if (d.splitter_->count() == 0)
377 // No work area, switch to the background widget.
380 QMainWindow::showEvent(e);
384 void GuiView::closeEvent(QCloseEvent * close_event)
386 // we may have been called through the close window button
387 // which bypasses the LFUN machinery.
388 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
389 if (!quitWriteAll()) {
390 close_event->ignore();
395 // Make sure that no LFUN use this close to be closed View.
396 theLyXFunc().setLyXView(0);
397 // Make sure the timer time out will not trigger a statusbar update.
398 d.statusbar_timer_.stop();
400 if (lyxrc.allow_geometry_session) {
402 QString const key = "view-" + QString::number(id_);
404 settings.setValue(key + "/pos", pos());
405 settings.setValue(key + "/size", size());
407 settings.setValue(key + "/geometry", saveGeometry());
409 settings.setValue(key + "/icon_size", iconSize());
410 d.toolbars_->saveToolbarInfo();
411 // Now take care of all other dialogs:
412 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
413 for (; it!= d.dialogs_.end(); ++it)
414 it->second->saveSession();
417 guiApp->unregisterView(id_);
418 if (guiApp->viewCount() > 0) {
419 // Just close the window and do nothing else if this is not the
421 close_event->accept();
427 // this is the place where we leave the frontend.
428 // it is the only point at which we start quitting.
429 close_event->accept();
430 // quit the event loop
435 void GuiView::dragEnterEvent(QDragEnterEvent * event)
437 if (event->mimeData()->hasUrls())
439 /// \todo Ask lyx-devel is this is enough:
440 /// if (event->mimeData()->hasFormat("text/plain"))
441 /// event->acceptProposedAction();
445 void GuiView::dropEvent(QDropEvent* event)
447 QList<QUrl> files = event->mimeData()->urls();
451 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
452 for (int i = 0; i != files.size(); ++i) {
453 string const file = os::internal_path(fromqstr(
454 files.at(i).toLocalFile()));
456 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
461 void GuiView::message(docstring const & str)
463 if (ForkedProcess::iAmAChild())
466 statusBar()->showMessage(toqstr(str));
467 d.statusbar_timer_.stop();
468 d.statusbar_timer_.start(3000);
472 void GuiView::smallSizedIcons()
474 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
478 void GuiView::normalSizedIcons()
480 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
484 void GuiView::bigSizedIcons()
486 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
490 void GuiView::clearMessage()
494 theLyXFunc().setLyXView(this);
495 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
496 d.statusbar_timer_.stop();
500 void GuiView::updateWindowTitle(GuiWorkArea * wa)
502 if (wa != d.current_work_area_)
504 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
505 setWindowIconText(wa->windowIconText());
509 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
512 disconnectBufferView();
513 connectBufferView(wa->bufferView());
514 connectBuffer(wa->bufferView().buffer());
515 d.current_work_area_ = wa;
516 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
517 this, SLOT(updateWindowTitle(GuiWorkArea *)));
518 updateWindowTitle(wa);
521 // Buffer-dependent dialogs should be updated or
522 // hidden. This should go here because some dialogs (eg ToC)
523 // require bv_->text.
524 updateBufferDependent(true);
531 void GuiView::updateStatusBar()
533 // let the user see the explicit message
534 if (d.statusbar_timer_.isActive())
537 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
541 bool GuiView::hasFocus() const
543 return qApp->activeWindow() == this;
547 bool GuiView::event(QEvent * e)
551 // Useful debug code:
552 //case QEvent::ActivationChange:
553 //case QEvent::WindowDeactivate:
554 //case QEvent::Paint:
555 //case QEvent::Enter:
556 //case QEvent::Leave:
557 //case QEvent::HoverEnter:
558 //case QEvent::HoverLeave:
559 //case QEvent::HoverMove:
560 //case QEvent::StatusTip:
561 //case QEvent::DragEnter:
562 //case QEvent::DragLeave:
566 case QEvent::WindowActivate: {
567 guiApp->setCurrentView(*this);
568 if (d.current_work_area_) {
569 BufferView & bv = d.current_work_area_->bufferView();
570 connectBufferView(bv);
571 connectBuffer(bv.buffer());
572 // The document structure, name and dialogs might have
573 // changed in another view.
574 updateBufferDependent(true);
576 setWindowTitle(qt_("LyX"));
577 setWindowIconText(qt_("LyX"));
579 return QMainWindow::event(e);
582 case QEvent::ShortcutOverride: {
583 if (d.current_work_area_)
584 // Nothing special to do.
585 return QMainWindow::event(e);
587 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
589 // Let Qt handle menu access and the Tab keys to navigate keys to navigate
591 if (ke->modifiers() & Qt::AltModifier || ke->key() == Qt::Key_Tab
592 || ke->key() == Qt::Key_Backtab)
593 return QMainWindow::event(e);
595 // Allow processing of shortcuts that are allowed even when no Buffer
597 theLyXFunc().setLyXView(this);
599 setKeySymbol(&sym, ke);
600 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
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);
798 bool const mathmacrotemplate =
799 lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled();
801 d.toolbars_->update(math, table, review, mathmacrotemplate);
803 d.toolbars_->update(false, false, false, false);
805 // update read-only status of open dialogs.
810 Buffer * GuiView::buffer()
812 if (d.current_work_area_)
813 return &d.current_work_area_->bufferView().buffer();
818 Buffer const * GuiView::buffer() const
820 if (d.current_work_area_)
821 return &d.current_work_area_->bufferView().buffer();
826 void GuiView::setBuffer(Buffer * newBuffer)
828 BOOST_ASSERT(newBuffer);
831 GuiWorkArea * wa = workArea(*newBuffer);
833 updateLabels(*newBuffer->masterBuffer());
834 wa = addWorkArea(*newBuffer);
836 //Disconnect the old buffer...there's no new one.
839 connectBuffer(*newBuffer);
840 connectBufferView(wa->bufferView());
841 setCurrentWorkArea(wa);
847 void GuiView::connectBuffer(Buffer & buf)
849 buf.setGuiDelegate(this);
853 void GuiView::disconnectBuffer()
855 if (d.current_work_area_)
856 d.current_work_area_->bufferView().setGuiDelegate(0);
860 void GuiView::connectBufferView(BufferView & bv)
862 bv.setGuiDelegate(this);
866 void GuiView::disconnectBufferView()
868 if (d.current_work_area_)
869 d.current_work_area_->bufferView().setGuiDelegate(0);
873 void GuiView::errors(string const & error_type)
875 ErrorList & el = buffer()->errorList(error_type);
877 showDialog("errorlist", error_type);
881 void GuiView::updateDialog(string const & name, string const & data)
883 if (!isDialogVisible(name))
886 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
887 if (it == d.dialogs_.end())
890 Dialog * const dialog = it->second.get();
891 if (dialog->isVisibleView())
892 dialog->updateData(data);
896 BufferView * GuiView::view()
898 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
902 void GuiView::updateToc()
904 updateDialog("toc", "");
908 void GuiView::updateEmbeddedFiles()
910 updateDialog("embedding", "");
914 void GuiView::autoSave()
916 LYXERR(Debug::INFO, "Running autoSave()");
919 view()->buffer().autoSave();
923 void GuiView::resetAutosaveTimers()
926 d.autosave_timeout_.restart();
930 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
934 Buffer * buf = buffer();
936 /* In LyX/Mac, when a dialog is open, the menus of the
937 application can still be accessed without giving focus to
938 the main window. In this case, we want to disable the menu
939 entries that are buffer-related.
941 Note that this code is not perfect, as bug 1941 attests:
942 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
944 if (cmd.origin == FuncRequest::MENU && !hasFocus())
948 case LFUN_BUFFER_WRITE:
949 enable = buf && (buf->isUnnamed() || !buf->isClean());
952 case LFUN_BUFFER_WRITE_AS:
956 case LFUN_TOOLBAR_TOGGLE:
957 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
960 case LFUN_DIALOG_TOGGLE:
961 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
962 // fall through to set "enable"
963 case LFUN_DIALOG_SHOW: {
964 string const name = cmd.getArg(0);
966 enable = name == "aboutlyx"
967 || name == "file" //FIXME: should be removed.
969 || name == "texinfo";
970 else if (name == "print")
971 enable = buf->isExportable("dvi")
972 && lyxrc.print_command != "none";
973 else if (name == "character") {
977 InsetCode ic = view()->cursor().inset().lyxCode();
978 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
981 else if (name == "latexlog")
982 enable = FileName(buf->logName()).isReadableFile();
983 else if (name == "spellchecker")
984 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
985 enable = !buf->isReadonly();
989 else if (name == "vclog")
990 enable = buf->lyxvc().inUse();
994 case LFUN_DIALOG_UPDATE: {
995 string const name = cmd.getArg(0);
997 enable = name == "prefs";
1001 case LFUN_INSET_APPLY: {
1006 string const name = cmd.getArg(0);
1007 Inset * inset = getOpenInset(name);
1009 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1011 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1012 // Every inset is supposed to handle this
1013 BOOST_ASSERT(false);
1017 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1018 flag |= getStatus(fr);
1020 enable = flag.enabled();
1032 flag.enabled(false);
1038 static FileName selectTemplateFile()
1040 FileDialog dlg(_("Select template file"));
1041 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1042 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1044 FileDialog::Result result =
1045 dlg.open(from_utf8(lyxrc.template_path),
1046 FileFilterList(_("LyX Documents (*.lyx)")),
1049 if (result.first == FileDialog::Later)
1051 if (result.second.empty())
1053 return FileName(to_utf8(result.second));
1057 void GuiView::newDocument(string const & filename, bool from_template)
1059 FileName initpath(lyxrc.document_path);
1060 Buffer * buf = buffer();
1062 FileName const trypath(buf->filePath());
1063 // If directory is writeable, use this as default.
1064 if (trypath.isDirWritable())
1068 string templatefile = from_template ?
1069 selectTemplateFile().absFilename() : string();
1071 if (filename.empty())
1072 b = newUnnamedFile(templatefile, initpath);
1074 b = newFile(filename, templatefile, true);
1078 // Ensure the cursor is correctly positionned on screen.
1079 view()->showCursor();
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 (!isLyXFilename(fname.absFilename()))
1192 fname.changeExtension(".lyx");
1194 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 (!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 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument())))
1378 menu->exec(QCursor::pos());
1381 case LFUN_FILE_INSERT:
1382 insertLyXFile(cmd.argument());
1384 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1385 insertPlaintextFile(cmd.argument(), true);
1388 case LFUN_FILE_INSERT_PLAINTEXT:
1389 insertPlaintextFile(cmd.argument(), false);
1392 case LFUN_BUFFER_WRITE:
1394 saveBuffer(bv->buffer());
1397 case LFUN_BUFFER_WRITE_AS:
1399 renameBuffer(bv->buffer(), cmd.argument());
1402 case LFUN_BUFFER_WRITE_ALL: {
1403 Buffer * first = theBufferList().first();
1406 message(_("Saving all documents..."));
1407 // We cannot use a for loop as the buffer list cycles.
1413 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1414 b = theBufferList().next(b);
1415 } while (b != first);
1416 message(_("All documents saved."));
1420 case LFUN_TOOLBAR_TOGGLE: {
1421 string const name = cmd.getArg(0);
1422 bool const allowauto = cmd.getArg(1) == "allowauto";
1423 // it is possible to get current toolbar status like this,...
1424 // but I decide to obey the order of ToolbarBackend::flags
1425 // and disregard real toolbar status.
1426 // toolbars_->saveToolbarInfo();
1428 // toggle state on/off/auto
1429 d.toolbars_->toggleToolbarState(name, allowauto);
1433 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1435 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1439 if (tbi->flags & ToolbarInfo::ON)
1441 else if (tbi->flags & ToolbarInfo::OFF)
1443 else if (tbi->flags & ToolbarInfo::AUTO)
1446 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1447 _(tbi->gui_name), state));
1451 case LFUN_DIALOG_UPDATE: {
1452 string const name = to_utf8(cmd.argument());
1453 // Can only update a dialog connected to an existing inset
1454 Inset * inset = getOpenInset(name);
1456 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1457 inset->dispatch(view()->cursor(), fr);
1458 } else if (name == "paragraph") {
1459 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1460 } else if (name == "prefs") {
1461 updateDialog(name, string());
1466 case LFUN_DIALOG_TOGGLE: {
1467 if (isDialogVisible(cmd.getArg(0)))
1468 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1470 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1474 case LFUN_DIALOG_DISCONNECT_INSET:
1475 disconnectDialog(to_utf8(cmd.argument()));
1478 case LFUN_DIALOG_HIDE: {
1481 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1485 case LFUN_DIALOG_SHOW: {
1486 string const name = cmd.getArg(0);
1487 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1489 if (name == "character") {
1490 data = freefont2string();
1492 showDialog("character", data);
1493 } else if (name == "latexlog") {
1494 Buffer::LogType type;
1495 string const logfile = buffer()->logName(&type);
1497 case Buffer::latexlog:
1500 case Buffer::buildlog:
1504 data += Lexer::quoteString(logfile);
1505 showDialog("log", data);
1506 } else if (name == "vclog") {
1507 string const data = "vc " +
1508 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1509 showDialog("log", data);
1511 showDialog(name, data);
1515 case LFUN_INSET_APPLY: {
1516 string const name = cmd.getArg(0);
1517 Inset * inset = getOpenInset(name);
1519 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1520 inset->dispatch(view()->cursor(), fr);
1522 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1536 Buffer const * GuiView::updateInset(Inset const * inset)
1538 if (!d.current_work_area_)
1542 d.current_work_area_->scheduleRedraw();
1544 return &d.current_work_area_->bufferView().buffer();
1548 void GuiView::restartCursor()
1550 /* When we move around, or type, it's nice to be able to see
1551 * the cursor immediately after the keypress.
1553 if (d.current_work_area_)
1554 d.current_work_area_->startBlinkingCursor();
1556 // Take this occasion to update the toobars and layout list.
1563 // This list should be kept in sync with the list of insets in
1564 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1565 // dialog should have the same name as the inset.
1567 char const * const dialognames[] = {
1568 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1569 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1570 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1571 "mathdelimiter", "mathmatrix", "note", "paragraph",
1572 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1574 #ifdef HAVE_LIBAIKSAURUS
1578 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1580 char const * const * const end_dialognames =
1581 dialognames + (sizeof(dialognames) / sizeof(char *));
1585 cmpCStr(char const * name) : name_(name) {}
1586 bool operator()(char const * other) {
1587 return strcmp(other, name_) == 0;
1594 bool isValidName(string const & name)
1596 return find_if(dialognames, end_dialognames,
1597 cmpCStr(name.c_str())) != end_dialognames;
1603 void GuiView::resetDialogs()
1605 // Make sure that no LFUN uses any LyXView.
1606 theLyXFunc().setLyXView(0);
1607 // FIXME: the "math panels" toolbar takes an awful lot of time to
1608 // initialise so we don't do that for the time being.
1609 //d.toolbars_->init();
1610 guiApp->menus().fillMenuBar(this);
1612 d.layout_->updateContents(true);
1613 // Now update controls with current buffer.
1614 theLyXFunc().setLyXView(this);
1619 Dialog * GuiView::find_or_build(string const & name)
1621 if (!isValidName(name))
1624 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1626 if (it != d.dialogs_.end())
1627 return it->second.get();
1629 Dialog * dialog = build(name);
1630 d.dialogs_[name].reset(dialog);
1631 if (lyxrc.allow_geometry_session)
1632 dialog->restoreSession();
1637 void GuiView::showDialog(string const & name, string const & data,
1644 Dialog * dialog = find_or_build(name);
1646 dialog->showData(data);
1648 d.open_insets_[name] = inset;
1654 bool GuiView::isDialogVisible(string const & name) const
1656 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1657 if (it == d.dialogs_.end())
1659 return it->second.get()->isVisibleView();
1663 void GuiView::hideDialog(string const & name, Inset * inset)
1665 // Don't send the signal if we are quitting, because on MSVC it is
1666 // destructed before the cut stack in CutAndPaste.cpp, and this method
1667 // is called from some inset destructor if the cut stack is not empty
1672 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1673 if (it == d.dialogs_.end())
1676 if (inset && inset != getOpenInset(name))
1679 Dialog * const dialog = it->second.get();
1680 if (dialog->isVisibleView())
1682 d.open_insets_[name] = 0;
1686 void GuiView::disconnectDialog(string const & name)
1688 if (!isValidName(name))
1691 if (d.open_insets_.find(name) != d.open_insets_.end())
1692 d.open_insets_[name] = 0;
1696 Inset * GuiView::getOpenInset(string const & name) const
1698 if (!isValidName(name))
1701 map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1702 return it == d.open_insets_.end() ? 0 : it->second;
1706 void GuiView::hideAll() const
1708 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1709 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1711 for(; it != end; ++it)
1712 it->second->hideView();
1716 void GuiView::hideBufferDependent() const
1718 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1719 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1721 for(; it != end; ++it) {
1722 Dialog * dialog = it->second.get();
1723 if (dialog->isBufferDependent())
1729 void GuiView::updateBufferDependent(bool switched) const
1731 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1732 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1734 for(; it != end; ++it) {
1735 Dialog * dialog = it->second.get();
1736 if (!dialog->isVisibleView())
1738 if (switched && dialog->isBufferDependent()) {
1739 if (dialog->initialiseParams(""))
1740 dialog->updateView();
1744 // A bit clunky, but the dialog will request
1745 // that the kernel provides it with the necessary
1747 dialog->updateDialog();
1753 void GuiView::checkStatus()
1755 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1756 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1758 for(; it != end; ++it) {
1759 Dialog * const dialog = it->second.get();
1760 if (dialog && dialog->isVisibleView())
1761 dialog->checkStatus();
1767 // will be replaced by a proper factory...
1768 Dialog * createGuiAbout(GuiView & lv);
1769 Dialog * createGuiBibitem(GuiView & lv);
1770 Dialog * createGuiBibtex(GuiView & lv);
1771 Dialog * createGuiBox(GuiView & lv);
1772 Dialog * createGuiBranch(GuiView & lv);
1773 Dialog * createGuiChanges(GuiView & lv);
1774 Dialog * createGuiCharacter(GuiView & lv);
1775 Dialog * createGuiCitation(GuiView & lv);
1776 Dialog * createGuiDelimiter(GuiView & lv);
1777 Dialog * createGuiDocument(GuiView & lv);
1778 Dialog * createGuiErrorList(GuiView & lv);
1779 Dialog * createGuiERT(GuiView & lv);
1780 Dialog * createGuiExternal(GuiView & lv);
1781 Dialog * createGuiFloat(GuiView & lv);
1782 Dialog * createGuiGraphics(GuiView & lv);
1783 Dialog * createGuiInclude(GuiView & lv);
1784 Dialog * createGuiIndex(GuiView & lv);
1785 Dialog * createGuiLabel(GuiView & lv);
1786 Dialog * createGuiListings(GuiView & lv);
1787 Dialog * createGuiLog(GuiView & lv);
1788 Dialog * createGuiMathMatrix(GuiView & lv);
1789 Dialog * createGuiNomenclature(GuiView & lv);
1790 Dialog * createGuiNote(GuiView & lv);
1791 Dialog * createGuiParagraph(GuiView & lv);
1792 Dialog * createGuiPreferences(GuiView & lv);
1793 Dialog * createGuiPrint(GuiView & lv);
1794 Dialog * createGuiRef(GuiView & lv);
1795 Dialog * createGuiSearch(GuiView & lv);
1796 Dialog * createGuiSendTo(GuiView & lv);
1797 Dialog * createGuiShowFile(GuiView & lv);
1798 Dialog * createGuiSpellchecker(GuiView & lv);
1799 Dialog * createGuiTabularCreate(GuiView & lv);
1800 Dialog * createGuiTabular(GuiView & lv);
1801 Dialog * createGuiTexInfo(GuiView & lv);
1802 Dialog * createGuiToc(GuiView & lv);
1803 Dialog * createGuiThesaurus(GuiView & lv);
1804 Dialog * createGuiHyperlink(GuiView & lv);
1805 Dialog * createGuiVSpace(GuiView & lv);
1806 Dialog * createGuiViewSource(GuiView & lv);
1807 Dialog * createGuiWrap(GuiView & lv);
1810 Dialog * GuiView::build(string const & name)
1812 BOOST_ASSERT(isValidName(name));
1814 if (name == "aboutlyx")
1815 return createGuiAbout(*this);
1816 if (name == "bibitem")
1817 return createGuiBibitem(*this);
1818 if (name == "bibtex")
1819 return createGuiBibtex(*this);
1821 return createGuiBox(*this);
1822 if (name == "branch")
1823 return createGuiBranch(*this);
1824 if (name == "changes")
1825 return createGuiChanges(*this);
1826 if (name == "character")
1827 return createGuiCharacter(*this);
1828 if (name == "citation")
1829 return createGuiCitation(*this);
1830 if (name == "document")
1831 return createGuiDocument(*this);
1832 if (name == "errorlist")
1833 return createGuiErrorList(*this);
1835 return createGuiERT(*this);
1836 if (name == "external")
1837 return createGuiExternal(*this);
1839 return createGuiShowFile(*this);
1840 if (name == "findreplace")
1841 return createGuiSearch(*this);
1842 if (name == "float")
1843 return createGuiFloat(*this);
1844 if (name == "graphics")
1845 return createGuiGraphics(*this);
1846 if (name == "include")
1847 return createGuiInclude(*this);
1848 if (name == "index")
1849 return createGuiIndex(*this);
1850 if (name == "nomenclature")
1851 return createGuiNomenclature(*this);
1852 if (name == "label")
1853 return createGuiLabel(*this);
1855 return createGuiLog(*this);
1856 if (name == "view-source")
1857 return createGuiViewSource(*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"