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.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
56 #include "LyXAction.h"
60 #include "Paragraph.h"
61 #include "SpellChecker.h"
64 #include "TextClass.h"
69 #include "support/convert.h"
70 #include "support/debug.h"
71 #include "support/ExceptionMessage.h"
72 #include "support/FileName.h"
73 #include "support/filetools.h"
74 #include "support/gettext.h"
75 #include "support/filetools.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
90 #include <QDesktopWidget>
91 #include <QDragEnterEvent>
94 #include <QFutureWatcher>
103 #include <QPixmapCache>
105 #include <QPushButton>
106 #include <QScrollBar>
108 #include <QShowEvent>
110 #include <QStackedWidget>
111 #include <QStatusBar>
112 #include <QSvgRenderer>
113 #include <QtConcurrentRun>
121 // sync with GuiAlert.cpp
122 #define EXPORT_in_THREAD 1
125 #include "support/bind.h"
129 #ifdef HAVE_SYS_TIME_H
130 # include <sys/time.h>
138 using namespace lyx::support;
142 using support::addExtension;
143 using support::changeExtension;
144 using support::removeExtension;
150 class BackgroundWidget : public QWidget
153 BackgroundWidget(int width, int height)
154 : width_(width), height_(height)
156 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
157 if (!lyxrc.show_banner)
159 /// The text to be written on top of the pixmap
160 QString const text = lyx_version ?
161 qt_("version ") + lyx_version : qt_("unknown version");
162 #if QT_VERSION >= 0x050000
163 QString imagedir = "images/";
164 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
165 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
166 if (svgRenderer.isValid()) {
167 splash_ = QPixmap(splashSize());
168 QPainter painter(&splash_);
169 svgRenderer.render(&painter);
170 splash_.setDevicePixelRatio(pixelRatio());
172 splash_ = getPixmap("images/", "banner", "png");
175 splash_ = getPixmap("images/", "banner", "svgz,png");
178 QPainter pain(&splash_);
179 pain.setPen(QColor(0, 0, 0));
180 qreal const fsize = fontSize();
181 QPointF const position = textPosition();
183 "widget pixel ratio: " << pixelRatio() <<
184 " splash pixel ratio: " << splashPixelRatio() <<
185 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
187 // The font used to display the version info
188 font.setStyleHint(QFont::SansSerif);
189 font.setWeight(QFont::Bold);
190 font.setPointSizeF(fsize);
192 pain.drawText(position, text);
193 setFocusPolicy(Qt::StrongFocus);
196 void paintEvent(QPaintEvent *)
198 int const w = width_;
199 int const h = height_;
200 int const x = (width() - w) / 2;
201 int const y = (height() - h) / 2;
203 "widget pixel ratio: " << pixelRatio() <<
204 " splash pixel ratio: " << splashPixelRatio() <<
205 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
207 pain.drawPixmap(x, y, w, h, splash_);
210 void keyPressEvent(QKeyEvent * ev)
213 setKeySymbol(&sym, ev);
215 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
227 /// Current ratio between physical pixels and device-independent pixels
228 double pixelRatio() const {
229 #if QT_VERSION >= 0x050000
230 return qt_scale_factor * devicePixelRatio();
236 qreal fontSize() const {
237 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
240 QPointF textPosition() const {
241 return QPointF(width_/2 - 18, height_/2 + 45);
244 QSize splashSize() const {
246 static_cast<unsigned int>(width_ * pixelRatio()),
247 static_cast<unsigned int>(height_ * pixelRatio()));
250 /// Ratio between physical pixels and device-independent pixels of splash image
251 double splashPixelRatio() const {
252 #if QT_VERSION >= 0x050000
253 return splash_.devicePixelRatio();
261 /// Toolbar store providing access to individual toolbars by name.
262 typedef map<string, GuiToolbar *> ToolbarMap;
264 typedef shared_ptr<Dialog> DialogPtr;
269 class GuiView::GuiViewPrivate
272 GuiViewPrivate(GuiViewPrivate const &);
273 void operator=(GuiViewPrivate const &);
275 GuiViewPrivate(GuiView * gv)
276 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
277 layout_(0), autosave_timeout_(5000),
280 // hardcode here the platform specific icon size
281 smallIconSize = 16; // scaling problems
282 normalIconSize = 20; // ok, default if iconsize.png is missing
283 bigIconSize = 26; // better for some math icons
284 hugeIconSize = 32; // better for hires displays
287 // if it exists, use width of iconsize.png as normal size
288 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
289 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
291 QImage image(toqstr(fn.absFileName()));
292 if (image.width() < int(smallIconSize))
293 normalIconSize = smallIconSize;
294 else if (image.width() > int(giantIconSize))
295 normalIconSize = giantIconSize;
297 normalIconSize = image.width();
300 splitter_ = new QSplitter;
301 bg_widget_ = new BackgroundWidget(400, 250);
302 stack_widget_ = new QStackedWidget;
303 stack_widget_->addWidget(bg_widget_);
304 stack_widget_->addWidget(splitter_);
307 // TODO cleanup, remove the singleton, handle multiple Windows?
308 progress_ = ProgressInterface::instance();
309 if (!dynamic_cast<GuiProgress*>(progress_)) {
310 progress_ = new GuiProgress; // TODO who deletes it
311 ProgressInterface::setInstance(progress_);
314 dynamic_cast<GuiProgress*>(progress_),
315 SIGNAL(updateStatusBarMessage(QString const&)),
316 gv, SLOT(updateStatusBarMessage(QString const&)));
318 dynamic_cast<GuiProgress*>(progress_),
319 SIGNAL(clearMessageText()),
320 gv, SLOT(clearMessageText()));
327 delete stack_widget_;
332 stack_widget_->setCurrentWidget(bg_widget_);
333 bg_widget_->setUpdatesEnabled(true);
334 bg_widget_->setFocus();
337 int tabWorkAreaCount()
339 return splitter_->count();
342 TabWorkArea * tabWorkArea(int i)
344 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
347 TabWorkArea * currentTabWorkArea()
349 int areas = tabWorkAreaCount();
351 // The first TabWorkArea is always the first one, if any.
352 return tabWorkArea(0);
354 for (int i = 0; i != areas; ++i) {
355 TabWorkArea * twa = tabWorkArea(i);
356 if (current_main_work_area_ == twa->currentWorkArea())
360 // None has the focus so we just take the first one.
361 return tabWorkArea(0);
364 int countWorkAreasOf(Buffer & buf)
366 int areas = tabWorkAreaCount();
368 for (int i = 0; i != areas; ++i) {
369 TabWorkArea * twa = tabWorkArea(i);
370 if (twa->workArea(buf))
376 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
378 if (processing_thread_watcher_.isRunning()) {
379 // we prefer to cancel this preview in order to keep a snappy
383 processing_thread_watcher_.setFuture(f);
386 QSize iconSize(docstring const & icon_size)
389 if (icon_size == "small")
390 size = smallIconSize;
391 else if (icon_size == "normal")
392 size = normalIconSize;
393 else if (icon_size == "big")
395 else if (icon_size == "huge")
397 else if (icon_size == "giant")
398 size = giantIconSize;
400 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
402 if (size < smallIconSize)
403 size = smallIconSize;
405 return QSize(size, size);
408 QSize iconSize(QString const & icon_size)
410 return iconSize(qstring_to_ucs4(icon_size));
413 string & iconSize(QSize const & qsize)
415 LATTEST(qsize.width() == qsize.height());
417 static string icon_size;
419 unsigned int size = qsize.width();
421 if (size < smallIconSize)
422 size = smallIconSize;
424 if (size == smallIconSize)
426 else if (size == normalIconSize)
427 icon_size = "normal";
428 else if (size == bigIconSize)
430 else if (size == hugeIconSize)
432 else if (size == giantIconSize)
435 icon_size = convert<string>(size);
442 GuiWorkArea * current_work_area_;
443 GuiWorkArea * current_main_work_area_;
444 QSplitter * splitter_;
445 QStackedWidget * stack_widget_;
446 BackgroundWidget * bg_widget_;
448 ToolbarMap toolbars_;
449 ProgressInterface* progress_;
450 /// The main layout box.
452 * \warning Don't Delete! The layout box is actually owned by
453 * whichever toolbar contains it. All the GuiView class needs is a
454 * means of accessing it.
456 * FIXME: replace that with a proper model so that we are not limited
457 * to only one dialog.
462 map<string, DialogPtr> dialogs_;
464 unsigned int smallIconSize;
465 unsigned int normalIconSize;
466 unsigned int bigIconSize;
467 unsigned int hugeIconSize;
468 unsigned int giantIconSize;
470 QTimer statusbar_timer_;
471 /// auto-saving of buffers
472 Timeout autosave_timeout_;
473 /// flag against a race condition due to multiclicks, see bug #1119
477 TocModels toc_models_;
480 QFutureWatcher<docstring> autosave_watcher_;
481 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
483 string last_export_format;
484 string processing_format;
486 static QSet<Buffer const *> busyBuffers;
487 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
488 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
489 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
490 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
493 static Buffer::ExportStatus runAndDestroy(const T& func, Buffer const * orig, Buffer * buffer, string const & format);
495 // TODO syncFunc/previewFunc: use bind
496 bool asyncBufferProcessing(string const & argument,
497 Buffer const * used_buffer,
498 docstring const & msg,
499 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
500 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
501 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const);
503 QVector<GuiWorkArea*> guiWorkAreas();
506 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
509 GuiView::GuiView(int id)
510 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
511 command_execute_(false), minibuffer_focus_(false), devel_mode_(false)
513 connect(this, SIGNAL(bufferViewChanged()),
514 this, SLOT(onBufferViewChanged()));
516 // GuiToolbars *must* be initialised before the menu bar.
517 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
520 // set ourself as the current view. This is needed for the menu bar
521 // filling, at least for the static special menu item on Mac. Otherwise
522 // they are greyed out.
523 guiApp->setCurrentView(this);
525 // Fill up the menu bar.
526 guiApp->menus().fillMenuBar(menuBar(), this, true);
528 setCentralWidget(d.stack_widget_);
530 // Start autosave timer
531 if (lyxrc.autosave) {
532 // The connection is closed when this is destroyed.
533 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
534 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
535 d.autosave_timeout_.start();
537 connect(&d.statusbar_timer_, SIGNAL(timeout()),
538 this, SLOT(clearMessage()));
540 // We don't want to keep the window in memory if it is closed.
541 setAttribute(Qt::WA_DeleteOnClose, true);
543 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
544 // QIcon::fromTheme was introduced in Qt 4.6
545 #if (QT_VERSION >= 0x040600)
546 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
547 // since the icon is provided in the application bundle. We use a themed
548 // version when available and use the bundled one as fallback.
549 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
551 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
557 // use tabbed dock area for multiple docks
558 // (such as "source" and "messages")
559 setDockOptions(QMainWindow::ForceTabbedDocks);
562 setAcceptDrops(true);
564 // add busy indicator to statusbar
565 QLabel * busylabel = new QLabel(statusBar());
566 statusBar()->addPermanentWidget(busylabel);
567 search_mode mode = theGuiApp()->imageSearchMode();
568 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
569 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
570 busylabel->setMovie(busyanim);
574 connect(&d.processing_thread_watcher_, SIGNAL(started()),
575 busylabel, SLOT(show()));
576 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
577 busylabel, SLOT(hide()));
579 QFontMetrics const fm(statusBar()->fontMetrics());
580 int const iconheight = max(int(d.normalIconSize), fm.height());
581 QSize const iconsize(iconheight, iconheight);
583 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
584 shell_escape_ = new QLabel(statusBar());
585 shell_escape_->setPixmap(shellescape);
586 shell_escape_->setScaledContents(true);
587 shell_escape_->setAlignment(Qt::AlignCenter);
588 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
589 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
590 "external commands for this document. "
591 "Right click to change."));
592 SEMenu * menu = new SEMenu(this);
593 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
594 menu, SLOT(showMenu(QPoint)));
595 shell_escape_->hide();
596 statusBar()->addPermanentWidget(shell_escape_);
598 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
599 read_only_ = new QLabel(statusBar());
600 read_only_->setPixmap(readonly);
601 read_only_->setScaledContents(true);
602 read_only_->setAlignment(Qt::AlignCenter);
604 statusBar()->addPermanentWidget(read_only_);
606 version_control_ = new QLabel(statusBar());
607 version_control_->setAlignment(Qt::AlignCenter);
608 version_control_->setFrameStyle(QFrame::StyledPanel);
609 version_control_->hide();
610 statusBar()->addPermanentWidget(version_control_);
612 statusBar()->setSizeGripEnabled(true);
615 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
616 SLOT(autoSaveThreadFinished()));
618 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
619 SLOT(processingThreadStarted()));
620 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
621 SLOT(processingThreadFinished()));
623 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
624 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
626 // set custom application bars context menu, e.g. tool bar and menu bar
627 setContextMenuPolicy(Qt::CustomContextMenu);
628 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
629 SLOT(toolBarPopup(const QPoint &)));
631 // Forbid too small unresizable window because it can happen
632 // with some window manager under X11.
633 setMinimumSize(300, 200);
635 if (lyxrc.allow_geometry_session) {
636 // Now take care of session management.
641 // no session handling, default to a sane size.
642 setGeometry(50, 50, 690, 510);
645 // clear session data if any.
647 settings.remove("views");
657 void GuiView::disableShellEscape()
659 BufferView * bv = documentBufferView();
662 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
663 bv->buffer().params().shell_escape = false;
664 bv->processUpdateFlags(Update::Force);
668 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
670 QVector<GuiWorkArea*> areas;
671 for (int i = 0; i < tabWorkAreaCount(); i++) {
672 TabWorkArea* ta = tabWorkArea(i);
673 for (int u = 0; u < ta->count(); u++) {
674 areas << ta->workArea(u);
680 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
681 string const & format)
683 docstring const fmt = theFormats().prettyName(format);
686 case Buffer::ExportSuccess:
687 msg = bformat(_("Successful export to format: %1$s"), fmt);
689 case Buffer::ExportCancel:
690 msg = _("Document export cancelled.");
692 case Buffer::ExportError:
693 case Buffer::ExportNoPathToFormat:
694 case Buffer::ExportTexPathHasSpaces:
695 case Buffer::ExportConverterError:
696 msg = bformat(_("Error while exporting format: %1$s"), fmt);
698 case Buffer::PreviewSuccess:
699 msg = bformat(_("Successful preview of format: %1$s"), fmt);
701 case Buffer::PreviewError:
702 msg = bformat(_("Error while previewing format: %1$s"), fmt);
709 void GuiView::processingThreadStarted()
714 void GuiView::processingThreadFinished()
716 QFutureWatcher<Buffer::ExportStatus> const * watcher =
717 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
719 Buffer::ExportStatus const status = watcher->result();
720 handleExportStatus(this, status, d.processing_format);
723 BufferView const * const bv = currentBufferView();
724 if (bv && !bv->buffer().errorList("Export").empty()) {
728 errors(d.last_export_format);
732 void GuiView::autoSaveThreadFinished()
734 QFutureWatcher<docstring> const * watcher =
735 static_cast<QFutureWatcher<docstring> const *>(sender());
736 message(watcher->result());
741 void GuiView::saveLayout() const
744 settings.setValue("zoom_ratio", zoom_ratio_);
745 settings.setValue("devel_mode", devel_mode_);
746 settings.beginGroup("views");
747 settings.beginGroup(QString::number(id_));
748 #if defined(Q_WS_X11) || defined(QPA_XCB)
749 settings.setValue("pos", pos());
750 settings.setValue("size", size());
752 settings.setValue("geometry", saveGeometry());
754 settings.setValue("layout", saveState(0));
755 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
759 void GuiView::saveUISettings() const
763 // Save the toolbar private states
764 ToolbarMap::iterator end = d.toolbars_.end();
765 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
766 it->second->saveSession(settings);
767 // Now take care of all other dialogs
768 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
769 for (; it!= d.dialogs_.end(); ++it)
770 it->second->saveSession(settings);
774 bool GuiView::restoreLayout()
777 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
778 // Actual zoom value: default zoom + fractional offset
779 int zoom = lyxrc.defaultZoom * zoom_ratio_;
780 if (zoom < static_cast<int>(zoom_min_))
782 lyxrc.currentZoom = zoom;
783 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
784 settings.beginGroup("views");
785 settings.beginGroup(QString::number(id_));
786 QString const icon_key = "icon_size";
787 if (!settings.contains(icon_key))
790 //code below is skipped when when ~/.config/LyX is (re)created
791 setIconSize(d.iconSize(settings.value(icon_key).toString()));
793 #if defined(Q_WS_X11) || defined(QPA_XCB)
794 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
795 QSize size = settings.value("size", QSize(690, 510)).toSize();
799 // Work-around for bug #6034: the window ends up in an undetermined
800 // state when trying to restore a maximized window when it is
801 // already maximized.
802 if (!(windowState() & Qt::WindowMaximized))
803 if (!restoreGeometry(settings.value("geometry").toByteArray()))
804 setGeometry(50, 50, 690, 510);
806 // Make sure layout is correctly oriented.
807 setLayoutDirection(qApp->layoutDirection());
809 // Allow the toc and view-source dock widget to be restored if needed.
811 if ((dialog = findOrBuild("toc", true)))
812 // see bug 5082. At least setup title and enabled state.
813 // Visibility will be adjusted by restoreState below.
814 dialog->prepareView();
815 if ((dialog = findOrBuild("view-source", true)))
816 dialog->prepareView();
817 if ((dialog = findOrBuild("progress", true)))
818 dialog->prepareView();
820 if (!restoreState(settings.value("layout").toByteArray(), 0))
823 // init the toolbars that have not been restored
824 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
825 Toolbars::Infos::iterator end = guiApp->toolbars().end();
826 for (; cit != end; ++cit) {
827 GuiToolbar * tb = toolbar(cit->name);
828 if (tb && !tb->isRestored())
829 initToolbar(cit->name);
832 // update lock (all) toolbars positions
833 updateLockToolbars();
840 GuiToolbar * GuiView::toolbar(string const & name)
842 ToolbarMap::iterator it = d.toolbars_.find(name);
843 if (it != d.toolbars_.end())
846 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
851 void GuiView::updateLockToolbars()
853 toolbarsMovable_ = false;
854 for (ToolbarInfo const & info : guiApp->toolbars()) {
855 GuiToolbar * tb = toolbar(info.name);
856 if (tb && tb->isMovable())
857 toolbarsMovable_ = true;
862 void GuiView::constructToolbars()
864 ToolbarMap::iterator it = d.toolbars_.begin();
865 for (; it != d.toolbars_.end(); ++it)
869 // I don't like doing this here, but the standard toolbar
870 // destroys this object when it's destroyed itself (vfr)
871 d.layout_ = new LayoutBox(*this);
872 d.stack_widget_->addWidget(d.layout_);
873 d.layout_->move(0,0);
875 // extracts the toolbars from the backend
876 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
877 Toolbars::Infos::iterator end = guiApp->toolbars().end();
878 for (; cit != end; ++cit)
879 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
883 void GuiView::initToolbars()
885 // extracts the toolbars from the backend
886 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
887 Toolbars::Infos::iterator end = guiApp->toolbars().end();
888 for (; cit != end; ++cit)
889 initToolbar(cit->name);
893 void GuiView::initToolbar(string const & name)
895 GuiToolbar * tb = toolbar(name);
898 int const visibility = guiApp->toolbars().defaultVisibility(name);
899 bool newline = !(visibility & Toolbars::SAMEROW);
900 tb->setVisible(false);
901 tb->setVisibility(visibility);
903 if (visibility & Toolbars::TOP) {
905 addToolBarBreak(Qt::TopToolBarArea);
906 addToolBar(Qt::TopToolBarArea, tb);
909 if (visibility & Toolbars::BOTTOM) {
911 addToolBarBreak(Qt::BottomToolBarArea);
912 addToolBar(Qt::BottomToolBarArea, tb);
915 if (visibility & Toolbars::LEFT) {
917 addToolBarBreak(Qt::LeftToolBarArea);
918 addToolBar(Qt::LeftToolBarArea, tb);
921 if (visibility & Toolbars::RIGHT) {
923 addToolBarBreak(Qt::RightToolBarArea);
924 addToolBar(Qt::RightToolBarArea, tb);
927 if (visibility & Toolbars::ON)
928 tb->setVisible(true);
930 tb->setMovable(true);
934 TocModels & GuiView::tocModels()
936 return d.toc_models_;
940 void GuiView::setFocus()
942 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
943 QMainWindow::setFocus();
947 bool GuiView::hasFocus() const
949 if (currentWorkArea())
950 return currentWorkArea()->hasFocus();
951 if (currentMainWorkArea())
952 return currentMainWorkArea()->hasFocus();
953 return d.bg_widget_->hasFocus();
957 void GuiView::focusInEvent(QFocusEvent * e)
959 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
960 QMainWindow::focusInEvent(e);
961 // Make sure guiApp points to the correct view.
962 guiApp->setCurrentView(this);
963 if (currentWorkArea())
964 currentWorkArea()->setFocus();
965 else if (currentMainWorkArea())
966 currentMainWorkArea()->setFocus();
968 d.bg_widget_->setFocus();
972 void GuiView::showEvent(QShowEvent * e)
974 LYXERR(Debug::GUI, "Passed Geometry "
975 << size().height() << "x" << size().width()
976 << "+" << pos().x() << "+" << pos().y());
978 if (d.splitter_->count() == 0)
979 // No work area, switch to the background widget.
983 QMainWindow::showEvent(e);
987 bool GuiView::closeScheduled()
994 bool GuiView::prepareAllBuffersForLogout()
996 Buffer * first = theBufferList().first();
1000 // First, iterate over all buffers and ask the users if unsaved
1001 // changes should be saved.
1002 // We cannot use a for loop as the buffer list cycles.
1005 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1007 b = theBufferList().next(b);
1008 } while (b != first);
1010 // Next, save session state
1011 // When a view/window was closed before without quitting LyX, there
1012 // are already entries in the lastOpened list.
1013 theSession().lastOpened().clear();
1020 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1021 ** is responsibility of the container (e.g., dialog)
1023 void GuiView::closeEvent(QCloseEvent * close_event)
1025 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1027 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1028 Alert::warning(_("Exit LyX"),
1029 _("LyX could not be closed because documents are being processed by LyX."));
1030 close_event->setAccepted(false);
1034 // If the user pressed the x (so we didn't call closeView
1035 // programmatically), we want to clear all existing entries.
1037 theSession().lastOpened().clear();
1042 // it can happen that this event arrives without selecting the view,
1043 // e.g. when clicking the close button on a background window.
1045 if (!closeWorkAreaAll()) {
1047 close_event->ignore();
1051 // Make sure that nothing will use this to be closed View.
1052 guiApp->unregisterView(this);
1054 if (isFullScreen()) {
1055 // Switch off fullscreen before closing.
1060 // Make sure the timer time out will not trigger a statusbar update.
1061 d.statusbar_timer_.stop();
1063 // Saving fullscreen requires additional tweaks in the toolbar code.
1064 // It wouldn't also work under linux natively.
1065 if (lyxrc.allow_geometry_session) {
1070 close_event->accept();
1074 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1076 if (event->mimeData()->hasUrls())
1078 /// \todo Ask lyx-devel is this is enough:
1079 /// if (event->mimeData()->hasFormat("text/plain"))
1080 /// event->acceptProposedAction();
1084 void GuiView::dropEvent(QDropEvent * event)
1086 QList<QUrl> files = event->mimeData()->urls();
1087 if (files.isEmpty())
1090 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1091 for (int i = 0; i != files.size(); ++i) {
1092 string const file = os::internal_path(fromqstr(
1093 files.at(i).toLocalFile()));
1097 string const ext = support::getExtension(file);
1098 vector<const Format *> found_formats;
1100 // Find all formats that have the correct extension.
1101 vector<const Format *> const & import_formats
1102 = theConverters().importableFormats();
1103 vector<const Format *>::const_iterator it = import_formats.begin();
1104 for (; it != import_formats.end(); ++it)
1105 if ((*it)->hasExtension(ext))
1106 found_formats.push_back(*it);
1109 if (found_formats.size() >= 1) {
1110 if (found_formats.size() > 1) {
1111 //FIXME: show a dialog to choose the correct importable format
1112 LYXERR(Debug::FILES,
1113 "Multiple importable formats found, selecting first");
1115 string const arg = found_formats[0]->name() + " " + file;
1116 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1119 //FIXME: do we have to explicitly check whether it's a lyx file?
1120 LYXERR(Debug::FILES,
1121 "No formats found, trying to open it as a lyx file");
1122 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1124 // add the functions to the queue
1125 guiApp->addToFuncRequestQueue(cmd);
1128 // now process the collected functions. We perform the events
1129 // asynchronously. This prevents potential problems in case the
1130 // BufferView is closed within an event.
1131 guiApp->processFuncRequestQueueAsync();
1135 void GuiView::message(docstring const & str)
1137 if (ForkedProcess::iAmAChild())
1140 // call is moved to GUI-thread by GuiProgress
1141 d.progress_->appendMessage(toqstr(str));
1145 void GuiView::clearMessageText()
1147 message(docstring());
1151 void GuiView::updateStatusBarMessage(QString const & str)
1153 statusBar()->showMessage(str);
1154 d.statusbar_timer_.stop();
1155 d.statusbar_timer_.start(3000);
1159 void GuiView::clearMessage()
1161 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1162 // the hasFocus function mostly returns false, even if the focus is on
1163 // a workarea in this view.
1167 d.statusbar_timer_.stop();
1171 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1173 if (wa != d.current_work_area_
1174 || wa->bufferView().buffer().isInternal())
1176 Buffer const & buf = wa->bufferView().buffer();
1177 // Set the windows title
1178 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1179 if (buf.notifiesExternalModification()) {
1180 title = bformat(_("%1$s (modified externally)"), title);
1181 // If the external modification status has changed, then maybe the status of
1182 // buffer-save has changed too.
1186 title += from_ascii(" - LyX");
1188 setWindowTitle(toqstr(title));
1189 // Sets the path for the window: this is used by OSX to
1190 // allow a context click on the title bar showing a menu
1191 // with the path up to the file
1192 setWindowFilePath(toqstr(buf.absFileName()));
1193 // Tell Qt whether the current document is changed
1194 setWindowModified(!buf.isClean());
1196 if (buf.params().shell_escape)
1197 shell_escape_->show();
1199 shell_escape_->hide();
1201 if (buf.hasReadonlyFlag())
1206 if (buf.lyxvc().inUse()) {
1207 version_control_->show();
1208 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1210 version_control_->hide();
1214 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1216 if (d.current_work_area_)
1217 // disconnect the current work area from all slots
1218 QObject::disconnect(d.current_work_area_, 0, this, 0);
1220 disconnectBufferView();
1221 connectBufferView(wa->bufferView());
1222 connectBuffer(wa->bufferView().buffer());
1223 d.current_work_area_ = wa;
1224 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1225 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1226 QObject::connect(wa, SIGNAL(busy(bool)),
1227 this, SLOT(setBusy(bool)));
1228 // connection of a signal to a signal
1229 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1230 this, SIGNAL(bufferViewChanged()));
1231 Q_EMIT updateWindowTitle(wa);
1232 Q_EMIT bufferViewChanged();
1236 void GuiView::onBufferViewChanged()
1239 // Buffer-dependent dialogs must be updated. This is done here because
1240 // some dialogs require buffer()->text.
1245 void GuiView::on_lastWorkAreaRemoved()
1248 // We already are in a close event. Nothing more to do.
1251 if (d.splitter_->count() > 1)
1252 // We have a splitter so don't close anything.
1255 // Reset and updates the dialogs.
1256 Q_EMIT bufferViewChanged();
1261 if (lyxrc.open_buffers_in_tabs)
1262 // Nothing more to do, the window should stay open.
1265 if (guiApp->viewIds().size() > 1) {
1271 // On Mac we also close the last window because the application stay
1272 // resident in memory. On other platforms we don't close the last
1273 // window because this would quit the application.
1279 void GuiView::updateStatusBar()
1281 // let the user see the explicit message
1282 if (d.statusbar_timer_.isActive())
1289 void GuiView::showMessage()
1293 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1294 if (msg.isEmpty()) {
1295 BufferView const * bv = currentBufferView();
1297 msg = toqstr(bv->cursor().currentState(devel_mode_));
1299 msg = qt_("Welcome to LyX!");
1301 statusBar()->showMessage(msg);
1305 bool GuiView::event(QEvent * e)
1309 // Useful debug code:
1310 //case QEvent::ActivationChange:
1311 //case QEvent::WindowDeactivate:
1312 //case QEvent::Paint:
1313 //case QEvent::Enter:
1314 //case QEvent::Leave:
1315 //case QEvent::HoverEnter:
1316 //case QEvent::HoverLeave:
1317 //case QEvent::HoverMove:
1318 //case QEvent::StatusTip:
1319 //case QEvent::DragEnter:
1320 //case QEvent::DragLeave:
1321 //case QEvent::Drop:
1324 case QEvent::WindowActivate: {
1325 GuiView * old_view = guiApp->currentView();
1326 if (this == old_view) {
1328 return QMainWindow::event(e);
1330 if (old_view && old_view->currentBufferView()) {
1331 // save current selection to the selection buffer to allow
1332 // middle-button paste in this window.
1333 cap::saveSelection(old_view->currentBufferView()->cursor());
1335 guiApp->setCurrentView(this);
1336 if (d.current_work_area_)
1337 on_currentWorkAreaChanged(d.current_work_area_);
1341 return QMainWindow::event(e);
1344 case QEvent::ShortcutOverride: {
1346 if (isFullScreen() && menuBar()->isHidden()) {
1347 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1348 // FIXME: we should also try to detect special LyX shortcut such as
1349 // Alt-P and Alt-M. Right now there is a hack in
1350 // GuiWorkArea::processKeySym() that hides again the menubar for
1352 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1354 return QMainWindow::event(e);
1357 return QMainWindow::event(e);
1361 return QMainWindow::event(e);
1365 void GuiView::resetWindowTitle()
1367 setWindowTitle(qt_("LyX"));
1370 bool GuiView::focusNextPrevChild(bool /*next*/)
1377 bool GuiView::busy() const
1383 void GuiView::setBusy(bool busy)
1385 bool const busy_before = busy_ > 0;
1386 busy ? ++busy_ : --busy_;
1387 if ((busy_ > 0) == busy_before)
1388 // busy state didn't change
1392 QApplication::setOverrideCursor(Qt::WaitCursor);
1395 QApplication::restoreOverrideCursor();
1400 void GuiView::resetCommandExecute()
1402 command_execute_ = false;
1407 double GuiView::pixelRatio() const
1409 #if QT_VERSION >= 0x050000
1410 return qt_scale_factor * devicePixelRatio();
1417 GuiWorkArea * GuiView::workArea(int index)
1419 if (TabWorkArea * twa = d.currentTabWorkArea())
1420 if (index < twa->count())
1421 return twa->workArea(index);
1426 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1428 if (currentWorkArea()
1429 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1430 return currentWorkArea();
1431 if (TabWorkArea * twa = d.currentTabWorkArea())
1432 return twa->workArea(buffer);
1437 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1439 // Automatically create a TabWorkArea if there are none yet.
1440 TabWorkArea * tab_widget = d.splitter_->count()
1441 ? d.currentTabWorkArea() : addTabWorkArea();
1442 return tab_widget->addWorkArea(buffer, *this);
1446 TabWorkArea * GuiView::addTabWorkArea()
1448 TabWorkArea * twa = new TabWorkArea;
1449 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1450 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1451 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1452 this, SLOT(on_lastWorkAreaRemoved()));
1454 d.splitter_->addWidget(twa);
1455 d.stack_widget_->setCurrentWidget(d.splitter_);
1460 GuiWorkArea const * GuiView::currentWorkArea() const
1462 return d.current_work_area_;
1466 GuiWorkArea * GuiView::currentWorkArea()
1468 return d.current_work_area_;
1472 GuiWorkArea const * GuiView::currentMainWorkArea() const
1474 if (!d.currentTabWorkArea())
1476 return d.currentTabWorkArea()->currentWorkArea();
1480 GuiWorkArea * GuiView::currentMainWorkArea()
1482 if (!d.currentTabWorkArea())
1484 return d.currentTabWorkArea()->currentWorkArea();
1488 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1490 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1492 d.current_work_area_ = 0;
1494 Q_EMIT bufferViewChanged();
1498 // FIXME: I've no clue why this is here and why it accesses
1499 // theGuiApp()->currentView, which might be 0 (bug 6464).
1500 // See also 27525 (vfr).
1501 if (theGuiApp()->currentView() == this
1502 && theGuiApp()->currentView()->currentWorkArea() == wa)
1505 if (currentBufferView())
1506 cap::saveSelection(currentBufferView()->cursor());
1508 theGuiApp()->setCurrentView(this);
1509 d.current_work_area_ = wa;
1511 // We need to reset this now, because it will need to be
1512 // right if the tabWorkArea gets reset in the for loop. We
1513 // will change it back if we aren't in that case.
1514 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1515 d.current_main_work_area_ = wa;
1517 for (int i = 0; i != d.splitter_->count(); ++i) {
1518 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1519 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1520 << ", Current main wa: " << currentMainWorkArea());
1525 d.current_main_work_area_ = old_cmwa;
1527 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1528 on_currentWorkAreaChanged(wa);
1529 BufferView & bv = wa->bufferView();
1530 bv.cursor().fixIfBroken();
1532 wa->setUpdatesEnabled(true);
1533 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1537 void GuiView::removeWorkArea(GuiWorkArea * wa)
1539 LASSERT(wa, return);
1540 if (wa == d.current_work_area_) {
1542 disconnectBufferView();
1543 d.current_work_area_ = 0;
1544 d.current_main_work_area_ = 0;
1547 bool found_twa = false;
1548 for (int i = 0; i != d.splitter_->count(); ++i) {
1549 TabWorkArea * twa = d.tabWorkArea(i);
1550 if (twa->removeWorkArea(wa)) {
1551 // Found in this tab group, and deleted the GuiWorkArea.
1553 if (twa->count() != 0) {
1554 if (d.current_work_area_ == 0)
1555 // This means that we are closing the current GuiWorkArea, so
1556 // switch to the next GuiWorkArea in the found TabWorkArea.
1557 setCurrentWorkArea(twa->currentWorkArea());
1559 // No more WorkAreas in this tab group, so delete it.
1566 // It is not a tabbed work area (i.e., the search work area), so it
1567 // should be deleted by other means.
1568 LASSERT(found_twa, return);
1570 if (d.current_work_area_ == 0) {
1571 if (d.splitter_->count() != 0) {
1572 TabWorkArea * twa = d.currentTabWorkArea();
1573 setCurrentWorkArea(twa->currentWorkArea());
1575 // No more work areas, switch to the background widget.
1576 setCurrentWorkArea(0);
1582 LayoutBox * GuiView::getLayoutDialog() const
1588 void GuiView::updateLayoutList()
1591 d.layout_->updateContents(false);
1595 void GuiView::updateToolbars()
1597 ToolbarMap::iterator end = d.toolbars_.end();
1598 if (d.current_work_area_) {
1600 if (d.current_work_area_->bufferView().cursor().inMathed()
1601 && !d.current_work_area_->bufferView().cursor().inRegexped())
1602 context |= Toolbars::MATH;
1603 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1604 context |= Toolbars::TABLE;
1605 if (currentBufferView()->buffer().areChangesPresent()
1606 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1607 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1608 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1609 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1610 context |= Toolbars::REVIEW;
1611 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1612 context |= Toolbars::MATHMACROTEMPLATE;
1613 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1614 context |= Toolbars::IPA;
1615 if (command_execute_)
1616 context |= Toolbars::MINIBUFFER;
1617 if (minibuffer_focus_) {
1618 context |= Toolbars::MINIBUFFER_FOCUS;
1619 minibuffer_focus_ = false;
1622 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1623 it->second->update(context);
1625 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1626 it->second->update();
1630 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1632 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1633 LASSERT(newBuffer, return);
1635 GuiWorkArea * wa = workArea(*newBuffer);
1638 newBuffer->masterBuffer()->updateBuffer();
1640 wa = addWorkArea(*newBuffer);
1641 // scroll to the position when the BufferView was last closed
1642 if (lyxrc.use_lastfilepos) {
1643 LastFilePosSection::FilePos filepos =
1644 theSession().lastFilePos().load(newBuffer->fileName());
1645 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1648 //Disconnect the old buffer...there's no new one.
1651 connectBuffer(*newBuffer);
1652 connectBufferView(wa->bufferView());
1654 setCurrentWorkArea(wa);
1658 void GuiView::connectBuffer(Buffer & buf)
1660 buf.setGuiDelegate(this);
1664 void GuiView::disconnectBuffer()
1666 if (d.current_work_area_)
1667 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1671 void GuiView::connectBufferView(BufferView & bv)
1673 bv.setGuiDelegate(this);
1677 void GuiView::disconnectBufferView()
1679 if (d.current_work_area_)
1680 d.current_work_area_->bufferView().setGuiDelegate(0);
1684 void GuiView::errors(string const & error_type, bool from_master)
1686 BufferView const * const bv = currentBufferView();
1690 #if EXPORT_in_THREAD
1691 // We are called with from_master == false by default, so we
1692 // have to figure out whether that is the case or not.
1693 ErrorList & el = bv->buffer().errorList(error_type);
1695 el = bv->buffer().masterBuffer()->errorList(error_type);
1699 ErrorList const & el = from_master ?
1700 bv->buffer().masterBuffer()->errorList(error_type) :
1701 bv->buffer().errorList(error_type);
1707 string data = error_type;
1709 data = "from_master|" + error_type;
1710 showDialog("errorlist", data);
1714 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1716 d.toc_models_.updateItem(toqstr(type), dit);
1720 void GuiView::structureChanged()
1722 // This is called from the Buffer, which has no way to ensure that cursors
1723 // in BufferView remain valid.
1724 if (documentBufferView())
1725 documentBufferView()->cursor().sanitize();
1726 // FIXME: This is slightly expensive, though less than the tocBackend update
1727 // (#9880). This also resets the view in the Toc Widget (#6675).
1728 d.toc_models_.reset(documentBufferView());
1729 // Navigator needs more than a simple update in this case. It needs to be
1731 updateDialog("toc", "");
1735 void GuiView::updateDialog(string const & name, string const & data)
1737 if (!isDialogVisible(name))
1740 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1741 if (it == d.dialogs_.end())
1744 Dialog * const dialog = it->second.get();
1745 if (dialog->isVisibleView())
1746 dialog->initialiseParams(data);
1750 BufferView * GuiView::documentBufferView()
1752 return currentMainWorkArea()
1753 ? ¤tMainWorkArea()->bufferView()
1758 BufferView const * GuiView::documentBufferView() const
1760 return currentMainWorkArea()
1761 ? ¤tMainWorkArea()->bufferView()
1766 BufferView * GuiView::currentBufferView()
1768 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1772 BufferView const * GuiView::currentBufferView() const
1774 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1778 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1779 Buffer const * orig, Buffer * clone)
1781 bool const success = clone->autoSave();
1783 busyBuffers.remove(orig);
1785 ? _("Automatic save done.")
1786 : _("Automatic save failed!");
1790 void GuiView::autoSave()
1792 LYXERR(Debug::INFO, "Running autoSave()");
1794 Buffer * buffer = documentBufferView()
1795 ? &documentBufferView()->buffer() : 0;
1797 resetAutosaveTimers();
1801 GuiViewPrivate::busyBuffers.insert(buffer);
1802 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1803 buffer, buffer->cloneBufferOnly());
1804 d.autosave_watcher_.setFuture(f);
1805 resetAutosaveTimers();
1809 void GuiView::resetAutosaveTimers()
1812 d.autosave_timeout_.restart();
1816 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1819 Buffer * buf = currentBufferView()
1820 ? ¤tBufferView()->buffer() : 0;
1821 Buffer * doc_buffer = documentBufferView()
1822 ? &(documentBufferView()->buffer()) : 0;
1825 /* In LyX/Mac, when a dialog is open, the menus of the
1826 application can still be accessed without giving focus to
1827 the main window. In this case, we want to disable the menu
1828 entries that are buffer-related.
1829 This code must not be used on Linux and Windows, since it
1830 would disable buffer-related entries when hovering over the
1831 menu (see bug #9574).
1833 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1839 // Check whether we need a buffer
1840 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1841 // no, exit directly
1842 flag.message(from_utf8(N_("Command not allowed with"
1843 "out any document open")));
1844 flag.setEnabled(false);
1848 if (cmd.origin() == FuncRequest::TOC) {
1849 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1850 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1851 flag.setEnabled(false);
1855 switch(cmd.action()) {
1856 case LFUN_BUFFER_IMPORT:
1859 case LFUN_MASTER_BUFFER_UPDATE:
1860 case LFUN_MASTER_BUFFER_VIEW:
1862 && (doc_buffer->parent() != 0
1863 || doc_buffer->hasChildren())
1864 && !d.processing_thread_watcher_.isRunning();
1867 case LFUN_BUFFER_UPDATE:
1868 case LFUN_BUFFER_VIEW: {
1869 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1873 string format = to_utf8(cmd.argument());
1874 if (cmd.argument().empty())
1875 format = doc_buffer->params().getDefaultOutputFormat();
1876 enable = doc_buffer->params().isExportable(format, true);
1880 case LFUN_BUFFER_RELOAD:
1881 enable = doc_buffer && !doc_buffer->isUnnamed()
1882 && doc_buffer->fileName().exists()
1883 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1886 case LFUN_BUFFER_CHILD_OPEN:
1887 enable = doc_buffer != 0;
1890 case LFUN_BUFFER_WRITE:
1891 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1894 //FIXME: This LFUN should be moved to GuiApplication.
1895 case LFUN_BUFFER_WRITE_ALL: {
1896 // We enable the command only if there are some modified buffers
1897 Buffer * first = theBufferList().first();
1902 // We cannot use a for loop as the buffer list is a cycle.
1904 if (!b->isClean()) {
1908 b = theBufferList().next(b);
1909 } while (b != first);
1913 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1914 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1917 case LFUN_BUFFER_WRITE_AS:
1918 case LFUN_BUFFER_EXPORT_AS:
1919 enable = doc_buffer != 0;
1922 case LFUN_BUFFER_CLOSE:
1923 case LFUN_VIEW_CLOSE:
1924 enable = doc_buffer != 0;
1927 case LFUN_BUFFER_CLOSE_ALL:
1928 enable = theBufferList().last() != theBufferList().first();
1931 case LFUN_VIEW_SPLIT:
1932 if (cmd.getArg(0) == "vertical")
1933 enable = doc_buffer && (d.splitter_->count() == 1 ||
1934 d.splitter_->orientation() == Qt::Vertical);
1936 enable = doc_buffer && (d.splitter_->count() == 1 ||
1937 d.splitter_->orientation() == Qt::Horizontal);
1940 case LFUN_TAB_GROUP_CLOSE:
1941 enable = d.tabWorkAreaCount() > 1;
1944 case LFUN_DEVEL_MODE_TOGGLE:
1945 flag.setOnOff(devel_mode_);
1948 case LFUN_TOOLBAR_TOGGLE: {
1949 string const name = cmd.getArg(0);
1950 if (GuiToolbar * t = toolbar(name))
1951 flag.setOnOff(t->isVisible());
1954 docstring const msg =
1955 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1961 case LFUN_TOOLBAR_MOVABLE: {
1962 string const name = cmd.getArg(0);
1963 // use negation since locked == !movable
1965 // toolbar name * locks all toolbars
1966 flag.setOnOff(!toolbarsMovable_);
1967 else if (GuiToolbar * t = toolbar(name))
1968 flag.setOnOff(!(t->isMovable()));
1971 docstring const msg =
1972 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1978 case LFUN_ICON_SIZE:
1979 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
1982 case LFUN_DROP_LAYOUTS_CHOICE:
1986 case LFUN_UI_TOGGLE:
1987 flag.setOnOff(isFullScreen());
1990 case LFUN_DIALOG_DISCONNECT_INSET:
1993 case LFUN_DIALOG_HIDE:
1994 // FIXME: should we check if the dialog is shown?
1997 case LFUN_DIALOG_TOGGLE:
1998 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2001 case LFUN_DIALOG_SHOW: {
2002 string const name = cmd.getArg(0);
2004 enable = name == "aboutlyx"
2005 || name == "file" //FIXME: should be removed.
2007 || name == "texinfo"
2008 || name == "progress"
2009 || name == "compare";
2010 else if (name == "character" || name == "symbols"
2011 || name == "mathdelimiter" || name == "mathmatrix") {
2012 if (!buf || buf->isReadonly())
2015 Cursor const & cur = currentBufferView()->cursor();
2016 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2019 else if (name == "latexlog")
2020 enable = FileName(doc_buffer->logName()).isReadableFile();
2021 else if (name == "spellchecker")
2022 enable = theSpellChecker()
2023 && !doc_buffer->isReadonly()
2024 && !doc_buffer->text().empty();
2025 else if (name == "vclog")
2026 enable = doc_buffer->lyxvc().inUse();
2030 case LFUN_DIALOG_UPDATE: {
2031 string const name = cmd.getArg(0);
2033 enable = name == "prefs";
2037 case LFUN_COMMAND_EXECUTE:
2039 case LFUN_MENU_OPEN:
2040 // Nothing to check.
2043 case LFUN_COMPLETION_INLINE:
2044 if (!d.current_work_area_
2045 || !d.current_work_area_->completer().inlinePossible(
2046 currentBufferView()->cursor()))
2050 case LFUN_COMPLETION_POPUP:
2051 if (!d.current_work_area_
2052 || !d.current_work_area_->completer().popupPossible(
2053 currentBufferView()->cursor()))
2058 if (!d.current_work_area_
2059 || !d.current_work_area_->completer().inlinePossible(
2060 currentBufferView()->cursor()))
2064 case LFUN_COMPLETION_ACCEPT:
2065 if (!d.current_work_area_
2066 || (!d.current_work_area_->completer().popupVisible()
2067 && !d.current_work_area_->completer().inlineVisible()
2068 && !d.current_work_area_->completer().completionAvailable()))
2072 case LFUN_COMPLETION_CANCEL:
2073 if (!d.current_work_area_
2074 || (!d.current_work_area_->completer().popupVisible()
2075 && !d.current_work_area_->completer().inlineVisible()))
2079 case LFUN_BUFFER_ZOOM_OUT:
2080 case LFUN_BUFFER_ZOOM_IN: {
2081 // only diff between these two is that the default for ZOOM_OUT
2083 bool const neg_zoom =
2084 convert<int>(cmd.argument()) < 0 ||
2085 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2086 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2087 docstring const msg =
2088 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2092 enable = doc_buffer;
2096 case LFUN_BUFFER_ZOOM: {
2097 bool const less_than_min_zoom =
2098 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2099 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2100 docstring const msg =
2101 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2106 enable = doc_buffer;
2110 case LFUN_BUFFER_MOVE_NEXT:
2111 case LFUN_BUFFER_MOVE_PREVIOUS:
2112 // we do not cycle when moving
2113 case LFUN_BUFFER_NEXT:
2114 case LFUN_BUFFER_PREVIOUS:
2115 // because we cycle, it doesn't matter whether on first or last
2116 enable = (d.currentTabWorkArea()->count() > 1);
2118 case LFUN_BUFFER_SWITCH:
2119 // toggle on the current buffer, but do not toggle off
2120 // the other ones (is that a good idea?)
2122 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2123 flag.setOnOff(true);
2126 case LFUN_VC_REGISTER:
2127 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2129 case LFUN_VC_RENAME:
2130 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2133 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2135 case LFUN_VC_CHECK_IN:
2136 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2138 case LFUN_VC_CHECK_OUT:
2139 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2141 case LFUN_VC_LOCKING_TOGGLE:
2142 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2143 && doc_buffer->lyxvc().lockingToggleEnabled();
2144 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2146 case LFUN_VC_REVERT:
2147 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2148 && !doc_buffer->hasReadonlyFlag();
2150 case LFUN_VC_UNDO_LAST:
2151 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2153 case LFUN_VC_REPO_UPDATE:
2154 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2156 case LFUN_VC_COMMAND: {
2157 if (cmd.argument().empty())
2159 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2163 case LFUN_VC_COMPARE:
2164 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2167 case LFUN_SERVER_GOTO_FILE_ROW:
2168 case LFUN_LYX_ACTIVATE:
2170 case LFUN_FORWARD_SEARCH:
2171 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2174 case LFUN_FILE_INSERT_PLAINTEXT:
2175 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2176 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2179 case LFUN_SPELLING_CONTINUOUSLY:
2180 flag.setOnOff(lyxrc.spellcheck_continuously);
2188 flag.setEnabled(false);
2194 static FileName selectTemplateFile()
2196 FileDialog dlg(qt_("Select template file"));
2197 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2198 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2200 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2201 QStringList(qt_("LyX Documents (*.lyx)")));
2203 if (result.first == FileDialog::Later)
2205 if (result.second.isEmpty())
2207 return FileName(fromqstr(result.second));
2211 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2215 Buffer * newBuffer = 0;
2217 newBuffer = checkAndLoadLyXFile(filename);
2218 } catch (ExceptionMessage const & e) {
2225 message(_("Document not loaded."));
2229 setBuffer(newBuffer);
2230 newBuffer->errors("Parse");
2233 theSession().lastFiles().add(filename);
2234 theSession().writeFile();
2241 void GuiView::openDocument(string const & fname)
2243 string initpath = lyxrc.document_path;
2245 if (documentBufferView()) {
2246 string const trypath = documentBufferView()->buffer().filePath();
2247 // If directory is writeable, use this as default.
2248 if (FileName(trypath).isDirWritable())
2254 if (fname.empty()) {
2255 FileDialog dlg(qt_("Select document to open"));
2256 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2257 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2259 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2260 FileDialog::Result result =
2261 dlg.open(toqstr(initpath), filter);
2263 if (result.first == FileDialog::Later)
2266 filename = fromqstr(result.second);
2268 // check selected filename
2269 if (filename.empty()) {
2270 message(_("Canceled."));
2276 // get absolute path of file and add ".lyx" to the filename if
2278 FileName const fullname =
2279 fileSearch(string(), filename, "lyx", support::may_not_exist);
2280 if (!fullname.empty())
2281 filename = fullname.absFileName();
2283 if (!fullname.onlyPath().isDirectory()) {
2284 Alert::warning(_("Invalid filename"),
2285 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2286 from_utf8(fullname.absFileName())));
2290 // if the file doesn't exist and isn't already open (bug 6645),
2291 // let the user create one
2292 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2293 !LyXVC::file_not_found_hook(fullname)) {
2294 // the user specifically chose this name. Believe him.
2295 Buffer * const b = newFile(filename, string(), true);
2301 docstring const disp_fn = makeDisplayPath(filename);
2302 message(bformat(_("Opening document %1$s..."), disp_fn));
2305 Buffer * buf = loadDocument(fullname);
2307 str2 = bformat(_("Document %1$s opened."), disp_fn);
2308 if (buf->lyxvc().inUse())
2309 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2310 " " + _("Version control detected.");
2312 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2317 // FIXME: clean that
2318 static bool import(GuiView * lv, FileName const & filename,
2319 string const & format, ErrorList & errorList)
2321 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2323 string loader_format;
2324 vector<string> loaders = theConverters().loaders();
2325 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2326 vector<string>::const_iterator it = loaders.begin();
2327 vector<string>::const_iterator en = loaders.end();
2328 for (; it != en; ++it) {
2329 if (!theConverters().isReachable(format, *it))
2332 string const tofile =
2333 support::changeExtension(filename.absFileName(),
2334 theFormats().extension(*it));
2335 if (!theConverters().convert(0, filename, FileName(tofile),
2336 filename, format, *it, errorList))
2338 loader_format = *it;
2341 if (loader_format.empty()) {
2342 frontend::Alert::error(_("Couldn't import file"),
2343 bformat(_("No information for importing the format %1$s."),
2344 theFormats().prettyName(format)));
2348 loader_format = format;
2350 if (loader_format == "lyx") {
2351 Buffer * buf = lv->loadDocument(lyxfile);
2355 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2359 bool as_paragraphs = loader_format == "textparagraph";
2360 string filename2 = (loader_format == format) ? filename.absFileName()
2361 : support::changeExtension(filename.absFileName(),
2362 theFormats().extension(loader_format));
2363 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2365 guiApp->setCurrentView(lv);
2366 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2373 void GuiView::importDocument(string const & argument)
2376 string filename = split(argument, format, ' ');
2378 LYXERR(Debug::INFO, format << " file: " << filename);
2380 // need user interaction
2381 if (filename.empty()) {
2382 string initpath = lyxrc.document_path;
2383 if (documentBufferView()) {
2384 string const trypath = documentBufferView()->buffer().filePath();
2385 // If directory is writeable, use this as default.
2386 if (FileName(trypath).isDirWritable())
2390 docstring const text = bformat(_("Select %1$s file to import"),
2391 theFormats().prettyName(format));
2393 FileDialog dlg(toqstr(text));
2394 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2395 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2397 docstring filter = theFormats().prettyName(format);
2400 filter += from_utf8(theFormats().extensions(format));
2403 FileDialog::Result result =
2404 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2406 if (result.first == FileDialog::Later)
2409 filename = fromqstr(result.second);
2411 // check selected filename
2412 if (filename.empty())
2413 message(_("Canceled."));
2416 if (filename.empty())
2419 // get absolute path of file
2420 FileName const fullname(support::makeAbsPath(filename));
2422 // Can happen if the user entered a path into the dialog
2424 if (fullname.onlyFileName().empty()) {
2425 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2426 "Aborting import."),
2427 from_utf8(fullname.absFileName()));
2428 frontend::Alert::error(_("File name error"), msg);
2429 message(_("Canceled."));
2434 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2436 // Check if the document already is open
2437 Buffer * buf = theBufferList().getBuffer(lyxfile);
2440 if (!closeBuffer()) {
2441 message(_("Canceled."));
2446 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2448 // if the file exists already, and we didn't do
2449 // -i lyx thefile.lyx, warn
2450 if (lyxfile.exists() && fullname != lyxfile) {
2452 docstring text = bformat(_("The document %1$s already exists.\n\n"
2453 "Do you want to overwrite that document?"), displaypath);
2454 int const ret = Alert::prompt(_("Overwrite document?"),
2455 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2458 message(_("Canceled."));
2463 message(bformat(_("Importing %1$s..."), displaypath));
2464 ErrorList errorList;
2465 if (import(this, fullname, format, errorList))
2466 message(_("imported."));
2468 message(_("file not imported!"));
2470 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2474 void GuiView::newDocument(string const & filename, bool from_template)
2476 FileName initpath(lyxrc.document_path);
2477 if (documentBufferView()) {
2478 FileName const trypath(documentBufferView()->buffer().filePath());
2479 // If directory is writeable, use this as default.
2480 if (trypath.isDirWritable())
2484 string templatefile;
2485 if (from_template) {
2486 templatefile = selectTemplateFile().absFileName();
2487 if (templatefile.empty())
2492 if (filename.empty())
2493 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2495 b = newFile(filename, templatefile, true);
2500 // If no new document could be created, it is unsure
2501 // whether there is a valid BufferView.
2502 if (currentBufferView())
2503 // Ensure the cursor is correctly positioned on screen.
2504 currentBufferView()->showCursor();
2508 void GuiView::insertLyXFile(docstring const & fname)
2510 BufferView * bv = documentBufferView();
2515 FileName filename(to_utf8(fname));
2516 if (filename.empty()) {
2517 // Launch a file browser
2519 string initpath = lyxrc.document_path;
2520 string const trypath = bv->buffer().filePath();
2521 // If directory is writeable, use this as default.
2522 if (FileName(trypath).isDirWritable())
2526 FileDialog dlg(qt_("Select LyX document to insert"));
2527 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2528 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2530 FileDialog::Result result = dlg.open(toqstr(initpath),
2531 QStringList(qt_("LyX Documents (*.lyx)")));
2533 if (result.first == FileDialog::Later)
2537 filename.set(fromqstr(result.second));
2539 // check selected filename
2540 if (filename.empty()) {
2541 // emit message signal.
2542 message(_("Canceled."));
2547 bv->insertLyXFile(filename);
2548 bv->buffer().errors("Parse");
2552 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2554 FileName fname = b.fileName();
2555 FileName const oldname = fname;
2557 if (!newname.empty()) {
2559 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2561 // Switch to this Buffer.
2564 // No argument? Ask user through dialog.
2566 FileDialog dlg(qt_("Choose a filename to save document as"));
2567 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2568 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2570 if (!isLyXFileName(fname.absFileName()))
2571 fname.changeExtension(".lyx");
2573 FileDialog::Result result =
2574 dlg.save(toqstr(fname.onlyPath().absFileName()),
2575 QStringList(qt_("LyX Documents (*.lyx)")),
2576 toqstr(fname.onlyFileName()));
2578 if (result.first == FileDialog::Later)
2581 fname.set(fromqstr(result.second));
2586 if (!isLyXFileName(fname.absFileName()))
2587 fname.changeExtension(".lyx");
2590 // fname is now the new Buffer location.
2592 // if there is already a Buffer open with this name, we do not want
2593 // to have another one. (the second test makes sure we're not just
2594 // trying to overwrite ourselves, which is fine.)
2595 if (theBufferList().exists(fname) && fname != oldname
2596 && theBufferList().getBuffer(fname) != &b) {
2597 docstring const text =
2598 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2599 "Please close it before attempting to overwrite it.\n"
2600 "Do you want to choose a new filename?"),
2601 from_utf8(fname.absFileName()));
2602 int const ret = Alert::prompt(_("Chosen File Already Open"),
2603 text, 0, 1, _("&Rename"), _("&Cancel"));
2605 case 0: return renameBuffer(b, docstring(), kind);
2606 case 1: return false;
2611 bool const existsLocal = fname.exists();
2612 bool const existsInVC = LyXVC::fileInVC(fname);
2613 if (existsLocal || existsInVC) {
2614 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2615 if (kind != LV_WRITE_AS && existsInVC) {
2616 // renaming to a name that is already in VC
2618 docstring text = bformat(_("The document %1$s "
2619 "is already registered.\n\n"
2620 "Do you want to choose a new name?"),
2622 docstring const title = (kind == LV_VC_RENAME) ?
2623 _("Rename document?") : _("Copy document?");
2624 docstring const button = (kind == LV_VC_RENAME) ?
2625 _("&Rename") : _("&Copy");
2626 int const ret = Alert::prompt(title, text, 0, 1,
2627 button, _("&Cancel"));
2629 case 0: return renameBuffer(b, docstring(), kind);
2630 case 1: return false;
2635 docstring text = bformat(_("The document %1$s "
2636 "already exists.\n\n"
2637 "Do you want to overwrite that document?"),
2639 int const ret = Alert::prompt(_("Overwrite document?"),
2640 text, 0, 2, _("&Overwrite"),
2641 _("&Rename"), _("&Cancel"));
2644 case 1: return renameBuffer(b, docstring(), kind);
2645 case 2: return false;
2651 case LV_VC_RENAME: {
2652 string msg = b.lyxvc().rename(fname);
2655 message(from_utf8(msg));
2659 string msg = b.lyxvc().copy(fname);
2662 message(from_utf8(msg));
2668 // LyXVC created the file already in case of LV_VC_RENAME or
2669 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2670 // relative paths of included stuff right if we moved e.g. from
2671 // /a/b.lyx to /a/c/b.lyx.
2673 bool const saved = saveBuffer(b, fname);
2680 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2682 FileName fname = b.fileName();
2684 FileDialog dlg(qt_("Choose a filename to export the document as"));
2685 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2688 QString const anyformat = qt_("Guess from extension (*.*)");
2691 vector<Format const *> export_formats;
2692 for (Format const & f : theFormats())
2693 if (f.documentFormat())
2694 export_formats.push_back(&f);
2695 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2696 map<QString, string> fmap;
2699 for (Format const * f : export_formats) {
2700 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2701 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2703 from_ascii(f->extension())));
2704 types << loc_filter;
2705 fmap[loc_filter] = f->name();
2706 if (from_ascii(f->name()) == iformat) {
2707 filter = loc_filter;
2708 ext = f->extension();
2711 string ofname = fname.onlyFileName();
2713 ofname = support::changeExtension(ofname, ext);
2714 FileDialog::Result result =
2715 dlg.save(toqstr(fname.onlyPath().absFileName()),
2719 if (result.first != FileDialog::Chosen)
2723 fname.set(fromqstr(result.second));
2724 if (filter == anyformat)
2725 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2727 fmt_name = fmap[filter];
2728 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2729 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2731 if (fmt_name.empty() || fname.empty())
2734 // fname is now the new Buffer location.
2735 if (FileName(fname).exists()) {
2736 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2737 docstring text = bformat(_("The document %1$s already "
2738 "exists.\n\nDo you want to "
2739 "overwrite that document?"),
2741 int const ret = Alert::prompt(_("Overwrite document?"),
2742 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2745 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2746 case 2: return false;
2750 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2753 return dr.dispatched();
2757 bool GuiView::saveBuffer(Buffer & b)
2759 return saveBuffer(b, FileName());
2763 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2765 if (workArea(b) && workArea(b)->inDialogMode())
2768 if (fn.empty() && b.isUnnamed())
2769 return renameBuffer(b, docstring());
2771 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2773 theSession().lastFiles().add(b.fileName());
2774 theSession().writeFile();
2778 // Switch to this Buffer.
2781 // FIXME: we don't tell the user *WHY* the save failed !!
2782 docstring const file = makeDisplayPath(b.absFileName(), 30);
2783 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2784 "Do you want to rename the document and "
2785 "try again?"), file);
2786 int const ret = Alert::prompt(_("Rename and save?"),
2787 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2790 if (!renameBuffer(b, docstring()))
2799 return saveBuffer(b, fn);
2803 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2805 return closeWorkArea(wa, false);
2809 // We only want to close the buffer if it is not visible in other workareas
2810 // of the same view, nor in other views, and if this is not a child
2811 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2813 Buffer & buf = wa->bufferView().buffer();
2815 bool last_wa = d.countWorkAreasOf(buf) == 1
2816 && !inOtherView(buf) && !buf.parent();
2818 bool close_buffer = last_wa;
2821 if (lyxrc.close_buffer_with_last_view == "yes")
2823 else if (lyxrc.close_buffer_with_last_view == "no")
2824 close_buffer = false;
2827 if (buf.isUnnamed())
2828 file = from_utf8(buf.fileName().onlyFileName());
2830 file = buf.fileName().displayName(30);
2831 docstring const text = bformat(
2832 _("Last view on document %1$s is being closed.\n"
2833 "Would you like to close or hide the document?\n"
2835 "Hidden documents can be displayed back through\n"
2836 "the menu: View->Hidden->...\n"
2838 "To remove this question, set your preference in:\n"
2839 " Tools->Preferences->Look&Feel->UserInterface\n"
2841 int ret = Alert::prompt(_("Close or hide document?"),
2842 text, 0, 1, _("&Close"), _("&Hide"));
2843 close_buffer = (ret == 0);
2847 return closeWorkArea(wa, close_buffer);
2851 bool GuiView::closeBuffer()
2853 GuiWorkArea * wa = currentMainWorkArea();
2854 // coverity complained about this
2855 // it seems unnecessary, but perhaps is worth the check
2856 LASSERT(wa, return false);
2858 setCurrentWorkArea(wa);
2859 Buffer & buf = wa->bufferView().buffer();
2860 return closeWorkArea(wa, !buf.parent());
2864 void GuiView::writeSession() const {
2865 GuiWorkArea const * active_wa = currentMainWorkArea();
2866 for (int i = 0; i < d.splitter_->count(); ++i) {
2867 TabWorkArea * twa = d.tabWorkArea(i);
2868 for (int j = 0; j < twa->count(); ++j) {
2869 GuiWorkArea * wa = twa->workArea(j);
2870 Buffer & buf = wa->bufferView().buffer();
2871 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2877 bool GuiView::closeBufferAll()
2879 // Close the workareas in all other views
2880 QList<int> const ids = guiApp->viewIds();
2881 for (int i = 0; i != ids.size(); ++i) {
2882 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2886 // Close our own workareas
2887 if (!closeWorkAreaAll())
2890 // Now close the hidden buffers. We prevent hidden buffers from being
2891 // dirty, so we can just close them.
2892 theBufferList().closeAll();
2897 bool GuiView::closeWorkAreaAll()
2899 setCurrentWorkArea(currentMainWorkArea());
2901 // We might be in a situation that there is still a tabWorkArea, but
2902 // there are no tabs anymore. This can happen when we get here after a
2903 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2904 // many TabWorkArea's have no documents anymore.
2907 // We have to call count() each time, because it can happen that
2908 // more than one splitter will disappear in one iteration (bug 5998).
2909 while (d.splitter_->count() > empty_twa) {
2910 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2912 if (twa->count() == 0)
2915 setCurrentWorkArea(twa->currentWorkArea());
2916 if (!closeTabWorkArea(twa))
2924 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2929 Buffer & buf = wa->bufferView().buffer();
2931 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2932 Alert::warning(_("Close document"),
2933 _("Document could not be closed because it is being processed by LyX."));
2938 return closeBuffer(buf);
2940 if (!inMultiTabs(wa))
2941 if (!saveBufferIfNeeded(buf, true))
2949 bool GuiView::closeBuffer(Buffer & buf)
2951 // If we are in a close_event all children will be closed in some time,
2952 // so no need to do it here. This will ensure that the children end up
2953 // in the session file in the correct order. If we close the master
2954 // buffer, we can close or release the child buffers here too.
2955 bool success = true;
2957 ListOfBuffers clist = buf.getChildren();
2958 ListOfBuffers::const_iterator it = clist.begin();
2959 ListOfBuffers::const_iterator const bend = clist.end();
2960 for (; it != bend; ++it) {
2961 Buffer * child_buf = *it;
2962 if (theBufferList().isOthersChild(&buf, child_buf)) {
2963 child_buf->setParent(0);
2967 // FIXME: should we look in other tabworkareas?
2968 // ANSWER: I don't think so. I've tested, and if the child is
2969 // open in some other window, it closes without a problem.
2970 GuiWorkArea * child_wa = workArea(*child_buf);
2972 success = closeWorkArea(child_wa, true);
2976 // In this case the child buffer is open but hidden.
2977 // It therefore should not (MUST NOT) be dirty!
2978 LATTEST(child_buf->isClean());
2979 theBufferList().release(child_buf);
2984 // goto bookmark to update bookmark pit.
2985 // FIXME: we should update only the bookmarks related to this buffer!
2986 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
2987 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
2988 guiApp->gotoBookmark(i+1, false, false);
2990 if (saveBufferIfNeeded(buf, false)) {
2991 buf.removeAutosaveFile();
2992 theBufferList().release(&buf);
2996 // open all children again to avoid a crash because of dangling
2997 // pointers (bug 6603)
3003 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3005 while (twa == d.currentTabWorkArea()) {
3006 twa->setCurrentIndex(twa->count() - 1);
3008 GuiWorkArea * wa = twa->currentWorkArea();
3009 Buffer & b = wa->bufferView().buffer();
3011 // We only want to close the buffer if the same buffer is not visible
3012 // in another view, and if this is not a child and if we are closing
3013 // a view (not a tabgroup).
3014 bool const close_buffer =
3015 !inOtherView(b) && !b.parent() && closing_;
3017 if (!closeWorkArea(wa, close_buffer))
3024 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3026 if (buf.isClean() || buf.paragraphs().empty())
3029 // Switch to this Buffer.
3035 if (buf.isUnnamed()) {
3036 file = from_utf8(buf.fileName().onlyFileName());
3039 FileName filename = buf.fileName();
3041 file = filename.displayName(30);
3042 exists = filename.exists();
3045 // Bring this window to top before asking questions.
3050 if (hiding && buf.isUnnamed()) {
3051 docstring const text = bformat(_("The document %1$s has not been "
3052 "saved yet.\n\nDo you want to save "
3053 "the document?"), file);
3054 ret = Alert::prompt(_("Save new document?"),
3055 text, 0, 1, _("&Save"), _("&Cancel"));
3059 docstring const text = exists ?
3060 bformat(_("The document %1$s has unsaved changes."
3061 "\n\nDo you want to save the document or "
3062 "discard the changes?"), file) :
3063 bformat(_("The document %1$s has not been saved yet."
3064 "\n\nDo you want to save the document or "
3065 "discard it entirely?"), file);
3066 docstring const title = exists ?
3067 _("Save changed document?") : _("Save document?");
3068 ret = Alert::prompt(title, text, 0, 2,
3069 _("&Save"), _("&Discard"), _("&Cancel"));
3074 if (!saveBuffer(buf))
3078 // If we crash after this we could have no autosave file
3079 // but I guess this is really improbable (Jug).
3080 // Sometimes improbable things happen:
3081 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3082 // buf.removeAutosaveFile();
3084 // revert all changes
3095 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3097 Buffer & buf = wa->bufferView().buffer();
3099 for (int i = 0; i != d.splitter_->count(); ++i) {
3100 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3101 if (wa_ && wa_ != wa)
3104 return inOtherView(buf);
3108 bool GuiView::inOtherView(Buffer & buf)
3110 QList<int> const ids = guiApp->viewIds();
3112 for (int i = 0; i != ids.size(); ++i) {
3116 if (guiApp->view(ids[i]).workArea(buf))
3123 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3125 if (!documentBufferView())
3128 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3129 Buffer * const curbuf = &documentBufferView()->buffer();
3130 int nwa = twa->count();
3131 for (int i = 0; i < nwa; ++i) {
3132 if (&workArea(i)->bufferView().buffer() == curbuf) {
3134 if (np == NEXTBUFFER)
3135 next_index = (i == nwa - 1 ? 0 : i + 1);
3137 next_index = (i == 0 ? nwa - 1 : i - 1);
3139 twa->moveTab(i, next_index);
3141 setBuffer(&workArea(next_index)->bufferView().buffer());
3149 /// make sure the document is saved
3150 static bool ensureBufferClean(Buffer * buffer)
3152 LASSERT(buffer, return false);
3153 if (buffer->isClean() && !buffer->isUnnamed())
3156 docstring const file = buffer->fileName().displayName(30);
3159 if (!buffer->isUnnamed()) {
3160 text = bformat(_("The document %1$s has unsaved "
3161 "changes.\n\nDo you want to save "
3162 "the document?"), file);
3163 title = _("Save changed document?");
3166 text = bformat(_("The document %1$s has not been "
3167 "saved yet.\n\nDo you want to save "
3168 "the document?"), file);
3169 title = _("Save new document?");
3171 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3174 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3176 return buffer->isClean() && !buffer->isUnnamed();
3180 bool GuiView::reloadBuffer(Buffer & buf)
3182 currentBufferView()->cursor().reset();
3183 Buffer::ReadStatus status = buf.reload();
3184 return status == Buffer::ReadSuccess;
3188 void GuiView::checkExternallyModifiedBuffers()
3190 BufferList::iterator bit = theBufferList().begin();
3191 BufferList::iterator const bend = theBufferList().end();
3192 for (; bit != bend; ++bit) {
3193 Buffer * buf = *bit;
3194 if (buf->fileName().exists() && buf->isChecksumModified()) {
3195 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3196 " Reload now? Any local changes will be lost."),
3197 from_utf8(buf->absFileName()));
3198 int const ret = Alert::prompt(_("Reload externally changed document?"),
3199 text, 0, 1, _("&Reload"), _("&Cancel"));
3207 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3209 Buffer * buffer = documentBufferView()
3210 ? &(documentBufferView()->buffer()) : 0;
3212 switch (cmd.action()) {
3213 case LFUN_VC_REGISTER:
3214 if (!buffer || !ensureBufferClean(buffer))
3216 if (!buffer->lyxvc().inUse()) {
3217 if (buffer->lyxvc().registrer()) {
3218 reloadBuffer(*buffer);
3219 dr.clearMessageUpdate();
3224 case LFUN_VC_RENAME:
3225 case LFUN_VC_COPY: {
3226 if (!buffer || !ensureBufferClean(buffer))
3228 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3229 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3230 // Some changes are not yet committed.
3231 // We test here and not in getStatus(), since
3232 // this test is expensive.
3234 LyXVC::CommandResult ret =
3235 buffer->lyxvc().checkIn(log);
3237 if (ret == LyXVC::ErrorCommand ||
3238 ret == LyXVC::VCSuccess)
3239 reloadBuffer(*buffer);
3240 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3241 frontend::Alert::error(
3242 _("Revision control error."),
3243 _("Document could not be checked in."));
3247 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3248 LV_VC_RENAME : LV_VC_COPY;
3249 renameBuffer(*buffer, cmd.argument(), kind);
3254 case LFUN_VC_CHECK_IN:
3255 if (!buffer || !ensureBufferClean(buffer))
3257 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3259 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3261 // Only skip reloading if the checkin was cancelled or
3262 // an error occurred before the real checkin VCS command
3263 // was executed, since the VCS might have changed the
3264 // file even if it could not checkin successfully.
3265 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3266 reloadBuffer(*buffer);
3270 case LFUN_VC_CHECK_OUT:
3271 if (!buffer || !ensureBufferClean(buffer))
3273 if (buffer->lyxvc().inUse()) {
3274 dr.setMessage(buffer->lyxvc().checkOut());
3275 reloadBuffer(*buffer);
3279 case LFUN_VC_LOCKING_TOGGLE:
3280 LASSERT(buffer, return);
3281 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3283 if (buffer->lyxvc().inUse()) {
3284 string res = buffer->lyxvc().lockingToggle();
3286 frontend::Alert::error(_("Revision control error."),
3287 _("Error when setting the locking property."));
3290 reloadBuffer(*buffer);
3295 case LFUN_VC_REVERT:
3296 LASSERT(buffer, return);
3297 if (buffer->lyxvc().revert()) {
3298 reloadBuffer(*buffer);
3299 dr.clearMessageUpdate();
3303 case LFUN_VC_UNDO_LAST:
3304 LASSERT(buffer, return);
3305 buffer->lyxvc().undoLast();
3306 reloadBuffer(*buffer);
3307 dr.clearMessageUpdate();
3310 case LFUN_VC_REPO_UPDATE:
3311 LASSERT(buffer, return);
3312 if (ensureBufferClean(buffer)) {
3313 dr.setMessage(buffer->lyxvc().repoUpdate());
3314 checkExternallyModifiedBuffers();
3318 case LFUN_VC_COMMAND: {
3319 string flag = cmd.getArg(0);
3320 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3323 if (contains(flag, 'M')) {
3324 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3327 string path = cmd.getArg(1);
3328 if (contains(path, "$$p") && buffer)
3329 path = subst(path, "$$p", buffer->filePath());
3330 LYXERR(Debug::LYXVC, "Directory: " << path);
3332 if (!pp.isReadableDirectory()) {
3333 lyxerr << _("Directory is not accessible.") << endl;
3336 support::PathChanger p(pp);
3338 string command = cmd.getArg(2);
3339 if (command.empty())
3342 command = subst(command, "$$i", buffer->absFileName());
3343 command = subst(command, "$$p", buffer->filePath());
3345 command = subst(command, "$$m", to_utf8(message));
3346 LYXERR(Debug::LYXVC, "Command: " << command);
3348 one.startscript(Systemcall::Wait, command);
3352 if (contains(flag, 'I'))
3353 buffer->markDirty();
3354 if (contains(flag, 'R'))
3355 reloadBuffer(*buffer);
3360 case LFUN_VC_COMPARE: {
3361 if (cmd.argument().empty()) {
3362 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3366 string rev1 = cmd.getArg(0);
3371 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3374 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3375 f2 = buffer->absFileName();
3377 string rev2 = cmd.getArg(1);
3381 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3385 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3386 f1 << "\n" << f2 << "\n" );
3387 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3388 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3398 void GuiView::openChildDocument(string const & fname)
3400 LASSERT(documentBufferView(), return);
3401 Buffer & buffer = documentBufferView()->buffer();
3402 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3403 documentBufferView()->saveBookmark(false);
3405 if (theBufferList().exists(filename)) {
3406 child = theBufferList().getBuffer(filename);
3409 message(bformat(_("Opening child document %1$s..."),
3410 makeDisplayPath(filename.absFileName())));
3411 child = loadDocument(filename, false);
3413 // Set the parent name of the child document.
3414 // This makes insertion of citations and references in the child work,
3415 // when the target is in the parent or another child document.
3417 child->setParent(&buffer);
3421 bool GuiView::goToFileRow(string const & argument)
3425 size_t i = argument.find_last_of(' ');
3426 if (i != string::npos) {
3427 file_name = os::internal_path(trim(argument.substr(0, i)));
3428 istringstream is(argument.substr(i + 1));
3433 if (i == string::npos) {
3434 LYXERR0("Wrong argument: " << argument);
3438 string const abstmp = package().temp_dir().absFileName();
3439 string const realtmp = package().temp_dir().realPath();
3440 // We have to use os::path_prefix_is() here, instead of
3441 // simply prefixIs(), because the file name comes from
3442 // an external application and may need case adjustment.
3443 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3444 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3445 // Needed by inverse dvi search. If it is a file
3446 // in tmpdir, call the apropriated function.
3447 // If tmpdir is a symlink, we may have the real
3448 // path passed back, so we correct for that.
3449 if (!prefixIs(file_name, abstmp))
3450 file_name = subst(file_name, realtmp, abstmp);
3451 buf = theBufferList().getBufferFromTmp(file_name);
3453 // Must replace extension of the file to be .lyx
3454 // and get full path
3455 FileName const s = fileSearch(string(),
3456 support::changeExtension(file_name, ".lyx"), "lyx");
3457 // Either change buffer or load the file
3458 if (theBufferList().exists(s))
3459 buf = theBufferList().getBuffer(s);
3460 else if (s.exists()) {
3461 buf = loadDocument(s);
3466 _("File does not exist: %1$s"),
3467 makeDisplayPath(file_name)));
3473 _("No buffer for file: %1$s."),
3474 makeDisplayPath(file_name))
3479 bool success = documentBufferView()->setCursorFromRow(row);
3481 LYXERR(Debug::LATEX,
3482 "setCursorFromRow: invalid position for row " << row);
3483 frontend::Alert::error(_("Inverse Search Failed"),
3484 _("Invalid position requested by inverse search.\n"
3485 "You may need to update the viewed document."));
3491 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3493 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3494 menu->exec(QCursor::pos());
3499 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3501 Buffer::ExportStatus const status = func(format);
3503 // the cloning operation will have produced a clone of the entire set of
3504 // documents, starting from the master. so we must delete those.
3505 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3507 busyBuffers.remove(orig);
3512 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3514 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3515 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3519 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3521 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3522 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3526 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3528 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3529 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3533 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3534 string const & argument,
3535 Buffer const * used_buffer,
3536 docstring const & msg,
3537 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3538 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3539 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3544 string format = argument;
3546 format = used_buffer->params().getDefaultOutputFormat();
3547 processing_format = format;
3549 progress_->clearMessages();
3552 #if EXPORT_in_THREAD
3553 GuiViewPrivate::busyBuffers.insert(used_buffer);
3554 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3555 if (!cloned_buffer) {
3556 Alert::error(_("Export Error"),
3557 _("Error cloning the Buffer."));
3560 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3565 setPreviewFuture(f);
3566 last_export_format = used_buffer->params().bufferFormat();
3569 // We are asynchronous, so we don't know here anything about the success
3572 Buffer::ExportStatus status;
3574 status = (used_buffer->*syncFunc)(format, true);
3575 } else if (previewFunc) {
3576 status = (used_buffer->*previewFunc)(format);
3579 handleExportStatus(gv_, status, format);
3581 return (status == Buffer::ExportSuccess
3582 || status == Buffer::PreviewSuccess);
3586 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3588 BufferView * bv = currentBufferView();
3589 LASSERT(bv, return);
3591 // Let the current BufferView dispatch its own actions.
3592 bv->dispatch(cmd, dr);
3593 if (dr.dispatched())
3596 // Try with the document BufferView dispatch if any.
3597 BufferView * doc_bv = documentBufferView();
3598 if (doc_bv && doc_bv != bv) {
3599 doc_bv->dispatch(cmd, dr);
3600 if (dr.dispatched())
3604 // Then let the current Cursor dispatch its own actions.
3605 bv->cursor().dispatch(cmd);
3607 // update completion. We do it here and not in
3608 // processKeySym to avoid another redraw just for a
3609 // changed inline completion
3610 if (cmd.origin() == FuncRequest::KEYBOARD) {
3611 if (cmd.action() == LFUN_SELF_INSERT
3612 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3613 updateCompletion(bv->cursor(), true, true);
3614 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3615 updateCompletion(bv->cursor(), false, true);
3617 updateCompletion(bv->cursor(), false, false);
3620 dr = bv->cursor().result();
3624 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3626 BufferView * bv = currentBufferView();
3627 // By default we won't need any update.
3628 dr.screenUpdate(Update::None);
3629 // assume cmd will be dispatched
3630 dr.dispatched(true);
3632 Buffer * doc_buffer = documentBufferView()
3633 ? &(documentBufferView()->buffer()) : 0;
3635 if (cmd.origin() == FuncRequest::TOC) {
3636 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3637 // FIXME: do we need to pass a DispatchResult object here?
3638 toc->doDispatch(bv->cursor(), cmd);
3642 string const argument = to_utf8(cmd.argument());
3644 switch(cmd.action()) {
3645 case LFUN_BUFFER_CHILD_OPEN:
3646 openChildDocument(to_utf8(cmd.argument()));
3649 case LFUN_BUFFER_IMPORT:
3650 importDocument(to_utf8(cmd.argument()));
3653 case LFUN_BUFFER_EXPORT: {
3656 // GCC only sees strfwd.h when building merged
3657 if (::lyx::operator==(cmd.argument(), "custom")) {
3658 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3662 string const dest = cmd.getArg(1);
3663 FileName target_dir;
3664 if (!dest.empty() && FileName::isAbsolute(dest))
3665 target_dir = FileName(support::onlyPath(dest));
3667 target_dir = doc_buffer->fileName().onlyPath();
3669 string const format = (argument.empty() || argument == "default") ?
3670 doc_buffer->params().getDefaultOutputFormat() : argument;
3672 if ((dest.empty() && doc_buffer->isUnnamed())
3673 || !target_dir.isDirWritable()) {
3674 exportBufferAs(*doc_buffer, from_utf8(format));
3677 /* TODO/Review: Is it a problem to also export the children?
3678 See the update_unincluded flag */
3679 d.asyncBufferProcessing(format,
3682 &GuiViewPrivate::exportAndDestroy,
3685 // TODO Inform user about success
3689 case LFUN_BUFFER_EXPORT_AS: {
3690 LASSERT(doc_buffer, break);
3691 docstring f = cmd.argument();
3693 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3694 exportBufferAs(*doc_buffer, f);
3698 case LFUN_BUFFER_UPDATE: {
3699 d.asyncBufferProcessing(argument,
3702 &GuiViewPrivate::compileAndDestroy,
3707 case LFUN_BUFFER_VIEW: {
3708 d.asyncBufferProcessing(argument,
3710 _("Previewing ..."),
3711 &GuiViewPrivate::previewAndDestroy,
3716 case LFUN_MASTER_BUFFER_UPDATE: {
3717 d.asyncBufferProcessing(argument,
3718 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3720 &GuiViewPrivate::compileAndDestroy,
3725 case LFUN_MASTER_BUFFER_VIEW: {
3726 d.asyncBufferProcessing(argument,
3727 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3729 &GuiViewPrivate::previewAndDestroy,
3730 0, &Buffer::preview);
3733 case LFUN_BUFFER_SWITCH: {
3734 string const file_name = to_utf8(cmd.argument());
3735 if (!FileName::isAbsolute(file_name)) {
3737 dr.setMessage(_("Absolute filename expected."));
3741 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3744 dr.setMessage(_("Document not loaded"));
3748 // Do we open or switch to the buffer in this view ?
3749 if (workArea(*buffer)
3750 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3755 // Look for the buffer in other views
3756 QList<int> const ids = guiApp->viewIds();
3758 for (; i != ids.size(); ++i) {
3759 GuiView & gv = guiApp->view(ids[i]);
3760 if (gv.workArea(*buffer)) {
3762 gv.activateWindow();
3764 gv.setBuffer(buffer);
3769 // If necessary, open a new window as a last resort
3770 if (i == ids.size()) {
3771 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3777 case LFUN_BUFFER_NEXT:
3778 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3781 case LFUN_BUFFER_MOVE_NEXT:
3782 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3785 case LFUN_BUFFER_PREVIOUS:
3786 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3789 case LFUN_BUFFER_MOVE_PREVIOUS:
3790 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3793 case LFUN_COMMAND_EXECUTE: {
3794 command_execute_ = true;
3795 minibuffer_focus_ = true;
3798 case LFUN_DROP_LAYOUTS_CHOICE:
3799 d.layout_->showPopup();
3802 case LFUN_MENU_OPEN:
3803 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3804 menu->exec(QCursor::pos());
3807 case LFUN_FILE_INSERT:
3808 insertLyXFile(cmd.argument());
3811 case LFUN_FILE_INSERT_PLAINTEXT:
3812 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3813 string const fname = to_utf8(cmd.argument());
3814 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3815 dr.setMessage(_("Absolute filename expected."));
3819 FileName filename(fname);
3820 if (fname.empty()) {
3821 FileDialog dlg(qt_("Select file to insert"));
3823 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3824 QStringList(qt_("All Files (*)")));
3826 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3827 dr.setMessage(_("Canceled."));
3831 filename.set(fromqstr(result.second));
3835 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3836 bv->dispatch(new_cmd, dr);
3841 case LFUN_BUFFER_RELOAD: {
3842 LASSERT(doc_buffer, break);
3845 if (!doc_buffer->isClean()) {
3846 docstring const file =
3847 makeDisplayPath(doc_buffer->absFileName(), 20);
3848 if (doc_buffer->notifiesExternalModification()) {
3849 docstring text = _("The current version will be lost. "
3850 "Are you sure you want to load the version on disk "
3851 "of the document %1$s?");
3852 ret = Alert::prompt(_("Reload saved document?"),
3853 bformat(text, file), 1, 1,
3854 _("&Reload"), _("&Cancel"));
3856 docstring text = _("Any changes will be lost. "
3857 "Are you sure you want to revert to the saved version "
3858 "of the document %1$s?");
3859 ret = Alert::prompt(_("Revert to saved document?"),
3860 bformat(text, file), 1, 1,
3861 _("&Revert"), _("&Cancel"));
3866 doc_buffer->markClean();
3867 reloadBuffer(*doc_buffer);
3868 dr.forceBufferUpdate();
3873 case LFUN_BUFFER_WRITE:
3874 LASSERT(doc_buffer, break);
3875 saveBuffer(*doc_buffer);
3878 case LFUN_BUFFER_WRITE_AS:
3879 LASSERT(doc_buffer, break);
3880 renameBuffer(*doc_buffer, cmd.argument());
3883 case LFUN_BUFFER_WRITE_ALL: {
3884 Buffer * first = theBufferList().first();
3887 message(_("Saving all documents..."));
3888 // We cannot use a for loop as the buffer list cycles.
3891 if (!b->isClean()) {
3893 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3895 b = theBufferList().next(b);
3896 } while (b != first);
3897 dr.setMessage(_("All documents saved."));
3901 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3902 LASSERT(doc_buffer, break);
3903 doc_buffer->clearExternalModification();
3906 case LFUN_BUFFER_CLOSE:
3910 case LFUN_BUFFER_CLOSE_ALL:
3914 case LFUN_DEVEL_MODE_TOGGLE:
3915 devel_mode_ = !devel_mode_;
3917 dr.setMessage(_("Developer mode is now enabled."));
3919 dr.setMessage(_("Developer mode is now disabled."));
3922 case LFUN_TOOLBAR_TOGGLE: {
3923 string const name = cmd.getArg(0);
3924 if (GuiToolbar * t = toolbar(name))
3929 case LFUN_TOOLBAR_MOVABLE: {
3930 string const name = cmd.getArg(0);
3932 // toggle (all) toolbars movablility
3933 toolbarsMovable_ = !toolbarsMovable_;
3934 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3935 GuiToolbar * tb = toolbar(ti.name);
3936 if (tb && tb->isMovable() != toolbarsMovable_)
3937 // toggle toolbar movablity if it does not fit lock
3938 // (all) toolbars positions state silent = true, since
3939 // status bar notifications are slow
3942 if (toolbarsMovable_)
3943 dr.setMessage(_("Toolbars unlocked."));
3945 dr.setMessage(_("Toolbars locked."));
3946 } else if (GuiToolbar * t = toolbar(name)) {
3947 // toggle current toolbar movablity
3949 // update lock (all) toolbars positions
3950 updateLockToolbars();
3955 case LFUN_ICON_SIZE: {
3956 QSize size = d.iconSize(cmd.argument());
3958 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
3959 size.width(), size.height()));
3963 case LFUN_DIALOG_UPDATE: {
3964 string const name = to_utf8(cmd.argument());
3965 if (name == "prefs" || name == "document")
3966 updateDialog(name, string());
3967 else if (name == "paragraph")
3968 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
3969 else if (currentBufferView()) {
3970 Inset * inset = currentBufferView()->editedInset(name);
3971 // Can only update a dialog connected to an existing inset
3973 // FIXME: get rid of this indirection; GuiView ask the inset
3974 // if he is kind enough to update itself...
3975 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
3976 //FIXME: pass DispatchResult here?
3977 inset->dispatch(currentBufferView()->cursor(), fr);
3983 case LFUN_DIALOG_TOGGLE: {
3984 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
3985 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
3986 dispatch(FuncRequest(func_code, cmd.argument()), dr);
3990 case LFUN_DIALOG_DISCONNECT_INSET:
3991 disconnectDialog(to_utf8(cmd.argument()));
3994 case LFUN_DIALOG_HIDE: {
3995 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
3999 case LFUN_DIALOG_SHOW: {
4000 string const name = cmd.getArg(0);
4001 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
4003 if (name == "character") {
4004 data = freefont2string();
4006 showDialog("character", data);
4007 } else if (name == "latexlog") {
4008 // getStatus checks that
4009 LATTEST(doc_buffer);
4010 Buffer::LogType type;
4011 string const logfile = doc_buffer->logName(&type);
4013 case Buffer::latexlog:
4016 case Buffer::buildlog:
4020 data += Lexer::quoteString(logfile);
4021 showDialog("log", data);
4022 } else if (name == "vclog") {
4023 // getStatus checks that
4024 LATTEST(doc_buffer);
4025 string const data = "vc " +
4026 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4027 showDialog("log", data);
4028 } else if (name == "symbols") {
4029 data = bv->cursor().getEncoding()->name();
4031 showDialog("symbols", data);
4033 } else if (name == "prefs" && isFullScreen()) {
4034 lfunUiToggle("fullscreen");
4035 showDialog("prefs", data);
4037 showDialog(name, data);
4042 dr.setMessage(cmd.argument());
4045 case LFUN_UI_TOGGLE: {
4046 string arg = cmd.getArg(0);
4047 if (!lfunUiToggle(arg)) {
4048 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4049 dr.setMessage(bformat(msg, from_utf8(arg)));
4051 // Make sure the keyboard focus stays in the work area.
4056 case LFUN_VIEW_SPLIT: {
4057 LASSERT(doc_buffer, break);
4058 string const orientation = cmd.getArg(0);
4059 d.splitter_->setOrientation(orientation == "vertical"
4060 ? Qt::Vertical : Qt::Horizontal);
4061 TabWorkArea * twa = addTabWorkArea();
4062 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4063 setCurrentWorkArea(wa);
4066 case LFUN_TAB_GROUP_CLOSE:
4067 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4068 closeTabWorkArea(twa);
4069 d.current_work_area_ = 0;
4070 twa = d.currentTabWorkArea();
4071 // Switch to the next GuiWorkArea in the found TabWorkArea.
4073 // Make sure the work area is up to date.
4074 setCurrentWorkArea(twa->currentWorkArea());
4076 setCurrentWorkArea(0);
4081 case LFUN_VIEW_CLOSE:
4082 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4083 closeWorkArea(twa->currentWorkArea());
4084 d.current_work_area_ = 0;
4085 twa = d.currentTabWorkArea();
4086 // Switch to the next GuiWorkArea in the found TabWorkArea.
4088 // Make sure the work area is up to date.
4089 setCurrentWorkArea(twa->currentWorkArea());
4091 setCurrentWorkArea(0);
4096 case LFUN_COMPLETION_INLINE:
4097 if (d.current_work_area_)
4098 d.current_work_area_->completer().showInline();
4101 case LFUN_COMPLETION_POPUP:
4102 if (d.current_work_area_)
4103 d.current_work_area_->completer().showPopup();
4108 if (d.current_work_area_)
4109 d.current_work_area_->completer().tab();
4112 case LFUN_COMPLETION_CANCEL:
4113 if (d.current_work_area_) {
4114 if (d.current_work_area_->completer().popupVisible())
4115 d.current_work_area_->completer().hidePopup();
4117 d.current_work_area_->completer().hideInline();
4121 case LFUN_COMPLETION_ACCEPT:
4122 if (d.current_work_area_)
4123 d.current_work_area_->completer().activate();
4126 case LFUN_BUFFER_ZOOM_IN:
4127 case LFUN_BUFFER_ZOOM_OUT:
4128 case LFUN_BUFFER_ZOOM: {
4129 if (cmd.argument().empty()) {
4130 if (cmd.action() == LFUN_BUFFER_ZOOM)
4132 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4137 if (cmd.action() == LFUN_BUFFER_ZOOM)
4138 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4139 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4140 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4142 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4145 // Actual zoom value: default zoom + fractional extra value
4146 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4147 if (zoom < static_cast<int>(zoom_min_))
4150 lyxrc.currentZoom = zoom;
4152 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4153 lyxrc.currentZoom, lyxrc.defaultZoom));
4155 // The global QPixmapCache is used in GuiPainter to cache text
4156 // painting so we must reset it.
4157 QPixmapCache::clear();
4158 guiApp->fontLoader().update();
4159 dr.screenUpdate(Update::Force | Update::FitCursor);
4163 case LFUN_VC_REGISTER:
4164 case LFUN_VC_RENAME:
4166 case LFUN_VC_CHECK_IN:
4167 case LFUN_VC_CHECK_OUT:
4168 case LFUN_VC_REPO_UPDATE:
4169 case LFUN_VC_LOCKING_TOGGLE:
4170 case LFUN_VC_REVERT:
4171 case LFUN_VC_UNDO_LAST:
4172 case LFUN_VC_COMMAND:
4173 case LFUN_VC_COMPARE:
4174 dispatchVC(cmd, dr);
4177 case LFUN_SERVER_GOTO_FILE_ROW:
4178 if(goToFileRow(to_utf8(cmd.argument())))
4179 dr.screenUpdate(Update::Force | Update::FitCursor);
4182 case LFUN_LYX_ACTIVATE:
4186 case LFUN_FORWARD_SEARCH: {
4187 // it seems safe to assume we have a document buffer, since
4188 // getStatus wants one.
4189 LATTEST(doc_buffer);
4190 Buffer const * doc_master = doc_buffer->masterBuffer();
4191 FileName const path(doc_master->temppath());
4192 string const texname = doc_master->isChild(doc_buffer)
4193 ? DocFileName(changeExtension(
4194 doc_buffer->absFileName(),
4195 "tex")).mangledFileName()
4196 : doc_buffer->latexName();
4197 string const fulltexname =
4198 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4199 string const mastername =
4200 removeExtension(doc_master->latexName());
4201 FileName const dviname(addName(path.absFileName(),
4202 addExtension(mastername, "dvi")));
4203 FileName const pdfname(addName(path.absFileName(),
4204 addExtension(mastername, "pdf")));
4205 bool const have_dvi = dviname.exists();
4206 bool const have_pdf = pdfname.exists();
4207 if (!have_dvi && !have_pdf) {
4208 dr.setMessage(_("Please, preview the document first."));
4211 string outname = dviname.onlyFileName();
4212 string command = lyxrc.forward_search_dvi;
4213 if (!have_dvi || (have_pdf &&
4214 pdfname.lastModified() > dviname.lastModified())) {
4215 outname = pdfname.onlyFileName();
4216 command = lyxrc.forward_search_pdf;
4219 DocIterator cur = bv->cursor();
4220 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4221 LYXERR(Debug::ACTION, "Forward search: row:" << row
4223 if (row == -1 || command.empty()) {
4224 dr.setMessage(_("Couldn't proceed."));
4227 string texrow = convert<string>(row);
4229 command = subst(command, "$$n", texrow);
4230 command = subst(command, "$$f", fulltexname);
4231 command = subst(command, "$$t", texname);
4232 command = subst(command, "$$o", outname);
4234 PathChanger p(path);
4236 one.startscript(Systemcall::DontWait, command);
4240 case LFUN_SPELLING_CONTINUOUSLY:
4241 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4242 dr.screenUpdate(Update::Force);
4246 // The LFUN must be for one of BufferView, Buffer or Cursor;
4248 dispatchToBufferView(cmd, dr);
4252 // Part of automatic menu appearance feature.
4253 if (isFullScreen()) {
4254 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4258 // Need to update bv because many LFUNs here might have destroyed it
4259 bv = currentBufferView();
4261 // Clear non-empty selections
4262 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4264 Cursor & cur = bv->cursor();
4265 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4266 cur.clearSelection();
4272 bool GuiView::lfunUiToggle(string const & ui_component)
4274 if (ui_component == "scrollbar") {
4275 // hide() is of no help
4276 if (d.current_work_area_->verticalScrollBarPolicy() ==
4277 Qt::ScrollBarAlwaysOff)
4279 d.current_work_area_->setVerticalScrollBarPolicy(
4280 Qt::ScrollBarAsNeeded);
4282 d.current_work_area_->setVerticalScrollBarPolicy(
4283 Qt::ScrollBarAlwaysOff);
4284 } else if (ui_component == "statusbar") {
4285 statusBar()->setVisible(!statusBar()->isVisible());
4286 } else if (ui_component == "menubar") {
4287 menuBar()->setVisible(!menuBar()->isVisible());
4289 if (ui_component == "frame") {
4291 getContentsMargins(&l, &t, &r, &b);
4292 //are the frames in default state?
4293 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4295 setContentsMargins(-2, -2, -2, -2);
4297 setContentsMargins(0, 0, 0, 0);
4300 if (ui_component == "fullscreen") {
4308 void GuiView::toggleFullScreen()
4310 if (isFullScreen()) {
4311 for (int i = 0; i != d.splitter_->count(); ++i)
4312 d.tabWorkArea(i)->setFullScreen(false);
4313 setContentsMargins(0, 0, 0, 0);
4314 setWindowState(windowState() ^ Qt::WindowFullScreen);
4317 statusBar()->show();
4320 hideDialogs("prefs", 0);
4321 for (int i = 0; i != d.splitter_->count(); ++i)
4322 d.tabWorkArea(i)->setFullScreen(true);
4323 setContentsMargins(-2, -2, -2, -2);
4325 setWindowState(windowState() ^ Qt::WindowFullScreen);
4326 if (lyxrc.full_screen_statusbar)
4327 statusBar()->hide();
4328 if (lyxrc.full_screen_menubar)
4330 if (lyxrc.full_screen_toolbars) {
4331 ToolbarMap::iterator end = d.toolbars_.end();
4332 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4337 // give dialogs like the TOC a chance to adapt
4342 Buffer const * GuiView::updateInset(Inset const * inset)
4347 Buffer const * inset_buffer = &(inset->buffer());
4349 for (int i = 0; i != d.splitter_->count(); ++i) {
4350 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4353 Buffer const * buffer = &(wa->bufferView().buffer());
4354 if (inset_buffer == buffer)
4355 wa->scheduleRedraw(true);
4357 return inset_buffer;
4361 void GuiView::restartCaret()
4363 /* When we move around, or type, it's nice to be able to see
4364 * the caret immediately after the keypress.
4366 if (d.current_work_area_)
4367 d.current_work_area_->startBlinkingCaret();
4369 // Take this occasion to update the other GUI elements.
4375 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4377 if (d.current_work_area_)
4378 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4383 // This list should be kept in sync with the list of insets in
4384 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4385 // dialog should have the same name as the inset.
4386 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4387 // docs in LyXAction.cpp.
4389 char const * const dialognames[] = {
4391 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4392 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4393 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4394 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4395 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4396 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4397 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4398 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4400 char const * const * const end_dialognames =
4401 dialognames + (sizeof(dialognames) / sizeof(char *));
4405 cmpCStr(char const * name) : name_(name) {}
4406 bool operator()(char const * other) {
4407 return strcmp(other, name_) == 0;
4414 bool isValidName(string const & name)
4416 return find_if(dialognames, end_dialognames,
4417 cmpCStr(name.c_str())) != end_dialognames;
4423 void GuiView::resetDialogs()
4425 // Make sure that no LFUN uses any GuiView.
4426 guiApp->setCurrentView(0);
4430 constructToolbars();
4431 guiApp->menus().fillMenuBar(menuBar(), this, false);
4432 d.layout_->updateContents(true);
4433 // Now update controls with current buffer.
4434 guiApp->setCurrentView(this);
4440 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4442 if (!isValidName(name))
4445 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4447 if (it != d.dialogs_.end()) {
4449 it->second->hideView();
4450 return it->second.get();
4453 Dialog * dialog = build(name);
4454 d.dialogs_[name].reset(dialog);
4455 if (lyxrc.allow_geometry_session)
4456 dialog->restoreSession();
4463 void GuiView::showDialog(string const & name, string const & data,
4466 triggerShowDialog(toqstr(name), toqstr(data), inset);
4470 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4476 const string name = fromqstr(qname);
4477 const string data = fromqstr(qdata);
4481 Dialog * dialog = findOrBuild(name, false);
4483 bool const visible = dialog->isVisibleView();
4484 dialog->showData(data);
4485 if (inset && currentBufferView())
4486 currentBufferView()->editInset(name, inset);
4487 // We only set the focus to the new dialog if it was not yet
4488 // visible in order not to change the existing previous behaviour
4490 // activateWindow is needed for floating dockviews
4491 dialog->asQWidget()->raise();
4492 dialog->asQWidget()->activateWindow();
4493 dialog->asQWidget()->setFocus();
4497 catch (ExceptionMessage const & ex) {
4505 bool GuiView::isDialogVisible(string const & name) const
4507 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4508 if (it == d.dialogs_.end())
4510 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4514 void GuiView::hideDialog(string const & name, Inset * inset)
4516 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4517 if (it == d.dialogs_.end())
4521 if (!currentBufferView())
4523 if (inset != currentBufferView()->editedInset(name))
4527 Dialog * const dialog = it->second.get();
4528 if (dialog->isVisibleView())
4530 if (currentBufferView())
4531 currentBufferView()->editInset(name, 0);
4535 void GuiView::disconnectDialog(string const & name)
4537 if (!isValidName(name))
4539 if (currentBufferView())
4540 currentBufferView()->editInset(name, 0);
4544 void GuiView::hideAll() const
4546 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4547 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4549 for(; it != end; ++it)
4550 it->second->hideView();
4554 void GuiView::updateDialogs()
4556 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4557 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4559 for(; it != end; ++it) {
4560 Dialog * dialog = it->second.get();
4562 if (dialog->needBufferOpen() && !documentBufferView())
4563 hideDialog(fromqstr(dialog->name()), 0);
4564 else if (dialog->isVisibleView())
4565 dialog->checkStatus();
4572 Dialog * createDialog(GuiView & lv, string const & name);
4574 // will be replaced by a proper factory...
4575 Dialog * createGuiAbout(GuiView & lv);
4576 Dialog * createGuiBibtex(GuiView & lv);
4577 Dialog * createGuiChanges(GuiView & lv);
4578 Dialog * createGuiCharacter(GuiView & lv);
4579 Dialog * createGuiCitation(GuiView & lv);
4580 Dialog * createGuiCompare(GuiView & lv);
4581 Dialog * createGuiCompareHistory(GuiView & lv);
4582 Dialog * createGuiDelimiter(GuiView & lv);
4583 Dialog * createGuiDocument(GuiView & lv);
4584 Dialog * createGuiErrorList(GuiView & lv);
4585 Dialog * createGuiExternal(GuiView & lv);
4586 Dialog * createGuiGraphics(GuiView & lv);
4587 Dialog * createGuiInclude(GuiView & lv);
4588 Dialog * createGuiIndex(GuiView & lv);
4589 Dialog * createGuiListings(GuiView & lv);
4590 Dialog * createGuiLog(GuiView & lv);
4591 Dialog * createGuiMathMatrix(GuiView & lv);
4592 Dialog * createGuiNote(GuiView & lv);
4593 Dialog * createGuiParagraph(GuiView & lv);
4594 Dialog * createGuiPhantom(GuiView & lv);
4595 Dialog * createGuiPreferences(GuiView & lv);
4596 Dialog * createGuiPrint(GuiView & lv);
4597 Dialog * createGuiPrintindex(GuiView & lv);
4598 Dialog * createGuiRef(GuiView & lv);
4599 Dialog * createGuiSearch(GuiView & lv);
4600 Dialog * createGuiSearchAdv(GuiView & lv);
4601 Dialog * createGuiSendTo(GuiView & lv);
4602 Dialog * createGuiShowFile(GuiView & lv);
4603 Dialog * createGuiSpellchecker(GuiView & lv);
4604 Dialog * createGuiSymbols(GuiView & lv);
4605 Dialog * createGuiTabularCreate(GuiView & lv);
4606 Dialog * createGuiTexInfo(GuiView & lv);
4607 Dialog * createGuiToc(GuiView & lv);
4608 Dialog * createGuiThesaurus(GuiView & lv);
4609 Dialog * createGuiViewSource(GuiView & lv);
4610 Dialog * createGuiWrap(GuiView & lv);
4611 Dialog * createGuiProgressView(GuiView & lv);
4615 Dialog * GuiView::build(string const & name)
4617 LASSERT(isValidName(name), return 0);
4619 Dialog * dialog = createDialog(*this, name);
4623 if (name == "aboutlyx")
4624 return createGuiAbout(*this);
4625 if (name == "bibtex")
4626 return createGuiBibtex(*this);
4627 if (name == "changes")
4628 return createGuiChanges(*this);
4629 if (name == "character")
4630 return createGuiCharacter(*this);
4631 if (name == "citation")
4632 return createGuiCitation(*this);
4633 if (name == "compare")
4634 return createGuiCompare(*this);
4635 if (name == "comparehistory")
4636 return createGuiCompareHistory(*this);
4637 if (name == "document")
4638 return createGuiDocument(*this);
4639 if (name == "errorlist")
4640 return createGuiErrorList(*this);
4641 if (name == "external")
4642 return createGuiExternal(*this);
4644 return createGuiShowFile(*this);
4645 if (name == "findreplace")
4646 return createGuiSearch(*this);
4647 if (name == "findreplaceadv")
4648 return createGuiSearchAdv(*this);
4649 if (name == "graphics")
4650 return createGuiGraphics(*this);
4651 if (name == "include")
4652 return createGuiInclude(*this);
4653 if (name == "index")
4654 return createGuiIndex(*this);
4655 if (name == "index_print")
4656 return createGuiPrintindex(*this);
4657 if (name == "listings")
4658 return createGuiListings(*this);
4660 return createGuiLog(*this);
4661 if (name == "mathdelimiter")
4662 return createGuiDelimiter(*this);
4663 if (name == "mathmatrix")
4664 return createGuiMathMatrix(*this);
4666 return createGuiNote(*this);
4667 if (name == "paragraph")
4668 return createGuiParagraph(*this);
4669 if (name == "phantom")
4670 return createGuiPhantom(*this);
4671 if (name == "prefs")
4672 return createGuiPreferences(*this);
4674 return createGuiRef(*this);
4675 if (name == "sendto")
4676 return createGuiSendTo(*this);
4677 if (name == "spellchecker")
4678 return createGuiSpellchecker(*this);
4679 if (name == "symbols")
4680 return createGuiSymbols(*this);
4681 if (name == "tabularcreate")
4682 return createGuiTabularCreate(*this);
4683 if (name == "texinfo")
4684 return createGuiTexInfo(*this);
4685 if (name == "thesaurus")
4686 return createGuiThesaurus(*this);
4688 return createGuiToc(*this);
4689 if (name == "view-source")
4690 return createGuiViewSource(*this);
4692 return createGuiWrap(*this);
4693 if (name == "progress")
4694 return createGuiProgressView(*this);
4700 SEMenu::SEMenu(QWidget * parent)
4702 QAction * action = addAction(qt_("Disable Shell Escape"));
4703 connect(action, SIGNAL(triggered()),
4704 parent, SLOT(disableShellEscape()));
4708 } // namespace frontend
4711 #include "moc_GuiView.cpp"