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>
103 using support::addPath;
104 using support::bformat;
105 using support::FileFilterList;
106 using support::FileName;
107 using support::makeAbsPath;
108 using support::makeDisplayPath;
109 using support::package;
110 using support::removeAutosaveFile;
115 class BackgroundWidget : public QWidget
120 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
121 /// The text to be written on top of the pixmap
122 QString const text = lyx_version ? lyx_version : qt_("unknown version");
123 splash_ = QPixmap(":/images/banner.png");
125 QPainter pain(&splash_);
126 pain.setPen(QColor(255, 255, 0));
128 // The font used to display the version info
129 font.setStyleHint(QFont::SansSerif);
130 font.setWeight(QFont::Bold);
131 font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble()));
133 pain.drawText(260, 270, text);
136 void paintEvent(QPaintEvent *)
138 int x = (width() - splash_.width()) / 2;
139 int y = (height() - splash_.height()) / 2;
141 pain.drawPixmap(x, y, splash_);
151 typedef boost::shared_ptr<Dialog> DialogPtr;
153 struct GuiView::GuiViewPrivate
156 : current_work_area_(0), layout_(0),
157 quitting_by_menu_(false), autosave_timeout_(5000), in_show_(false)
159 // hardcode here the platform specific icon size
160 smallIconSize = 14; // scaling problems
161 normalIconSize = 20; // ok, default
162 bigIconSize = 26; // better for some math icons
164 splitter_ = new QSplitter;
165 bg_widget_ = new BackgroundWidget;
166 stack_widget_ = new QStackedWidget;
167 stack_widget_->addWidget(bg_widget_);
168 stack_widget_->addWidget(splitter_);
176 delete stack_widget_;
181 QMenu * toolBarPopup(GuiView * parent)
183 // FIXME: translation
184 QMenu * menu = new QMenu(parent);
185 QActionGroup * iconSizeGroup = new QActionGroup(parent);
187 QAction * smallIcons = new QAction(iconSizeGroup);
188 smallIcons->setText(qt_("Small-sized icons"));
189 smallIcons->setCheckable(true);
190 QObject::connect(smallIcons, SIGNAL(triggered()),
191 parent, SLOT(smallSizedIcons()));
192 menu->addAction(smallIcons);
194 QAction * normalIcons = new QAction(iconSizeGroup);
195 normalIcons->setText(qt_("Normal-sized icons"));
196 normalIcons->setCheckable(true);
197 QObject::connect(normalIcons, SIGNAL(triggered()),
198 parent, SLOT(normalSizedIcons()));
199 menu->addAction(normalIcons);
201 QAction * bigIcons = new QAction(iconSizeGroup);
202 bigIcons->setText(qt_("Big-sized icons"));
203 bigIcons->setCheckable(true);
204 QObject::connect(bigIcons, SIGNAL(triggered()),
205 parent, SLOT(bigSizedIcons()));
206 menu->addAction(bigIcons);
208 unsigned int cur = parent->iconSize().width();
209 if ( cur == parent->d.smallIconSize)
210 smallIcons->setChecked(true);
211 else if (cur == parent->d.normalIconSize)
212 normalIcons->setChecked(true);
213 else if (cur == parent->d.bigIconSize)
214 bigIcons->setChecked(true);
221 stack_widget_->setCurrentWidget(bg_widget_);
222 bg_widget_->setUpdatesEnabled(true);
225 TabWorkArea * tabWorkArea(int i)
227 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
230 TabWorkArea * currentTabWorkArea()
232 if (splitter_->count() == 1)
233 // The first TabWorkArea is always the first one, if any.
234 return tabWorkArea(0);
236 TabWorkArea * tab_widget = 0;
237 for (int i = 0; i != splitter_->count(); ++i) {
238 QWidget * w = splitter_->widget(i);
241 tab_widget = dynamic_cast<TabWorkArea *>(w);
250 GuiWorkArea * current_work_area_;
251 QSplitter * splitter_;
252 QStackedWidget * stack_widget_;
253 BackgroundWidget * bg_widget_;
255 GuiMenubar * menubar_;
257 GuiToolbars * toolbars_;
258 /// The main layout box.
260 * \warning Don't Delete! The layout box is actually owned by
261 * whichever toolbar contains it. All the GuiView class needs is a
262 * means of accessing it.
264 * FIXME: replace that with a proper model so that we are not limited
265 * to only one dialog.
267 GuiLayoutBox * layout_;
270 std::map<std::string, Inset *> open_insets_;
273 std::map<std::string, DialogPtr> dialogs_;
275 unsigned int smallIconSize;
276 unsigned int normalIconSize;
277 unsigned int bigIconSize;
279 QTimer statusbar_timer_;
280 /// are we quitting by the menu?
281 bool quitting_by_menu_;
282 /// auto-saving of buffers
283 Timeout autosave_timeout_;
284 /// flag against a race condition due to multiclicks, see bug #1119
289 GuiView::GuiView(int id)
290 : d(*new GuiViewPrivate), id_(id)
292 // GuiToolbars *must* be initialised before GuiMenubar.
293 d.toolbars_ = new GuiToolbars(*this);
294 d.menubar_ = new GuiMenubar(this, menubackend);
296 setCentralWidget(d.stack_widget_);
298 // Start autosave timer
299 if (lyxrc.autosave) {
300 d.autosave_timeout_.timeout.connect(boost::bind(&GuiView::autoSave, this));
301 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
302 d.autosave_timeout_.start();
304 connect(&d.statusbar_timer_, SIGNAL(timeout()),
305 this, SLOT(clearMessage()));
307 // Qt bug? signal lastWindowClosed does not work
308 setAttribute(Qt::WA_QuitOnClose, false);
309 setAttribute(Qt::WA_DeleteOnClose, true);
311 // assign an icon to main form. We do not do it under Qt/Mac,
312 // since the icon is provided in the application bundle.
313 setWindowIcon(QPixmap(":/images/lyx.png"));
317 setAcceptDrops(true);
319 statusBar()->setSizeGripEnabled(true);
321 // Forbid too small unresizable window because it can happen
322 // with some window manager under X11.
323 setMinimumSize(300, 200);
325 if (!lyxrc.allow_geometry_session)
326 // No session handling, default to a sane size.
327 setGeometry(50, 50, 690, 510);
329 // Now take care of session management.
331 QString const key = "view-" + QString::number(id_);
333 QPoint pos = settings.value(key + "/pos", QPoint(50, 50)).toPoint();
334 QSize size = settings.value(key + "/size", QSize(690, 510)).toSize();
338 if (!restoreGeometry(settings.value(key + "/geometry").toByteArray()))
339 setGeometry(50, 50, 690, 510);
341 setIconSize(settings.value(key + "/icon_size").toSize());
351 void GuiView::close()
353 d.quitting_by_menu_ = true;
354 d.current_work_area_ = 0;
355 for (int i = 0; i != d.splitter_->count(); ++i) {
356 TabWorkArea * twa = d.tabWorkArea(i);
360 QMainWindow::close();
361 d.quitting_by_menu_ = false;
365 void GuiView::setFocus()
367 if (d.current_work_area_)
368 d.current_work_area_->setFocus();
374 QMenu * GuiView::createPopupMenu()
376 return d.toolBarPopup(this);
380 void GuiView::showEvent(QShowEvent * e)
382 LYXERR(Debug::GUI, "Passed Geometry "
383 << size().height() << "x" << size().width()
384 << "+" << pos().x() << "+" << pos().y());
386 if (d.splitter_->count() == 0)
387 // No work area, switch to the background widget.
390 QMainWindow::showEvent(e);
394 void GuiView::closeEvent(QCloseEvent * close_event)
396 // we may have been called through the close window button
397 // which bypasses the LFUN machinery.
398 if (!d.quitting_by_menu_ && guiApp->viewCount() == 1) {
399 if (!quitWriteAll()) {
400 close_event->ignore();
405 // Make sure that no LFUN use this close to be closed View.
406 theLyXFunc().setLyXView(0);
407 // Make sure the timer time out will not trigger a statusbar update.
408 d.statusbar_timer_.stop();
410 if (lyxrc.allow_geometry_session) {
412 QString const key = "view-" + QString::number(id_);
414 settings.setValue(key + "/pos", pos());
415 settings.setValue(key + "/size", size());
417 settings.setValue(key + "/geometry", saveGeometry());
419 settings.setValue(key + "/icon_size", iconSize());
420 d.toolbars_->saveToolbarInfo();
421 // Now take care of all other dialogs:
422 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
423 for (; it!= d.dialogs_.end(); ++it)
424 it->second->saveSession();
427 guiApp->unregisterView(id_);
428 if (guiApp->viewCount() > 0) {
429 // Just close the window and do nothing else if this is not the
431 close_event->accept();
437 // this is the place where we leave the frontend.
438 // it is the only point at which we start quitting.
439 close_event->accept();
440 // quit the event loop
445 void GuiView::dragEnterEvent(QDragEnterEvent * event)
447 if (event->mimeData()->hasUrls())
449 /// \todo Ask lyx-devel is this is enough:
450 /// if (event->mimeData()->hasFormat("text/plain"))
451 /// event->acceptProposedAction();
455 void GuiView::dropEvent(QDropEvent* event)
457 QList<QUrl> files = event->mimeData()->urls();
461 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
462 for (int i = 0; i != files.size(); ++i) {
463 string const file = support::os::internal_path(fromqstr(
464 files.at(i).toLocalFile()));
466 lyx::dispatch(FuncRequest(LFUN_FILE_OPEN, file));
471 void GuiView::message(docstring const & str)
473 statusBar()->showMessage(toqstr(str));
474 d.statusbar_timer_.stop();
475 d.statusbar_timer_.start(3000);
479 void GuiView::smallSizedIcons()
481 setIconSize(QSize(d.smallIconSize, d.smallIconSize));
485 void GuiView::normalSizedIcons()
487 setIconSize(QSize(d.normalIconSize, d.normalIconSize));
491 void GuiView::bigSizedIcons()
493 setIconSize(QSize(d.bigIconSize, d.bigIconSize));
497 void GuiView::clearMessage()
501 theLyXFunc().setLyXView(this);
502 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
503 d.statusbar_timer_.stop();
507 void GuiView::updateWindowTitle(GuiWorkArea * wa)
509 if (wa != d.current_work_area_)
511 setWindowTitle(qt_("LyX: ") + wa->windowTitle());
512 setWindowIconText(wa->windowIconText());
516 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
519 disconnectBufferView();
520 connectBufferView(wa->bufferView());
521 connectBuffer(wa->bufferView().buffer());
522 d.current_work_area_ = wa;
523 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
524 this, SLOT(updateWindowTitle(GuiWorkArea *)));
525 updateWindowTitle(wa);
528 // Buffer-dependent dialogs should be updated or
529 // hidden. This should go here because some dialogs (eg ToC)
530 // require bv_->text.
531 updateBufferDependent(true);
538 void GuiView::updateStatusBar()
540 // let the user see the explicit message
541 if (d.statusbar_timer_.isActive())
544 statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
548 bool GuiView::hasFocus() const
550 return qApp->activeWindow() == this;
554 bool GuiView::event(QEvent * e)
558 // Useful debug code:
559 //case QEvent::ActivationChange:
560 //case QEvent::WindowDeactivate:
561 //case QEvent::Paint:
562 //case QEvent::Enter:
563 //case QEvent::Leave:
564 //case QEvent::HoverEnter:
565 //case QEvent::HoverLeave:
566 //case QEvent::HoverMove:
567 //case QEvent::StatusTip:
568 //case QEvent::DragEnter:
569 //case QEvent::DragLeave:
573 case QEvent::WindowActivate: {
574 guiApp->setCurrentView(*this);
575 if (d.current_work_area_) {
576 BufferView & bv = d.current_work_area_->bufferView();
577 connectBufferView(bv);
578 connectBuffer(bv.buffer());
579 // The document structure, name and dialogs might have
580 // changed in another view.
581 updateBufferDependent(true);
583 setWindowTitle(qt_("LyX"));
584 setWindowIconText(qt_("LyX"));
586 return QMainWindow::event(e);
588 case QEvent::ShortcutOverride: {
589 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
590 if (!d.current_work_area_) {
591 theLyXFunc().setLyXView(this);
593 setKeySymbol(&sym, ke);
594 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
598 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
600 setKeySymbol(&sym, ke);
601 d.current_work_area_->processKeySym(sym, NoModifier);
607 return QMainWindow::event(e);
612 bool GuiView::focusNextPrevChild(bool /*next*/)
619 void GuiView::setBusy(bool yes)
621 if (d.current_work_area_) {
622 d.current_work_area_->setUpdatesEnabled(!yes);
624 d.current_work_area_->stopBlinkingCursor();
626 d.current_work_area_->startBlinkingCursor();
630 QApplication::setOverrideCursor(Qt::WaitCursor);
632 QApplication::restoreOverrideCursor();
636 GuiToolbar * GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
638 GuiToolbar * toolBar = new GuiToolbar(tbinfo, *this);
640 if (tbinfo.flags & ToolbarInfo::TOP) {
642 addToolBarBreak(Qt::TopToolBarArea);
643 addToolBar(Qt::TopToolBarArea, toolBar);
646 if (tbinfo.flags & ToolbarInfo::BOTTOM) {
647 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
648 #if (QT_VERSION >= 0x040202)
650 addToolBarBreak(Qt::BottomToolBarArea);
652 addToolBar(Qt::BottomToolBarArea, toolBar);
655 if (tbinfo.flags & ToolbarInfo::LEFT) {
656 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
657 #if (QT_VERSION >= 0x040202)
659 addToolBarBreak(Qt::LeftToolBarArea);
661 addToolBar(Qt::LeftToolBarArea, toolBar);
664 if (tbinfo.flags & ToolbarInfo::RIGHT) {
665 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
666 #if (QT_VERSION >= 0x040202)
668 addToolBarBreak(Qt::RightToolBarArea);
670 addToolBar(Qt::RightToolBarArea, toolBar);
673 // The following does not work so I cannot restore to exact toolbar location
675 ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
676 toolBar->move(tbinfo.posx, tbinfo.posy);
683 GuiWorkArea * GuiView::workArea(Buffer & buffer)
685 for (int i = 0; i != d.splitter_->count(); ++i) {
686 GuiWorkArea * wa = d.tabWorkArea(i)->workArea(buffer);
694 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
697 // Automatically create a TabWorkArea if there are none yet.
698 if (!d.splitter_->count())
701 TabWorkArea * tab_widget = d.currentTabWorkArea();
702 return tab_widget->addWorkArea(buffer, *this);
706 void GuiView::addTabWorkArea()
708 TabWorkArea * twa = new TabWorkArea;
709 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
710 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
711 d.splitter_->addWidget(twa);
712 d.stack_widget_->setCurrentWidget(d.splitter_);
716 GuiWorkArea const * GuiView::currentWorkArea() const
718 return d.current_work_area_;
722 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
726 // Changing work area can result from opening a file so
727 // update the toc in any case.
730 d.current_work_area_ = wa;
731 for (int i = 0; i != d.splitter_->count(); ++i) {
732 if (d.tabWorkArea(i)->setCurrentWorkArea(wa))
738 void GuiView::removeWorkArea(GuiWorkArea * wa)
741 if (wa == d.current_work_area_) {
743 disconnectBufferView();
744 hideBufferDependent();
745 d.current_work_area_ = 0;
748 for (int i = 0; i != d.splitter_->count(); ++i) {
749 TabWorkArea * twa = d.tabWorkArea(i);
750 if (!twa->removeWorkArea(wa))
751 // Not found in this tab group.
754 // We found and removed the GuiWorkArea.
756 // No more WorkAreas in this tab group, so delete it.
761 if (d.current_work_area_)
762 // This means that we are not closing the current GuiWorkArea;
765 // Switch to the next GuiWorkArea in the found TabWorkArea.
766 d.current_work_area_ = twa->currentWorkArea();
770 if (d.splitter_->count() == 0)
771 // No more work area, switch to the background widget.
776 void GuiView::setLayoutDialog(GuiLayoutBox * layout)
782 void GuiView::updateLayoutList()
785 d.layout_->updateContents(false);
789 void GuiView::updateToolbars()
791 if (d.current_work_area_) {
793 d.current_work_area_->bufferView().cursor().inMathed();
795 lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled();
797 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled() &&
798 lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onoff(true);
800 d.toolbars_->update(math, table, review);
802 d.toolbars_->update(false, false, false);
804 // update read-only status of open dialogs.
809 Buffer * GuiView::buffer()
811 if (d.current_work_area_)
812 return &d.current_work_area_->bufferView().buffer();
817 Buffer const * GuiView::buffer() const
819 if (d.current_work_area_)
820 return &d.current_work_area_->bufferView().buffer();
825 void GuiView::setBuffer(Buffer * newBuffer)
827 BOOST_ASSERT(newBuffer);
830 GuiWorkArea * wa = workArea(*newBuffer);
832 updateLabels(*newBuffer->masterBuffer());
833 wa = addWorkArea(*newBuffer);
835 //Disconnect the old buffer...there's no new one.
838 connectBuffer(*newBuffer);
839 connectBufferView(wa->bufferView());
840 setCurrentWorkArea(wa);
846 void GuiView::connectBuffer(Buffer & buf)
848 buf.setGuiDelegate(this);
852 void GuiView::disconnectBuffer()
854 if (d.current_work_area_)
855 d.current_work_area_->bufferView().setGuiDelegate(0);
859 void GuiView::connectBufferView(BufferView & bv)
861 bv.setGuiDelegate(this);
865 void GuiView::disconnectBufferView()
867 if (d.current_work_area_)
868 d.current_work_area_->bufferView().setGuiDelegate(0);
872 void GuiView::errors(string const & error_type)
874 ErrorList & el = buffer()->errorList(error_type);
876 showDialog("errorlist", error_type);
880 void GuiView::updateDialog(string const & name, string const & data)
882 if (!isDialogVisible(name))
885 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
886 if (it == d.dialogs_.end())
889 Dialog * const dialog = it->second.get();
890 if (dialog->isVisibleView())
891 dialog->updateData(data);
895 BufferView * GuiView::view()
897 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
901 void GuiView::updateToc()
903 updateDialog("toc", "");
907 void GuiView::updateEmbeddedFiles()
909 updateDialog("embedding", "");
913 void GuiView::autoSave()
915 LYXERR(Debug::INFO, "Running autoSave()");
918 view()->buffer().autoSave();
922 void GuiView::resetAutosaveTimers()
925 d.autosave_timeout_.restart();
929 FuncStatus GuiView::getStatus(FuncRequest const & cmd)
933 Buffer * buf = buffer();
935 /* In LyX/Mac, when a dialog is open, the menus of the
936 application can still be accessed without giving focus to
937 the main window. In this case, we want to disable the menu
938 entries that are buffer-related.
940 Note that this code is not perfect, as bug 1941 attests:
941 http://bugzilla.lyx.org/show_bug.cgi?id=1941#c4
943 if (cmd.origin == FuncRequest::MENU && !hasFocus())
947 case LFUN_BUFFER_WRITE:
948 enable = buf && (buf->isUnnamed() || !buf->isClean());
951 case LFUN_BUFFER_WRITE_AS:
955 case LFUN_TOOLBAR_TOGGLE:
956 flag.setOnOff(d.toolbars_->visible(cmd.getArg(0)));
959 case LFUN_DIALOG_TOGGLE:
960 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
961 // fall through to set "enable"
962 case LFUN_DIALOG_SHOW: {
963 string const name = cmd.getArg(0);
965 enable = name == "aboutlyx"
966 || name == "file" //FIXME: should be removed.
968 || name == "texinfo";
969 else if (name == "print")
970 enable = buf->isExportable("dvi")
971 && lyxrc.print_command != "none";
972 else if (name == "character") {
976 InsetCode ic = view()->cursor().inset().lyxCode();
977 enable = ic != ERT_CODE && ic != LISTINGS_CODE;
980 else if (name == "latexlog")
981 enable = FileName(buf->logName()).isReadableFile();
982 else if (name == "spellchecker")
983 #if defined (USE_ASPELL) || defined (USE_ISPELL) || defined (USE_PSPELL)
984 enable = !buf->isReadonly();
988 else if (name == "vclog")
989 enable = buf->lyxvc().inUse();
993 case LFUN_DIALOG_UPDATE: {
994 string const name = cmd.getArg(0);
996 enable = name == "prefs";
1000 case LFUN_INSET_APPLY: {
1005 string const name = cmd.getArg(0);
1006 Inset * inset = getOpenInset(name);
1008 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1010 if (!inset->getStatus(view()->cursor(), fr, fs)) {
1011 // Every inset is supposed to handle this
1012 BOOST_ASSERT(false);
1016 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1017 flag |= getStatus(fr);
1019 enable = flag.enabled();
1031 flag.enabled(false);
1037 static FileName selectTemplateFile()
1039 FileDialog dlg(_("Select template file"));
1040 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1041 dlg.setButton1(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1043 FileDialog::Result result =
1044 dlg.open(from_utf8(lyxrc.template_path),
1045 FileFilterList(_("LyX Documents (*.lyx)")),
1048 if (result.first == FileDialog::Later)
1050 if (result.second.empty())
1052 return FileName(to_utf8(result.second));
1056 void GuiView::newDocument(string const & filename, bool from_template)
1058 FileName initpath(lyxrc.document_path);
1059 Buffer * buf = buffer();
1061 FileName const trypath(buf->filePath());
1062 // If directory is writeable, use this as default.
1063 if (trypath.isDirWritable())
1067 string templatefile = from_template ?
1068 selectTemplateFile().absFilename() : string();
1070 if (filename.empty())
1071 b = newUnnamedFile(templatefile, initpath);
1073 b = newFile(filename, templatefile, true);
1080 void GuiView::insertLyXFile(docstring const & fname)
1082 BufferView * bv = view();
1087 FileName filename(to_utf8(fname));
1089 if (!filename.empty()) {
1090 bv->insertLyXFile(filename);
1094 // Launch a file browser
1096 string initpath = lyxrc.document_path;
1097 string const trypath = bv->buffer().filePath();
1098 // If directory is writeable, use this as default.
1099 if (FileName(trypath).isDirWritable())
1103 FileDialog dlg(_("Select LyX document to insert"), LFUN_FILE_INSERT);
1104 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1105 dlg.setButton2(_("Examples|#E#e"),
1106 from_utf8(addPath(package().system_support().absFilename(),
1109 FileDialog::Result result =
1110 dlg.open(from_utf8(initpath),
1111 FileFilterList(_("LyX Documents (*.lyx)")),
1114 if (result.first == FileDialog::Later)
1118 filename.set(to_utf8(result.second));
1120 // check selected filename
1121 if (filename.empty()) {
1122 // emit message signal.
1123 message(_("Canceled."));
1127 bv->insertLyXFile(filename);
1131 void GuiView::insertPlaintextFile(docstring const & fname,
1134 BufferView * bv = view();
1139 FileName filename(to_utf8(fname));
1141 if (!filename.empty()) {
1142 bv->insertPlaintextFile(filename, asParagraph);
1146 FileDialog dlg(_("Select file to insert"), (asParagraph ?
1147 LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT));
1149 FileDialog::Result result = dlg.open(from_utf8(bv->buffer().filePath()),
1150 FileFilterList(), docstring());
1152 if (result.first == FileDialog::Later)
1156 filename.set(to_utf8(result.second));
1158 // check selected filename
1159 if (filename.empty()) {
1160 // emit message signal.
1161 message(_("Canceled."));
1165 bv->insertPlaintextFile(filename, asParagraph);
1169 bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
1171 FileName fname = b.fileName();
1172 FileName const oldname = fname;
1174 if (!newname.empty()) {
1176 fname = makeAbsPath(to_utf8(newname), oldname.onlyPath().absFilename());
1178 // Switch to this Buffer.
1181 /// No argument? Ask user through dialog.
1183 FileDialog dlg(_("Choose a filename to save document as"),
1184 LFUN_BUFFER_WRITE_AS);
1185 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
1186 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
1188 if (!support::isLyXFilename(fname.absFilename()))
1189 fname.changeExtension(".lyx");
1191 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
1193 FileDialog::Result result =
1194 dlg.save(from_utf8(fname.onlyPath().absFilename()),
1196 from_utf8(fname.onlyFileName()));
1198 if (result.first == FileDialog::Later)
1201 fname.set(to_utf8(result.second));
1206 if (!support::isLyXFilename(fname.absFilename()))
1207 fname.changeExtension(".lyx");
1210 if (FileName(fname).exists()) {
1211 docstring const file = makeDisplayPath(fname.absFilename(), 30);
1212 docstring text = bformat(_("The document %1$s already "
1213 "exists.\n\nDo you want to "
1214 "overwrite that document?"),
1216 int const ret = Alert::prompt(_("Overwrite document?"),
1217 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
1220 case 1: return renameBuffer(b, docstring());
1221 case 2: return false;
1225 // Ok, change the name of the buffer
1226 b.setFileName(fname.absFilename());
1228 bool unnamed = b.isUnnamed();
1229 b.setUnnamed(false);
1230 b.saveCheckSum(fname);
1232 if (!saveBuffer(b)) {
1233 b.setFileName(oldname.absFilename());
1234 b.setUnnamed(unnamed);
1235 b.saveCheckSum(oldname);
1243 bool GuiView::saveBuffer(Buffer & b)
1246 return renameBuffer(b, docstring());
1249 LyX::ref().session().lastFiles().add(b.fileName());
1253 // Switch to this Buffer.
1256 // FIXME: we don't tell the user *WHY* the save failed !!
1257 docstring const file = makeDisplayPath(b.absFileName(), 30);
1258 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
1259 "Do you want to rename the document and "
1260 "try again?"), file);
1261 int const ret = Alert::prompt(_("Rename and save?"),
1262 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
1265 if (!renameBuffer(b, docstring()))
1274 return saveBuffer(b);
1278 bool GuiView::closeBuffer()
1280 Buffer * buf = buffer();
1281 return buf && closeBuffer(*buf);
1285 bool GuiView::closeBuffer(Buffer & buf)
1287 if (buf.isClean() || buf.paragraphs().empty()) {
1288 theBufferList().release(&buf);
1291 // Switch to this Buffer.
1296 if (buf.isUnnamed())
1297 file = from_utf8(buf.fileName().onlyFileName());
1299 file = buf.fileName().displayName(30);
1301 docstring const text = bformat(_("The document %1$s has unsaved changes."
1302 "\n\nDo you want to save the document or discard the changes?"), file);
1303 int const ret = Alert::prompt(_("Save changed document?"),
1304 text, 0, 2, _("&Save"), _("&Discard"), _("&Cancel"));
1308 if (!saveBuffer(buf))
1312 // if we crash after this we could
1313 // have no autosave file but I guess
1314 // this is really improbable (Jug)
1315 removeAutosaveFile(buf.absFileName());
1321 // save file names to .lyx/session
1322 // if master/slave are both open, do not save slave since it
1323 // will be automatically loaded when the master is loaded
1324 if (buf.masterBuffer() == &buf)
1325 LyX::ref().session().lastOpened().add(buf.fileName());
1327 theBufferList().release(&buf);
1332 bool GuiView::quitWriteAll()
1334 while (!theBufferList().empty()) {
1335 Buffer * b = theBufferList().first();
1336 if (!closeBuffer(*b))
1343 bool GuiView::dispatch(FuncRequest const & cmd)
1345 BufferView * bv = view();
1346 // By default we won't need any update.
1348 bv->cursor().updateFlags(Update::None);
1350 switch(cmd.action) {
1351 case LFUN_BUFFER_SWITCH:
1352 setBuffer(theBufferList().getBuffer(to_utf8(cmd.argument())));
1355 case LFUN_BUFFER_NEXT:
1356 setBuffer(theBufferList().next(buffer()));
1359 case LFUN_BUFFER_PREVIOUS:
1360 setBuffer(theBufferList().previous(buffer()));
1363 case LFUN_COMMAND_EXECUTE: {
1364 bool const show_it = cmd.argument() != "off";
1365 d.toolbars_->showCommandBuffer(show_it);
1368 case LFUN_DROP_LAYOUTS_CHOICE:
1370 d.layout_->showPopup();
1373 case LFUN_MENU_OPEN:
1374 d.menubar_->openByName(toqstr(cmd.argument()));
1377 case LFUN_FILE_INSERT:
1378 insertLyXFile(cmd.argument());
1380 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1381 insertPlaintextFile(cmd.argument(), true);
1384 case LFUN_FILE_INSERT_PLAINTEXT:
1385 insertPlaintextFile(cmd.argument(), false);
1388 case LFUN_BUFFER_WRITE:
1390 saveBuffer(bv->buffer());
1393 case LFUN_BUFFER_WRITE_AS:
1395 renameBuffer(bv->buffer(), cmd.argument());
1398 case LFUN_BUFFER_WRITE_ALL: {
1399 Buffer * first = theBufferList().first();
1402 message(_("Saving all documents..."));
1403 // We cannot use a for loop as the buffer list cycles.
1409 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
1410 b = theBufferList().next(b);
1411 } while (b != first);
1412 message(_("All documents saved."));
1416 case LFUN_TOOLBAR_TOGGLE: {
1417 string const name = cmd.getArg(0);
1418 bool const allowauto = cmd.getArg(1) == "allowauto";
1419 // it is possible to get current toolbar status like this,...
1420 // but I decide to obey the order of ToolbarBackend::flags
1421 // and disregard real toolbar status.
1422 // toolbars_->saveToolbarInfo();
1424 // toggle state on/off/auto
1425 d.toolbars_->toggleToolbarState(name, allowauto);
1429 ToolbarInfo * tbi = d.toolbars_->getToolbarInfo(name);
1431 message(bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name)));
1435 if (tbi->flags & ToolbarInfo::ON)
1437 else if (tbi->flags & ToolbarInfo::OFF)
1439 else if (tbi->flags & ToolbarInfo::AUTO)
1442 message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
1443 _(tbi->gui_name), state));
1447 case LFUN_DIALOG_UPDATE: {
1448 string const name = to_utf8(cmd.argument());
1449 // Can only update a dialog connected to an existing inset
1450 Inset * inset = getOpenInset(name);
1452 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
1453 inset->dispatch(view()->cursor(), fr);
1454 } else if (name == "paragraph") {
1455 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
1456 } else if (name == "prefs") {
1457 updateDialog(name, string());
1462 case LFUN_DIALOG_TOGGLE: {
1463 if (isDialogVisible(cmd.getArg(0)))
1464 dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()));
1466 dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()));
1470 case LFUN_DIALOG_DISCONNECT_INSET:
1471 disconnectDialog(to_utf8(cmd.argument()));
1474 case LFUN_DIALOG_HIDE: {
1477 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
1481 case LFUN_DIALOG_SHOW: {
1482 string const name = cmd.getArg(0);
1483 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
1485 if (name == "character") {
1486 data = freefont2string();
1488 showDialog("character", data);
1489 } else if (name == "latexlog") {
1490 Buffer::LogType type;
1491 string const logfile = buffer()->logName(&type);
1493 case Buffer::latexlog:
1496 case Buffer::buildlog:
1500 data += Lexer::quoteString(logfile);
1501 showDialog("log", data);
1502 } else if (name == "vclog") {
1503 string const data = "vc " +
1504 Lexer::quoteString(buffer()->lyxvc().getLogFile());
1505 showDialog("log", data);
1507 showDialog(name, data);
1511 case LFUN_INSET_APPLY: {
1512 string const name = cmd.getArg(0);
1513 Inset * inset = getOpenInset(name);
1515 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1516 inset->dispatch(view()->cursor(), fr);
1518 FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1532 Buffer const * GuiView::updateInset(Inset const * inset)
1534 if (!d.current_work_area_)
1538 d.current_work_area_->scheduleRedraw();
1540 return &d.current_work_area_->bufferView().buffer();
1544 void GuiView::restartCursor()
1546 /* When we move around, or type, it's nice to be able to see
1547 * the cursor immediately after the keypress.
1549 if (d.current_work_area_)
1550 d.current_work_area_->startBlinkingCursor();
1552 // Take this occasion to update the toobars and layout list.
1559 // This list should be kept in sync with the list of insets in
1560 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
1561 // dialog should have the same name as the inset.
1563 char const * const dialognames[] = {
1564 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
1565 "citation", "document", "embedding", "errorlist", "ert", "external", "file",
1566 "findreplace", "float", "graphics", "include", "index", "nomenclature", "label", "log",
1567 "mathdelimiter", "mathmatrix", "note", "paragraph",
1568 "prefs", "print", "ref", "sendto", "spellchecker","tabular", "tabularcreate",
1570 #ifdef HAVE_LIBAIKSAURUS
1574 "texinfo", "toc", "href", "view-source", "vspace", "wrap", "listings" };
1576 char const * const * const end_dialognames =
1577 dialognames + (sizeof(dialognames) / sizeof(char *));
1581 cmpCStr(char const * name) : name_(name) {}
1582 bool operator()(char const * other) {
1583 return strcmp(other, name_) == 0;
1590 bool isValidName(string const & name)
1592 return std::find_if(dialognames, end_dialognames,
1593 cmpCStr(name.c_str())) != end_dialognames;
1599 void GuiView::resetDialogs()
1601 // Make sure that no LFUN uses any LyXView.
1602 theLyXFunc().setLyXView(0);
1603 d.toolbars_->init();
1606 d.layout_->updateContents(true);
1607 // Now update controls with current buffer.
1608 theLyXFunc().setLyXView(this);
1613 Dialog * GuiView::find_or_build(string const & name)
1615 if (!isValidName(name))
1618 std::map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
1620 if (it != d.dialogs_.end())
1621 return it->second.get();
1623 Dialog * dialog = build(name);
1624 d.dialogs_[name].reset(dialog);
1625 if (lyxrc.allow_geometry_session)
1626 dialog->restoreSession();
1631 void GuiView::showDialog(string const & name, string const & data,
1638 Dialog * dialog = find_or_build(name);
1640 dialog->showData(data);
1642 d.open_insets_[name] = inset;
1648 bool GuiView::isDialogVisible(string const & name) const
1650 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1651 if (it == d.dialogs_.end())
1653 return it->second.get()->isVisibleView();
1657 void GuiView::hideDialog(string const & name, Inset * inset)
1659 // Don't send the signal if we are quitting, because on MSVC it is
1660 // destructed before the cut stack in CutAndPaste.cpp, and this method
1661 // is called from some inset destructor if the cut stack is not empty
1666 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1667 if (it == d.dialogs_.end())
1670 if (inset && inset != getOpenInset(name))
1673 Dialog * const dialog = it->second.get();
1674 if (dialog->isVisibleView())
1676 d.open_insets_[name] = 0;
1680 void GuiView::disconnectDialog(string const & name)
1682 if (!isValidName(name))
1685 if (d.open_insets_.find(name) != d.open_insets_.end())
1686 d.open_insets_[name] = 0;
1690 Inset * GuiView::getOpenInset(string const & name) const
1692 if (!isValidName(name))
1695 std::map<string, Inset *>::const_iterator it = d.open_insets_.find(name);
1696 return it == d.open_insets_.end() ? 0 : it->second;
1700 void GuiView::hideAll() const
1702 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1703 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1705 for(; it != end; ++it)
1706 it->second->hideView();
1710 void GuiView::hideBufferDependent() const
1712 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1713 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1715 for(; it != end; ++it) {
1716 Dialog * dialog = it->second.get();
1717 if (dialog->isBufferDependent())
1723 void GuiView::updateBufferDependent(bool switched) const
1725 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1726 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1728 for(; it != end; ++it) {
1729 Dialog * dialog = it->second.get();
1730 if (!dialog->isVisibleView())
1732 if (switched && dialog->isBufferDependent()) {
1733 if (dialog->initialiseParams(""))
1734 dialog->updateView();
1738 // A bit clunky, but the dialog will request
1739 // that the kernel provides it with the necessary
1741 dialog->updateDialog();
1747 void GuiView::checkStatus()
1749 std::map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
1750 std::map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
1752 for(; it != end; ++it) {
1753 Dialog * const dialog = it->second.get();
1754 if (dialog && dialog->isVisibleView())
1755 dialog->checkStatus();
1761 // will be replaced by a proper factory...
1762 Dialog * createGuiAbout(GuiView & lv);
1763 Dialog * createGuiBibitem(GuiView & lv);
1764 Dialog * createGuiBibtex(GuiView & lv);
1765 Dialog * createGuiBox(GuiView & lv);
1766 Dialog * createGuiBranch(GuiView & lv);
1767 Dialog * createGuiChanges(GuiView & lv);
1768 Dialog * createGuiCharacter(GuiView & lv);
1769 Dialog * createGuiCitation(GuiView & lv);
1770 Dialog * createGuiDelimiter(GuiView & lv);
1771 Dialog * createGuiDocument(GuiView & lv);
1772 Dialog * createGuiErrorList(GuiView & lv);
1773 Dialog * createGuiERT(GuiView & lv);
1774 Dialog * createGuiExternal(GuiView & lv);
1775 Dialog * createGuiFloat(GuiView & lv);
1776 Dialog * createGuiGraphics(GuiView & lv);
1777 Dialog * createGuiInclude(GuiView & lv);
1778 Dialog * createGuiIndex(GuiView & lv);
1779 Dialog * createGuiLabel(GuiView & lv);
1780 Dialog * createGuiListings(GuiView & lv);
1781 Dialog * createGuiLog(GuiView & lv);
1782 Dialog * createGuiMathMatrix(GuiView & lv);
1783 Dialog * createGuiNomenclature(GuiView & lv);
1784 Dialog * createGuiNote(GuiView & lv);
1785 Dialog * createGuiParagraph(GuiView & lv);
1786 Dialog * createGuiPreferences(GuiView & lv);
1787 Dialog * createGuiPrint(GuiView & lv);
1788 Dialog * createGuiRef(GuiView & lv);
1789 Dialog * createGuiSearch(GuiView & lv);
1790 Dialog * createGuiSendTo(GuiView & lv);
1791 Dialog * createGuiShowFile(GuiView & lv);
1792 Dialog * createGuiSpellchecker(GuiView & lv);
1793 Dialog * createGuiTabularCreate(GuiView & lv);
1794 Dialog * createGuiTabular(GuiView & lv);
1795 Dialog * createGuiTexInfo(GuiView & lv);
1796 Dialog * createGuiToc(GuiView & lv);
1797 Dialog * createGuiThesaurus(GuiView & lv);
1798 Dialog * createGuiHyperlink(GuiView & lv);
1799 Dialog * createGuiVSpace(GuiView & lv);
1800 Dialog * createGuiViewSource(GuiView & lv);
1801 Dialog * createGuiWrap(GuiView & lv);
1804 Dialog * GuiView::build(string const & name)
1806 BOOST_ASSERT(isValidName(name));
1808 if (name == "aboutlyx")
1809 return createGuiAbout(*this);
1810 if (name == "bibitem")
1811 return createGuiBibitem(*this);
1812 if (name == "bibtex")
1813 return createGuiBibtex(*this);
1815 return createGuiBox(*this);
1816 if (name == "branch")
1817 return createGuiBranch(*this);
1818 if (name == "changes")
1819 return createGuiChanges(*this);
1820 if (name == "character")
1821 return createGuiCharacter(*this);
1822 if (name == "citation")
1823 return createGuiCitation(*this);
1824 if (name == "document")
1825 return createGuiDocument(*this);
1826 if (name == "errorlist")
1827 return createGuiErrorList(*this);
1829 return createGuiERT(*this);
1830 if (name == "external")
1831 return createGuiExternal(*this);
1833 return createGuiShowFile(*this);
1834 if (name == "findreplace")
1835 return createGuiSearch(*this);
1836 if (name == "float")
1837 return createGuiFloat(*this);
1838 if (name == "graphics")
1839 return createGuiGraphics(*this);
1840 if (name == "include")
1841 return createGuiInclude(*this);
1842 if (name == "index")
1843 return createGuiIndex(*this);
1844 if (name == "nomenclature")
1845 return createGuiNomenclature(*this);
1846 if (name == "label")
1847 return createGuiLabel(*this);
1849 return createGuiLog(*this);
1850 if (name == "view-source")
1851 return createGuiViewSource(*this);
1852 if (name == "mathdelimiter")
1853 return createGuiDelimiter(*this);
1854 if (name == "mathmatrix")
1855 return createGuiMathMatrix(*this);
1857 return createGuiNote(*this);
1858 if (name == "paragraph")
1859 return createGuiParagraph(*this);
1860 if (name == "prefs")
1861 return createGuiPreferences(*this);
1862 if (name == "print")
1863 return createGuiPrint(*this);
1865 return createGuiRef(*this);
1866 if (name == "sendto")
1867 return createGuiSendTo(*this);
1868 if (name == "spellchecker")
1869 return createGuiSpellchecker(*this);
1870 if (name == "tabular")
1871 return createGuiTabular(*this);
1872 if (name == "tabularcreate")
1873 return createGuiTabularCreate(*this);
1874 if (name == "texinfo")
1875 return createGuiTexInfo(*this);
1876 #ifdef HAVE_LIBAIKSAURUS
1877 if (name == "thesaurus")
1878 return createGuiThesaurus(*this);
1881 return createGuiToc(*this);
1883 return createGuiHyperlink(*this);
1884 if (name == "vspace")
1885 return createGuiVSpace(*this);
1887 return createGuiWrap(*this);
1888 if (name == "listings")
1889 return createGuiListings(*this);
1895 } // namespace frontend
1898 #include "GuiView_moc.cpp"