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
761 // Save the toolbar private states
762 ToolbarMap::iterator end = d.toolbars_.end();
763 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
764 it->second->saveSession();
765 // Now take care of all other dialogs
766 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
767 for (; it!= d.dialogs_.end(); ++it)
768 it->second->saveSession();
772 bool GuiView::restoreLayout()
775 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
776 // Actual zoom value: default zoom + fractional offset
777 int zoom = lyxrc.defaultZoom * zoom_ratio_;
778 if (zoom < static_cast<int>(zoom_min_))
780 lyxrc.currentZoom = zoom;
781 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
782 settings.beginGroup("views");
783 settings.beginGroup(QString::number(id_));
784 QString const icon_key = "icon_size";
785 if (!settings.contains(icon_key))
788 //code below is skipped when when ~/.config/LyX is (re)created
789 setIconSize(d.iconSize(settings.value(icon_key).toString()));
791 #if defined(Q_WS_X11) || defined(QPA_XCB)
792 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
793 QSize size = settings.value("size", QSize(690, 510)).toSize();
797 // Work-around for bug #6034: the window ends up in an undetermined
798 // state when trying to restore a maximized window when it is
799 // already maximized.
800 if (!(windowState() & Qt::WindowMaximized))
801 if (!restoreGeometry(settings.value("geometry").toByteArray()))
802 setGeometry(50, 50, 690, 510);
804 // Make sure layout is correctly oriented.
805 setLayoutDirection(qApp->layoutDirection());
807 // Allow the toc and view-source dock widget to be restored if needed.
809 if ((dialog = findOrBuild("toc", true)))
810 // see bug 5082. At least setup title and enabled state.
811 // Visibility will be adjusted by restoreState below.
812 dialog->prepareView();
813 if ((dialog = findOrBuild("view-source", true)))
814 dialog->prepareView();
815 if ((dialog = findOrBuild("progress", true)))
816 dialog->prepareView();
818 if (!restoreState(settings.value("layout").toByteArray(), 0))
821 // init the toolbars that have not been restored
822 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
823 Toolbars::Infos::iterator end = guiApp->toolbars().end();
824 for (; cit != end; ++cit) {
825 GuiToolbar * tb = toolbar(cit->name);
826 if (tb && !tb->isRestored())
827 initToolbar(cit->name);
830 // update lock (all) toolbars positions
831 updateLockToolbars();
838 GuiToolbar * GuiView::toolbar(string const & name)
840 ToolbarMap::iterator it = d.toolbars_.find(name);
841 if (it != d.toolbars_.end())
844 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
849 void GuiView::updateLockToolbars()
851 toolbarsMovable_ = false;
852 for (ToolbarInfo const & info : guiApp->toolbars()) {
853 GuiToolbar * tb = toolbar(info.name);
854 if (tb && tb->isMovable())
855 toolbarsMovable_ = true;
860 void GuiView::constructToolbars()
862 ToolbarMap::iterator it = d.toolbars_.begin();
863 for (; it != d.toolbars_.end(); ++it)
867 // I don't like doing this here, but the standard toolbar
868 // destroys this object when it's destroyed itself (vfr)
869 d.layout_ = new LayoutBox(*this);
870 d.stack_widget_->addWidget(d.layout_);
871 d.layout_->move(0,0);
873 // extracts the toolbars from the backend
874 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
875 Toolbars::Infos::iterator end = guiApp->toolbars().end();
876 for (; cit != end; ++cit)
877 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
881 void GuiView::initToolbars()
883 // extracts the toolbars from the backend
884 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
885 Toolbars::Infos::iterator end = guiApp->toolbars().end();
886 for (; cit != end; ++cit)
887 initToolbar(cit->name);
891 void GuiView::initToolbar(string const & name)
893 GuiToolbar * tb = toolbar(name);
896 int const visibility = guiApp->toolbars().defaultVisibility(name);
897 bool newline = !(visibility & Toolbars::SAMEROW);
898 tb->setVisible(false);
899 tb->setVisibility(visibility);
901 if (visibility & Toolbars::TOP) {
903 addToolBarBreak(Qt::TopToolBarArea);
904 addToolBar(Qt::TopToolBarArea, tb);
907 if (visibility & Toolbars::BOTTOM) {
909 addToolBarBreak(Qt::BottomToolBarArea);
910 addToolBar(Qt::BottomToolBarArea, tb);
913 if (visibility & Toolbars::LEFT) {
915 addToolBarBreak(Qt::LeftToolBarArea);
916 addToolBar(Qt::LeftToolBarArea, tb);
919 if (visibility & Toolbars::RIGHT) {
921 addToolBarBreak(Qt::RightToolBarArea);
922 addToolBar(Qt::RightToolBarArea, tb);
925 if (visibility & Toolbars::ON)
926 tb->setVisible(true);
928 tb->setMovable(true);
932 TocModels & GuiView::tocModels()
934 return d.toc_models_;
938 void GuiView::setFocus()
940 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
941 QMainWindow::setFocus();
945 bool GuiView::hasFocus() const
947 if (currentWorkArea())
948 return currentWorkArea()->hasFocus();
949 if (currentMainWorkArea())
950 return currentMainWorkArea()->hasFocus();
951 return d.bg_widget_->hasFocus();
955 void GuiView::focusInEvent(QFocusEvent * e)
957 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
958 QMainWindow::focusInEvent(e);
959 // Make sure guiApp points to the correct view.
960 guiApp->setCurrentView(this);
961 if (currentWorkArea())
962 currentWorkArea()->setFocus();
963 else if (currentMainWorkArea())
964 currentMainWorkArea()->setFocus();
966 d.bg_widget_->setFocus();
970 void GuiView::showEvent(QShowEvent * e)
972 LYXERR(Debug::GUI, "Passed Geometry "
973 << size().height() << "x" << size().width()
974 << "+" << pos().x() << "+" << pos().y());
976 if (d.splitter_->count() == 0)
977 // No work area, switch to the background widget.
981 QMainWindow::showEvent(e);
985 bool GuiView::closeScheduled()
992 bool GuiView::prepareAllBuffersForLogout()
994 Buffer * first = theBufferList().first();
998 // First, iterate over all buffers and ask the users if unsaved
999 // changes should be saved.
1000 // We cannot use a for loop as the buffer list cycles.
1003 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1005 b = theBufferList().next(b);
1006 } while (b != first);
1008 // Next, save session state
1009 // When a view/window was closed before without quitting LyX, there
1010 // are already entries in the lastOpened list.
1011 theSession().lastOpened().clear();
1018 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1019 ** is responsibility of the container (e.g., dialog)
1021 void GuiView::closeEvent(QCloseEvent * close_event)
1023 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1025 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1026 Alert::warning(_("Exit LyX"),
1027 _("LyX could not be closed because documents are being processed by LyX."));
1028 close_event->setAccepted(false);
1032 // If the user pressed the x (so we didn't call closeView
1033 // programmatically), we want to clear all existing entries.
1035 theSession().lastOpened().clear();
1040 // it can happen that this event arrives without selecting the view,
1041 // e.g. when clicking the close button on a background window.
1043 if (!closeWorkAreaAll()) {
1045 close_event->ignore();
1049 // Make sure that nothing will use this to be closed View.
1050 guiApp->unregisterView(this);
1052 if (isFullScreen()) {
1053 // Switch off fullscreen before closing.
1058 // Make sure the timer time out will not trigger a statusbar update.
1059 d.statusbar_timer_.stop();
1061 // Saving fullscreen requires additional tweaks in the toolbar code.
1062 // It wouldn't also work under linux natively.
1063 if (lyxrc.allow_geometry_session) {
1068 close_event->accept();
1072 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1074 if (event->mimeData()->hasUrls())
1076 /// \todo Ask lyx-devel is this is enough:
1077 /// if (event->mimeData()->hasFormat("text/plain"))
1078 /// event->acceptProposedAction();
1082 void GuiView::dropEvent(QDropEvent * event)
1084 QList<QUrl> files = event->mimeData()->urls();
1085 if (files.isEmpty())
1088 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1089 for (int i = 0; i != files.size(); ++i) {
1090 string const file = os::internal_path(fromqstr(
1091 files.at(i).toLocalFile()));
1095 string const ext = support::getExtension(file);
1096 vector<const Format *> found_formats;
1098 // Find all formats that have the correct extension.
1099 vector<const Format *> const & import_formats
1100 = theConverters().importableFormats();
1101 vector<const Format *>::const_iterator it = import_formats.begin();
1102 for (; it != import_formats.end(); ++it)
1103 if ((*it)->hasExtension(ext))
1104 found_formats.push_back(*it);
1107 if (found_formats.size() >= 1) {
1108 if (found_formats.size() > 1) {
1109 //FIXME: show a dialog to choose the correct importable format
1110 LYXERR(Debug::FILES,
1111 "Multiple importable formats found, selecting first");
1113 string const arg = found_formats[0]->name() + " " + file;
1114 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1117 //FIXME: do we have to explicitly check whether it's a lyx file?
1118 LYXERR(Debug::FILES,
1119 "No formats found, trying to open it as a lyx file");
1120 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1122 // add the functions to the queue
1123 guiApp->addToFuncRequestQueue(cmd);
1126 // now process the collected functions. We perform the events
1127 // asynchronously. This prevents potential problems in case the
1128 // BufferView is closed within an event.
1129 guiApp->processFuncRequestQueueAsync();
1133 void GuiView::message(docstring const & str)
1135 if (ForkedProcess::iAmAChild())
1138 // call is moved to GUI-thread by GuiProgress
1139 d.progress_->appendMessage(toqstr(str));
1143 void GuiView::clearMessageText()
1145 message(docstring());
1149 void GuiView::updateStatusBarMessage(QString const & str)
1151 statusBar()->showMessage(str);
1152 d.statusbar_timer_.stop();
1153 d.statusbar_timer_.start(3000);
1157 void GuiView::clearMessage()
1159 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1160 // the hasFocus function mostly returns false, even if the focus is on
1161 // a workarea in this view.
1165 d.statusbar_timer_.stop();
1169 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1171 if (wa != d.current_work_area_
1172 || wa->bufferView().buffer().isInternal())
1174 Buffer const & buf = wa->bufferView().buffer();
1175 // Set the windows title
1176 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1177 if (buf.notifiesExternalModification()) {
1178 title = bformat(_("%1$s (modified externally)"), title);
1179 // If the external modification status has changed, then maybe the status of
1180 // buffer-save has changed too.
1184 title += from_ascii(" - LyX");
1186 setWindowTitle(toqstr(title));
1187 // Sets the path for the window: this is used by OSX to
1188 // allow a context click on the title bar showing a menu
1189 // with the path up to the file
1190 setWindowFilePath(toqstr(buf.absFileName()));
1191 // Tell Qt whether the current document is changed
1192 setWindowModified(!buf.isClean());
1194 if (buf.params().shell_escape)
1195 shell_escape_->show();
1197 shell_escape_->hide();
1199 if (buf.hasReadonlyFlag())
1204 if (buf.lyxvc().inUse()) {
1205 version_control_->show();
1206 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1208 version_control_->hide();
1212 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1214 if (d.current_work_area_)
1215 // disconnect the current work area from all slots
1216 QObject::disconnect(d.current_work_area_, 0, this, 0);
1218 disconnectBufferView();
1219 connectBufferView(wa->bufferView());
1220 connectBuffer(wa->bufferView().buffer());
1221 d.current_work_area_ = wa;
1222 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1223 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1224 QObject::connect(wa, SIGNAL(busy(bool)),
1225 this, SLOT(setBusy(bool)));
1226 // connection of a signal to a signal
1227 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1228 this, SIGNAL(bufferViewChanged()));
1229 Q_EMIT updateWindowTitle(wa);
1230 Q_EMIT bufferViewChanged();
1234 void GuiView::onBufferViewChanged()
1237 // Buffer-dependent dialogs must be updated. This is done here because
1238 // some dialogs require buffer()->text.
1243 void GuiView::on_lastWorkAreaRemoved()
1246 // We already are in a close event. Nothing more to do.
1249 if (d.splitter_->count() > 1)
1250 // We have a splitter so don't close anything.
1253 // Reset and updates the dialogs.
1254 Q_EMIT bufferViewChanged();
1259 if (lyxrc.open_buffers_in_tabs)
1260 // Nothing more to do, the window should stay open.
1263 if (guiApp->viewIds().size() > 1) {
1269 // On Mac we also close the last window because the application stay
1270 // resident in memory. On other platforms we don't close the last
1271 // window because this would quit the application.
1277 void GuiView::updateStatusBar()
1279 // let the user see the explicit message
1280 if (d.statusbar_timer_.isActive())
1287 void GuiView::showMessage()
1291 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1292 if (msg.isEmpty()) {
1293 BufferView const * bv = currentBufferView();
1295 msg = toqstr(bv->cursor().currentState(devel_mode_));
1297 msg = qt_("Welcome to LyX!");
1299 statusBar()->showMessage(msg);
1303 bool GuiView::event(QEvent * e)
1307 // Useful debug code:
1308 //case QEvent::ActivationChange:
1309 //case QEvent::WindowDeactivate:
1310 //case QEvent::Paint:
1311 //case QEvent::Enter:
1312 //case QEvent::Leave:
1313 //case QEvent::HoverEnter:
1314 //case QEvent::HoverLeave:
1315 //case QEvent::HoverMove:
1316 //case QEvent::StatusTip:
1317 //case QEvent::DragEnter:
1318 //case QEvent::DragLeave:
1319 //case QEvent::Drop:
1322 case QEvent::WindowActivate: {
1323 GuiView * old_view = guiApp->currentView();
1324 if (this == old_view) {
1326 return QMainWindow::event(e);
1328 if (old_view && old_view->currentBufferView()) {
1329 // save current selection to the selection buffer to allow
1330 // middle-button paste in this window.
1331 cap::saveSelection(old_view->currentBufferView()->cursor());
1333 guiApp->setCurrentView(this);
1334 if (d.current_work_area_)
1335 on_currentWorkAreaChanged(d.current_work_area_);
1339 return QMainWindow::event(e);
1342 case QEvent::ShortcutOverride: {
1344 if (isFullScreen() && menuBar()->isHidden()) {
1345 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1346 // FIXME: we should also try to detect special LyX shortcut such as
1347 // Alt-P and Alt-M. Right now there is a hack in
1348 // GuiWorkArea::processKeySym() that hides again the menubar for
1350 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1352 return QMainWindow::event(e);
1355 return QMainWindow::event(e);
1359 return QMainWindow::event(e);
1363 void GuiView::resetWindowTitle()
1365 setWindowTitle(qt_("LyX"));
1368 bool GuiView::focusNextPrevChild(bool /*next*/)
1375 bool GuiView::busy() const
1381 void GuiView::setBusy(bool busy)
1383 bool const busy_before = busy_ > 0;
1384 busy ? ++busy_ : --busy_;
1385 if ((busy_ > 0) == busy_before)
1386 // busy state didn't change
1390 QApplication::setOverrideCursor(Qt::WaitCursor);
1393 QApplication::restoreOverrideCursor();
1398 void GuiView::resetCommandExecute()
1400 command_execute_ = false;
1405 double GuiView::pixelRatio() const
1407 #if QT_VERSION >= 0x050000
1408 return qt_scale_factor * devicePixelRatio();
1415 GuiWorkArea * GuiView::workArea(int index)
1417 if (TabWorkArea * twa = d.currentTabWorkArea())
1418 if (index < twa->count())
1419 return twa->workArea(index);
1424 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1426 if (currentWorkArea()
1427 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1428 return currentWorkArea();
1429 if (TabWorkArea * twa = d.currentTabWorkArea())
1430 return twa->workArea(buffer);
1435 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1437 // Automatically create a TabWorkArea if there are none yet.
1438 TabWorkArea * tab_widget = d.splitter_->count()
1439 ? d.currentTabWorkArea() : addTabWorkArea();
1440 return tab_widget->addWorkArea(buffer, *this);
1444 TabWorkArea * GuiView::addTabWorkArea()
1446 TabWorkArea * twa = new TabWorkArea;
1447 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1448 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1449 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1450 this, SLOT(on_lastWorkAreaRemoved()));
1452 d.splitter_->addWidget(twa);
1453 d.stack_widget_->setCurrentWidget(d.splitter_);
1458 GuiWorkArea const * GuiView::currentWorkArea() const
1460 return d.current_work_area_;
1464 GuiWorkArea * GuiView::currentWorkArea()
1466 return d.current_work_area_;
1470 GuiWorkArea const * GuiView::currentMainWorkArea() const
1472 if (!d.currentTabWorkArea())
1474 return d.currentTabWorkArea()->currentWorkArea();
1478 GuiWorkArea * GuiView::currentMainWorkArea()
1480 if (!d.currentTabWorkArea())
1482 return d.currentTabWorkArea()->currentWorkArea();
1486 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1488 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1490 d.current_work_area_ = 0;
1492 Q_EMIT bufferViewChanged();
1496 // FIXME: I've no clue why this is here and why it accesses
1497 // theGuiApp()->currentView, which might be 0 (bug 6464).
1498 // See also 27525 (vfr).
1499 if (theGuiApp()->currentView() == this
1500 && theGuiApp()->currentView()->currentWorkArea() == wa)
1503 if (currentBufferView())
1504 cap::saveSelection(currentBufferView()->cursor());
1506 theGuiApp()->setCurrentView(this);
1507 d.current_work_area_ = wa;
1509 // We need to reset this now, because it will need to be
1510 // right if the tabWorkArea gets reset in the for loop. We
1511 // will change it back if we aren't in that case.
1512 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1513 d.current_main_work_area_ = wa;
1515 for (int i = 0; i != d.splitter_->count(); ++i) {
1516 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1517 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1518 << ", Current main wa: " << currentMainWorkArea());
1523 d.current_main_work_area_ = old_cmwa;
1525 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1526 on_currentWorkAreaChanged(wa);
1527 BufferView & bv = wa->bufferView();
1528 bv.cursor().fixIfBroken();
1530 wa->setUpdatesEnabled(true);
1531 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1535 void GuiView::removeWorkArea(GuiWorkArea * wa)
1537 LASSERT(wa, return);
1538 if (wa == d.current_work_area_) {
1540 disconnectBufferView();
1541 d.current_work_area_ = 0;
1542 d.current_main_work_area_ = 0;
1545 bool found_twa = false;
1546 for (int i = 0; i != d.splitter_->count(); ++i) {
1547 TabWorkArea * twa = d.tabWorkArea(i);
1548 if (twa->removeWorkArea(wa)) {
1549 // Found in this tab group, and deleted the GuiWorkArea.
1551 if (twa->count() != 0) {
1552 if (d.current_work_area_ == 0)
1553 // This means that we are closing the current GuiWorkArea, so
1554 // switch to the next GuiWorkArea in the found TabWorkArea.
1555 setCurrentWorkArea(twa->currentWorkArea());
1557 // No more WorkAreas in this tab group, so delete it.
1564 // It is not a tabbed work area (i.e., the search work area), so it
1565 // should be deleted by other means.
1566 LASSERT(found_twa, return);
1568 if (d.current_work_area_ == 0) {
1569 if (d.splitter_->count() != 0) {
1570 TabWorkArea * twa = d.currentTabWorkArea();
1571 setCurrentWorkArea(twa->currentWorkArea());
1573 // No more work areas, switch to the background widget.
1574 setCurrentWorkArea(0);
1580 LayoutBox * GuiView::getLayoutDialog() const
1586 void GuiView::updateLayoutList()
1589 d.layout_->updateContents(false);
1593 void GuiView::updateToolbars()
1595 ToolbarMap::iterator end = d.toolbars_.end();
1596 if (d.current_work_area_) {
1598 if (d.current_work_area_->bufferView().cursor().inMathed()
1599 && !d.current_work_area_->bufferView().cursor().inRegexped())
1600 context |= Toolbars::MATH;
1601 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1602 context |= Toolbars::TABLE;
1603 if (currentBufferView()->buffer().areChangesPresent()
1604 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1605 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1606 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1607 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1608 context |= Toolbars::REVIEW;
1609 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1610 context |= Toolbars::MATHMACROTEMPLATE;
1611 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1612 context |= Toolbars::IPA;
1613 if (command_execute_)
1614 context |= Toolbars::MINIBUFFER;
1615 if (minibuffer_focus_) {
1616 context |= Toolbars::MINIBUFFER_FOCUS;
1617 minibuffer_focus_ = false;
1620 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1621 it->second->update(context);
1623 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1624 it->second->update();
1628 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1630 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1631 LASSERT(newBuffer, return);
1633 GuiWorkArea * wa = workArea(*newBuffer);
1636 newBuffer->masterBuffer()->updateBuffer();
1638 wa = addWorkArea(*newBuffer);
1639 // scroll to the position when the BufferView was last closed
1640 if (lyxrc.use_lastfilepos) {
1641 LastFilePosSection::FilePos filepos =
1642 theSession().lastFilePos().load(newBuffer->fileName());
1643 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1646 //Disconnect the old buffer...there's no new one.
1649 connectBuffer(*newBuffer);
1650 connectBufferView(wa->bufferView());
1652 setCurrentWorkArea(wa);
1656 void GuiView::connectBuffer(Buffer & buf)
1658 buf.setGuiDelegate(this);
1662 void GuiView::disconnectBuffer()
1664 if (d.current_work_area_)
1665 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1669 void GuiView::connectBufferView(BufferView & bv)
1671 bv.setGuiDelegate(this);
1675 void GuiView::disconnectBufferView()
1677 if (d.current_work_area_)
1678 d.current_work_area_->bufferView().setGuiDelegate(0);
1682 void GuiView::errors(string const & error_type, bool from_master)
1684 BufferView const * const bv = currentBufferView();
1688 #if EXPORT_in_THREAD
1689 // We are called with from_master == false by default, so we
1690 // have to figure out whether that is the case or not.
1691 ErrorList & el = bv->buffer().errorList(error_type);
1693 el = bv->buffer().masterBuffer()->errorList(error_type);
1697 ErrorList const & el = from_master ?
1698 bv->buffer().masterBuffer()->errorList(error_type) :
1699 bv->buffer().errorList(error_type);
1705 string data = error_type;
1707 data = "from_master|" + error_type;
1708 showDialog("errorlist", data);
1712 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1714 d.toc_models_.updateItem(toqstr(type), dit);
1718 void GuiView::structureChanged()
1720 // This is called from the Buffer, which has no way to ensure that cursors
1721 // in BufferView remain valid.
1722 if (documentBufferView())
1723 documentBufferView()->cursor().sanitize();
1724 // FIXME: This is slightly expensive, though less than the tocBackend update
1725 // (#9880). This also resets the view in the Toc Widget (#6675).
1726 d.toc_models_.reset(documentBufferView());
1727 // Navigator needs more than a simple update in this case. It needs to be
1729 updateDialog("toc", "");
1733 void GuiView::updateDialog(string const & name, string const & data)
1735 if (!isDialogVisible(name))
1738 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1739 if (it == d.dialogs_.end())
1742 Dialog * const dialog = it->second.get();
1743 if (dialog->isVisibleView())
1744 dialog->initialiseParams(data);
1748 BufferView * GuiView::documentBufferView()
1750 return currentMainWorkArea()
1751 ? ¤tMainWorkArea()->bufferView()
1756 BufferView const * GuiView::documentBufferView() const
1758 return currentMainWorkArea()
1759 ? ¤tMainWorkArea()->bufferView()
1764 BufferView * GuiView::currentBufferView()
1766 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1770 BufferView const * GuiView::currentBufferView() const
1772 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1776 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1777 Buffer const * orig, Buffer * clone)
1779 bool const success = clone->autoSave();
1781 busyBuffers.remove(orig);
1783 ? _("Automatic save done.")
1784 : _("Automatic save failed!");
1788 void GuiView::autoSave()
1790 LYXERR(Debug::INFO, "Running autoSave()");
1792 Buffer * buffer = documentBufferView()
1793 ? &documentBufferView()->buffer() : 0;
1795 resetAutosaveTimers();
1799 GuiViewPrivate::busyBuffers.insert(buffer);
1800 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1801 buffer, buffer->cloneBufferOnly());
1802 d.autosave_watcher_.setFuture(f);
1803 resetAutosaveTimers();
1807 void GuiView::resetAutosaveTimers()
1810 d.autosave_timeout_.restart();
1814 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1817 Buffer * buf = currentBufferView()
1818 ? ¤tBufferView()->buffer() : 0;
1819 Buffer * doc_buffer = documentBufferView()
1820 ? &(documentBufferView()->buffer()) : 0;
1823 /* In LyX/Mac, when a dialog is open, the menus of the
1824 application can still be accessed without giving focus to
1825 the main window. In this case, we want to disable the menu
1826 entries that are buffer-related.
1827 This code must not be used on Linux and Windows, since it
1828 would disable buffer-related entries when hovering over the
1829 menu (see bug #9574).
1831 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1837 // Check whether we need a buffer
1838 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1839 // no, exit directly
1840 flag.message(from_utf8(N_("Command not allowed with"
1841 "out any document open")));
1842 flag.setEnabled(false);
1846 if (cmd.origin() == FuncRequest::TOC) {
1847 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1848 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1849 flag.setEnabled(false);
1853 switch(cmd.action()) {
1854 case LFUN_BUFFER_IMPORT:
1857 case LFUN_MASTER_BUFFER_UPDATE:
1858 case LFUN_MASTER_BUFFER_VIEW:
1860 && (doc_buffer->parent() != 0
1861 || doc_buffer->hasChildren())
1862 && !d.processing_thread_watcher_.isRunning();
1865 case LFUN_BUFFER_UPDATE:
1866 case LFUN_BUFFER_VIEW: {
1867 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1871 string format = to_utf8(cmd.argument());
1872 if (cmd.argument().empty())
1873 format = doc_buffer->params().getDefaultOutputFormat();
1874 enable = doc_buffer->params().isExportable(format, true);
1878 case LFUN_BUFFER_RELOAD:
1879 enable = doc_buffer && !doc_buffer->isUnnamed()
1880 && doc_buffer->fileName().exists() && !doc_buffer->isClean();
1883 case LFUN_BUFFER_CHILD_OPEN:
1884 enable = doc_buffer != 0;
1887 case LFUN_BUFFER_WRITE:
1888 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1891 //FIXME: This LFUN should be moved to GuiApplication.
1892 case LFUN_BUFFER_WRITE_ALL: {
1893 // We enable the command only if there are some modified buffers
1894 Buffer * first = theBufferList().first();
1899 // We cannot use a for loop as the buffer list is a cycle.
1901 if (!b->isClean()) {
1905 b = theBufferList().next(b);
1906 } while (b != first);
1910 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1911 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1914 case LFUN_BUFFER_WRITE_AS:
1915 case LFUN_BUFFER_EXPORT_AS:
1916 enable = doc_buffer != 0;
1919 case LFUN_BUFFER_CLOSE:
1920 case LFUN_VIEW_CLOSE:
1921 enable = doc_buffer != 0;
1924 case LFUN_BUFFER_CLOSE_ALL:
1925 enable = theBufferList().last() != theBufferList().first();
1928 case LFUN_VIEW_SPLIT:
1929 if (cmd.getArg(0) == "vertical")
1930 enable = doc_buffer && (d.splitter_->count() == 1 ||
1931 d.splitter_->orientation() == Qt::Vertical);
1933 enable = doc_buffer && (d.splitter_->count() == 1 ||
1934 d.splitter_->orientation() == Qt::Horizontal);
1937 case LFUN_TAB_GROUP_CLOSE:
1938 enable = d.tabWorkAreaCount() > 1;
1941 case LFUN_DEVEL_MODE_TOGGLE:
1942 flag.setOnOff(devel_mode_);
1945 case LFUN_TOOLBAR_TOGGLE: {
1946 string const name = cmd.getArg(0);
1947 if (GuiToolbar * t = toolbar(name))
1948 flag.setOnOff(t->isVisible());
1951 docstring const msg =
1952 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1958 case LFUN_TOOLBAR_MOVABLE: {
1959 string const name = cmd.getArg(0);
1960 // use negation since locked == !movable
1962 // toolbar name * locks all toolbars
1963 flag.setOnOff(!toolbarsMovable_);
1964 else if (GuiToolbar * t = toolbar(name))
1965 flag.setOnOff(!(t->isMovable()));
1968 docstring const msg =
1969 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1975 case LFUN_ICON_SIZE:
1976 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
1979 case LFUN_DROP_LAYOUTS_CHOICE:
1983 case LFUN_UI_TOGGLE:
1984 flag.setOnOff(isFullScreen());
1987 case LFUN_DIALOG_DISCONNECT_INSET:
1990 case LFUN_DIALOG_HIDE:
1991 // FIXME: should we check if the dialog is shown?
1994 case LFUN_DIALOG_TOGGLE:
1995 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
1998 case LFUN_DIALOG_SHOW: {
1999 string const name = cmd.getArg(0);
2001 enable = name == "aboutlyx"
2002 || name == "file" //FIXME: should be removed.
2004 || name == "texinfo"
2005 || name == "progress"
2006 || name == "compare";
2007 else if (name == "character" || name == "symbols"
2008 || name == "mathdelimiter" || name == "mathmatrix") {
2009 if (!buf || buf->isReadonly())
2012 Cursor const & cur = currentBufferView()->cursor();
2013 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2016 else if (name == "latexlog")
2017 enable = FileName(doc_buffer->logName()).isReadableFile();
2018 else if (name == "spellchecker")
2019 enable = theSpellChecker()
2020 && !doc_buffer->isReadonly()
2021 && !doc_buffer->text().empty();
2022 else if (name == "vclog")
2023 enable = doc_buffer->lyxvc().inUse();
2027 case LFUN_DIALOG_UPDATE: {
2028 string const name = cmd.getArg(0);
2030 enable = name == "prefs";
2034 case LFUN_COMMAND_EXECUTE:
2036 case LFUN_MENU_OPEN:
2037 // Nothing to check.
2040 case LFUN_COMPLETION_INLINE:
2041 if (!d.current_work_area_
2042 || !d.current_work_area_->completer().inlinePossible(
2043 currentBufferView()->cursor()))
2047 case LFUN_COMPLETION_POPUP:
2048 if (!d.current_work_area_
2049 || !d.current_work_area_->completer().popupPossible(
2050 currentBufferView()->cursor()))
2055 if (!d.current_work_area_
2056 || !d.current_work_area_->completer().inlinePossible(
2057 currentBufferView()->cursor()))
2061 case LFUN_COMPLETION_ACCEPT:
2062 if (!d.current_work_area_
2063 || (!d.current_work_area_->completer().popupVisible()
2064 && !d.current_work_area_->completer().inlineVisible()
2065 && !d.current_work_area_->completer().completionAvailable()))
2069 case LFUN_COMPLETION_CANCEL:
2070 if (!d.current_work_area_
2071 || (!d.current_work_area_->completer().popupVisible()
2072 && !d.current_work_area_->completer().inlineVisible()))
2076 case LFUN_BUFFER_ZOOM_OUT:
2077 case LFUN_BUFFER_ZOOM_IN: {
2078 // only diff between these two is that the default for ZOOM_OUT
2080 bool const neg_zoom =
2081 convert<int>(cmd.argument()) < 0 ||
2082 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2083 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2084 docstring const msg =
2085 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2089 enable = doc_buffer;
2093 case LFUN_BUFFER_ZOOM: {
2094 bool const less_than_min_zoom =
2095 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2096 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2097 docstring const msg =
2098 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2103 enable = doc_buffer;
2107 case LFUN_BUFFER_MOVE_NEXT:
2108 case LFUN_BUFFER_MOVE_PREVIOUS:
2109 // we do not cycle when moving
2110 case LFUN_BUFFER_NEXT:
2111 case LFUN_BUFFER_PREVIOUS:
2112 // because we cycle, it doesn't matter whether on first or last
2113 enable = (d.currentTabWorkArea()->count() > 1);
2115 case LFUN_BUFFER_SWITCH:
2116 // toggle on the current buffer, but do not toggle off
2117 // the other ones (is that a good idea?)
2119 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2120 flag.setOnOff(true);
2123 case LFUN_VC_REGISTER:
2124 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2126 case LFUN_VC_RENAME:
2127 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2130 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2132 case LFUN_VC_CHECK_IN:
2133 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2135 case LFUN_VC_CHECK_OUT:
2136 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2138 case LFUN_VC_LOCKING_TOGGLE:
2139 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2140 && doc_buffer->lyxvc().lockingToggleEnabled();
2141 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2143 case LFUN_VC_REVERT:
2144 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2145 && !doc_buffer->hasReadonlyFlag();
2147 case LFUN_VC_UNDO_LAST:
2148 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2150 case LFUN_VC_REPO_UPDATE:
2151 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2153 case LFUN_VC_COMMAND: {
2154 if (cmd.argument().empty())
2156 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2160 case LFUN_VC_COMPARE:
2161 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2164 case LFUN_SERVER_GOTO_FILE_ROW:
2165 case LFUN_LYX_ACTIVATE:
2167 case LFUN_FORWARD_SEARCH:
2168 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2171 case LFUN_FILE_INSERT_PLAINTEXT:
2172 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2173 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2176 case LFUN_SPELLING_CONTINUOUSLY:
2177 flag.setOnOff(lyxrc.spellcheck_continuously);
2185 flag.setEnabled(false);
2191 static FileName selectTemplateFile()
2193 FileDialog dlg(qt_("Select template file"));
2194 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2195 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2197 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2198 QStringList(qt_("LyX Documents (*.lyx)")));
2200 if (result.first == FileDialog::Later)
2202 if (result.second.isEmpty())
2204 return FileName(fromqstr(result.second));
2208 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2212 Buffer * newBuffer = 0;
2214 newBuffer = checkAndLoadLyXFile(filename);
2215 } catch (ExceptionMessage const & e) {
2222 message(_("Document not loaded."));
2226 setBuffer(newBuffer);
2227 newBuffer->errors("Parse");
2230 theSession().lastFiles().add(filename);
2236 void GuiView::openDocument(string const & fname)
2238 string initpath = lyxrc.document_path;
2240 if (documentBufferView()) {
2241 string const trypath = documentBufferView()->buffer().filePath();
2242 // If directory is writeable, use this as default.
2243 if (FileName(trypath).isDirWritable())
2249 if (fname.empty()) {
2250 FileDialog dlg(qt_("Select document to open"));
2251 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2252 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2254 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2255 FileDialog::Result result =
2256 dlg.open(toqstr(initpath), filter);
2258 if (result.first == FileDialog::Later)
2261 filename = fromqstr(result.second);
2263 // check selected filename
2264 if (filename.empty()) {
2265 message(_("Canceled."));
2271 // get absolute path of file and add ".lyx" to the filename if
2273 FileName const fullname =
2274 fileSearch(string(), filename, "lyx", support::may_not_exist);
2275 if (!fullname.empty())
2276 filename = fullname.absFileName();
2278 if (!fullname.onlyPath().isDirectory()) {
2279 Alert::warning(_("Invalid filename"),
2280 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2281 from_utf8(fullname.absFileName())));
2285 // if the file doesn't exist and isn't already open (bug 6645),
2286 // let the user create one
2287 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2288 !LyXVC::file_not_found_hook(fullname)) {
2289 // the user specifically chose this name. Believe him.
2290 Buffer * const b = newFile(filename, string(), true);
2296 docstring const disp_fn = makeDisplayPath(filename);
2297 message(bformat(_("Opening document %1$s..."), disp_fn));
2300 Buffer * buf = loadDocument(fullname);
2302 str2 = bformat(_("Document %1$s opened."), disp_fn);
2303 if (buf->lyxvc().inUse())
2304 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2305 " " + _("Version control detected.");
2307 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2312 // FIXME: clean that
2313 static bool import(GuiView * lv, FileName const & filename,
2314 string const & format, ErrorList & errorList)
2316 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2318 string loader_format;
2319 vector<string> loaders = theConverters().loaders();
2320 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2321 vector<string>::const_iterator it = loaders.begin();
2322 vector<string>::const_iterator en = loaders.end();
2323 for (; it != en; ++it) {
2324 if (!theConverters().isReachable(format, *it))
2327 string const tofile =
2328 support::changeExtension(filename.absFileName(),
2329 theFormats().extension(*it));
2330 if (!theConverters().convert(0, filename, FileName(tofile),
2331 filename, format, *it, errorList))
2333 loader_format = *it;
2336 if (loader_format.empty()) {
2337 frontend::Alert::error(_("Couldn't import file"),
2338 bformat(_("No information for importing the format %1$s."),
2339 theFormats().prettyName(format)));
2343 loader_format = format;
2345 if (loader_format == "lyx") {
2346 Buffer * buf = lv->loadDocument(lyxfile);
2350 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2354 bool as_paragraphs = loader_format == "textparagraph";
2355 string filename2 = (loader_format == format) ? filename.absFileName()
2356 : support::changeExtension(filename.absFileName(),
2357 theFormats().extension(loader_format));
2358 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2360 guiApp->setCurrentView(lv);
2361 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2368 void GuiView::importDocument(string const & argument)
2371 string filename = split(argument, format, ' ');
2373 LYXERR(Debug::INFO, format << " file: " << filename);
2375 // need user interaction
2376 if (filename.empty()) {
2377 string initpath = lyxrc.document_path;
2378 if (documentBufferView()) {
2379 string const trypath = documentBufferView()->buffer().filePath();
2380 // If directory is writeable, use this as default.
2381 if (FileName(trypath).isDirWritable())
2385 docstring const text = bformat(_("Select %1$s file to import"),
2386 theFormats().prettyName(format));
2388 FileDialog dlg(toqstr(text));
2389 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2390 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2392 docstring filter = theFormats().prettyName(format);
2395 filter += from_utf8(theFormats().extensions(format));
2398 FileDialog::Result result =
2399 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2401 if (result.first == FileDialog::Later)
2404 filename = fromqstr(result.second);
2406 // check selected filename
2407 if (filename.empty())
2408 message(_("Canceled."));
2411 if (filename.empty())
2414 // get absolute path of file
2415 FileName const fullname(support::makeAbsPath(filename));
2417 // Can happen if the user entered a path into the dialog
2419 if (fullname.onlyFileName().empty()) {
2420 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2421 "Aborting import."),
2422 from_utf8(fullname.absFileName()));
2423 frontend::Alert::error(_("File name error"), msg);
2424 message(_("Canceled."));
2429 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2431 // Check if the document already is open
2432 Buffer * buf = theBufferList().getBuffer(lyxfile);
2435 if (!closeBuffer()) {
2436 message(_("Canceled."));
2441 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2443 // if the file exists already, and we didn't do
2444 // -i lyx thefile.lyx, warn
2445 if (lyxfile.exists() && fullname != lyxfile) {
2447 docstring text = bformat(_("The document %1$s already exists.\n\n"
2448 "Do you want to overwrite that document?"), displaypath);
2449 int const ret = Alert::prompt(_("Overwrite document?"),
2450 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2453 message(_("Canceled."));
2458 message(bformat(_("Importing %1$s..."), displaypath));
2459 ErrorList errorList;
2460 if (import(this, fullname, format, errorList))
2461 message(_("imported."));
2463 message(_("file not imported!"));
2465 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2469 void GuiView::newDocument(string const & filename, bool from_template)
2471 FileName initpath(lyxrc.document_path);
2472 if (documentBufferView()) {
2473 FileName const trypath(documentBufferView()->buffer().filePath());
2474 // If directory is writeable, use this as default.
2475 if (trypath.isDirWritable())
2479 string templatefile;
2480 if (from_template) {
2481 templatefile = selectTemplateFile().absFileName();
2482 if (templatefile.empty())
2487 if (filename.empty())
2488 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2490 b = newFile(filename, templatefile, true);
2495 // If no new document could be created, it is unsure
2496 // whether there is a valid BufferView.
2497 if (currentBufferView())
2498 // Ensure the cursor is correctly positioned on screen.
2499 currentBufferView()->showCursor();
2503 void GuiView::insertLyXFile(docstring const & fname)
2505 BufferView * bv = documentBufferView();
2510 FileName filename(to_utf8(fname));
2511 if (filename.empty()) {
2512 // Launch a file browser
2514 string initpath = lyxrc.document_path;
2515 string const trypath = bv->buffer().filePath();
2516 // If directory is writeable, use this as default.
2517 if (FileName(trypath).isDirWritable())
2521 FileDialog dlg(qt_("Select LyX document to insert"));
2522 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2523 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2525 FileDialog::Result result = dlg.open(toqstr(initpath),
2526 QStringList(qt_("LyX Documents (*.lyx)")));
2528 if (result.first == FileDialog::Later)
2532 filename.set(fromqstr(result.second));
2534 // check selected filename
2535 if (filename.empty()) {
2536 // emit message signal.
2537 message(_("Canceled."));
2542 bv->insertLyXFile(filename);
2543 bv->buffer().errors("Parse");
2547 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2549 FileName fname = b.fileName();
2550 FileName const oldname = fname;
2552 if (!newname.empty()) {
2554 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2556 // Switch to this Buffer.
2559 // No argument? Ask user through dialog.
2561 FileDialog dlg(qt_("Choose a filename to save document as"));
2562 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2563 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2565 if (!isLyXFileName(fname.absFileName()))
2566 fname.changeExtension(".lyx");
2568 FileDialog::Result result =
2569 dlg.save(toqstr(fname.onlyPath().absFileName()),
2570 QStringList(qt_("LyX Documents (*.lyx)")),
2571 toqstr(fname.onlyFileName()));
2573 if (result.first == FileDialog::Later)
2576 fname.set(fromqstr(result.second));
2581 if (!isLyXFileName(fname.absFileName()))
2582 fname.changeExtension(".lyx");
2585 // fname is now the new Buffer location.
2587 // if there is already a Buffer open with this name, we do not want
2588 // to have another one. (the second test makes sure we're not just
2589 // trying to overwrite ourselves, which is fine.)
2590 if (theBufferList().exists(fname) && fname != oldname
2591 && theBufferList().getBuffer(fname) != &b) {
2592 docstring const text =
2593 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2594 "Please close it before attempting to overwrite it.\n"
2595 "Do you want to choose a new filename?"),
2596 from_utf8(fname.absFileName()));
2597 int const ret = Alert::prompt(_("Chosen File Already Open"),
2598 text, 0, 1, _("&Rename"), _("&Cancel"));
2600 case 0: return renameBuffer(b, docstring(), kind);
2601 case 1: return false;
2606 bool const existsLocal = fname.exists();
2607 bool const existsInVC = LyXVC::fileInVC(fname);
2608 if (existsLocal || existsInVC) {
2609 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2610 if (kind != LV_WRITE_AS && existsInVC) {
2611 // renaming to a name that is already in VC
2613 docstring text = bformat(_("The document %1$s "
2614 "is already registered.\n\n"
2615 "Do you want to choose a new name?"),
2617 docstring const title = (kind == LV_VC_RENAME) ?
2618 _("Rename document?") : _("Copy document?");
2619 docstring const button = (kind == LV_VC_RENAME) ?
2620 _("&Rename") : _("&Copy");
2621 int const ret = Alert::prompt(title, text, 0, 1,
2622 button, _("&Cancel"));
2624 case 0: return renameBuffer(b, docstring(), kind);
2625 case 1: return false;
2630 docstring text = bformat(_("The document %1$s "
2631 "already exists.\n\n"
2632 "Do you want to overwrite that document?"),
2634 int const ret = Alert::prompt(_("Overwrite document?"),
2635 text, 0, 2, _("&Overwrite"),
2636 _("&Rename"), _("&Cancel"));
2639 case 1: return renameBuffer(b, docstring(), kind);
2640 case 2: return false;
2646 case LV_VC_RENAME: {
2647 string msg = b.lyxvc().rename(fname);
2650 message(from_utf8(msg));
2654 string msg = b.lyxvc().copy(fname);
2657 message(from_utf8(msg));
2663 // LyXVC created the file already in case of LV_VC_RENAME or
2664 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2665 // relative paths of included stuff right if we moved e.g. from
2666 // /a/b.lyx to /a/c/b.lyx.
2668 bool const saved = saveBuffer(b, fname);
2675 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2677 FileName fname = b.fileName();
2679 FileDialog dlg(qt_("Choose a filename to export the document as"));
2680 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2683 QString const anyformat = qt_("Guess from extension (*.*)");
2686 vector<Format const *> export_formats;
2687 for (Format const & f : theFormats())
2688 if (f.documentFormat())
2689 export_formats.push_back(&f);
2690 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2691 map<QString, string> fmap;
2694 for (Format const * f : export_formats) {
2695 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2696 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2698 from_ascii(f->extension())));
2699 types << loc_filter;
2700 fmap[loc_filter] = f->name();
2701 if (from_ascii(f->name()) == iformat) {
2702 filter = loc_filter;
2703 ext = f->extension();
2706 string ofname = fname.onlyFileName();
2708 ofname = support::changeExtension(ofname, ext);
2709 FileDialog::Result result =
2710 dlg.save(toqstr(fname.onlyPath().absFileName()),
2714 if (result.first != FileDialog::Chosen)
2718 fname.set(fromqstr(result.second));
2719 if (filter == anyformat)
2720 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2722 fmt_name = fmap[filter];
2723 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2724 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2726 if (fmt_name.empty() || fname.empty())
2729 // fname is now the new Buffer location.
2730 if (FileName(fname).exists()) {
2731 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2732 docstring text = bformat(_("The document %1$s already "
2733 "exists.\n\nDo you want to "
2734 "overwrite that document?"),
2736 int const ret = Alert::prompt(_("Overwrite document?"),
2737 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2740 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2741 case 2: return false;
2745 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2748 return dr.dispatched();
2752 bool GuiView::saveBuffer(Buffer & b)
2754 return saveBuffer(b, FileName());
2758 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2760 if (workArea(b) && workArea(b)->inDialogMode())
2763 if (fn.empty() && b.isUnnamed())
2764 return renameBuffer(b, docstring());
2766 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2768 theSession().lastFiles().add(b.fileName());
2772 // Switch to this Buffer.
2775 // FIXME: we don't tell the user *WHY* the save failed !!
2776 docstring const file = makeDisplayPath(b.absFileName(), 30);
2777 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2778 "Do you want to rename the document and "
2779 "try again?"), file);
2780 int const ret = Alert::prompt(_("Rename and save?"),
2781 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2784 if (!renameBuffer(b, docstring()))
2793 return saveBuffer(b, fn);
2797 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2799 return closeWorkArea(wa, false);
2803 // We only want to close the buffer if it is not visible in other workareas
2804 // of the same view, nor in other views, and if this is not a child
2805 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2807 Buffer & buf = wa->bufferView().buffer();
2809 bool last_wa = d.countWorkAreasOf(buf) == 1
2810 && !inOtherView(buf) && !buf.parent();
2812 bool close_buffer = last_wa;
2815 if (lyxrc.close_buffer_with_last_view == "yes")
2817 else if (lyxrc.close_buffer_with_last_view == "no")
2818 close_buffer = false;
2821 if (buf.isUnnamed())
2822 file = from_utf8(buf.fileName().onlyFileName());
2824 file = buf.fileName().displayName(30);
2825 docstring const text = bformat(
2826 _("Last view on document %1$s is being closed.\n"
2827 "Would you like to close or hide the document?\n"
2829 "Hidden documents can be displayed back through\n"
2830 "the menu: View->Hidden->...\n"
2832 "To remove this question, set your preference in:\n"
2833 " Tools->Preferences->Look&Feel->UserInterface\n"
2835 int ret = Alert::prompt(_("Close or hide document?"),
2836 text, 0, 1, _("&Close"), _("&Hide"));
2837 close_buffer = (ret == 0);
2841 return closeWorkArea(wa, close_buffer);
2845 bool GuiView::closeBuffer()
2847 GuiWorkArea * wa = currentMainWorkArea();
2848 // coverity complained about this
2849 // it seems unnecessary, but perhaps is worth the check
2850 LASSERT(wa, return false);
2852 setCurrentWorkArea(wa);
2853 Buffer & buf = wa->bufferView().buffer();
2854 return closeWorkArea(wa, !buf.parent());
2858 void GuiView::writeSession() const {
2859 GuiWorkArea const * active_wa = currentMainWorkArea();
2860 for (int i = 0; i < d.splitter_->count(); ++i) {
2861 TabWorkArea * twa = d.tabWorkArea(i);
2862 for (int j = 0; j < twa->count(); ++j) {
2863 GuiWorkArea * wa = twa->workArea(j);
2864 Buffer & buf = wa->bufferView().buffer();
2865 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2871 bool GuiView::closeBufferAll()
2873 // Close the workareas in all other views
2874 QList<int> const ids = guiApp->viewIds();
2875 for (int i = 0; i != ids.size(); ++i) {
2876 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2880 // Close our own workareas
2881 if (!closeWorkAreaAll())
2884 // Now close the hidden buffers. We prevent hidden buffers from being
2885 // dirty, so we can just close them.
2886 theBufferList().closeAll();
2891 bool GuiView::closeWorkAreaAll()
2893 setCurrentWorkArea(currentMainWorkArea());
2895 // We might be in a situation that there is still a tabWorkArea, but
2896 // there are no tabs anymore. This can happen when we get here after a
2897 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2898 // many TabWorkArea's have no documents anymore.
2901 // We have to call count() each time, because it can happen that
2902 // more than one splitter will disappear in one iteration (bug 5998).
2903 while (d.splitter_->count() > empty_twa) {
2904 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2906 if (twa->count() == 0)
2909 setCurrentWorkArea(twa->currentWorkArea());
2910 if (!closeTabWorkArea(twa))
2918 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2923 Buffer & buf = wa->bufferView().buffer();
2925 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2926 Alert::warning(_("Close document"),
2927 _("Document could not be closed because it is being processed by LyX."));
2932 return closeBuffer(buf);
2934 if (!inMultiTabs(wa))
2935 if (!saveBufferIfNeeded(buf, true))
2943 bool GuiView::closeBuffer(Buffer & buf)
2945 // If we are in a close_event all children will be closed in some time,
2946 // so no need to do it here. This will ensure that the children end up
2947 // in the session file in the correct order. If we close the master
2948 // buffer, we can close or release the child buffers here too.
2949 bool success = true;
2951 ListOfBuffers clist = buf.getChildren();
2952 ListOfBuffers::const_iterator it = clist.begin();
2953 ListOfBuffers::const_iterator const bend = clist.end();
2954 for (; it != bend; ++it) {
2955 Buffer * child_buf = *it;
2956 if (theBufferList().isOthersChild(&buf, child_buf)) {
2957 child_buf->setParent(0);
2961 // FIXME: should we look in other tabworkareas?
2962 // ANSWER: I don't think so. I've tested, and if the child is
2963 // open in some other window, it closes without a problem.
2964 GuiWorkArea * child_wa = workArea(*child_buf);
2966 success = closeWorkArea(child_wa, true);
2970 // In this case the child buffer is open but hidden.
2971 // It therefore should not (MUST NOT) be dirty!
2972 LATTEST(child_buf->isClean());
2973 theBufferList().release(child_buf);
2978 // goto bookmark to update bookmark pit.
2979 // FIXME: we should update only the bookmarks related to this buffer!
2980 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
2981 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
2982 guiApp->gotoBookmark(i+1, false, false);
2984 if (saveBufferIfNeeded(buf, false)) {
2985 buf.removeAutosaveFile();
2986 theBufferList().release(&buf);
2990 // open all children again to avoid a crash because of dangling
2991 // pointers (bug 6603)
2997 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
2999 while (twa == d.currentTabWorkArea()) {
3000 twa->setCurrentIndex(twa->count() - 1);
3002 GuiWorkArea * wa = twa->currentWorkArea();
3003 Buffer & b = wa->bufferView().buffer();
3005 // We only want to close the buffer if the same buffer is not visible
3006 // in another view, and if this is not a child and if we are closing
3007 // a view (not a tabgroup).
3008 bool const close_buffer =
3009 !inOtherView(b) && !b.parent() && closing_;
3011 if (!closeWorkArea(wa, close_buffer))
3018 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3020 if (buf.isClean() || buf.paragraphs().empty())
3023 // Switch to this Buffer.
3029 if (buf.isUnnamed()) {
3030 file = from_utf8(buf.fileName().onlyFileName());
3033 FileName filename = buf.fileName();
3035 file = filename.displayName(30);
3036 exists = filename.exists();
3039 // Bring this window to top before asking questions.
3044 if (hiding && buf.isUnnamed()) {
3045 docstring const text = bformat(_("The document %1$s has not been "
3046 "saved yet.\n\nDo you want to save "
3047 "the document?"), file);
3048 ret = Alert::prompt(_("Save new document?"),
3049 text, 0, 1, _("&Save"), _("&Cancel"));
3053 docstring const text = exists ?
3054 bformat(_("The document %1$s has unsaved changes."
3055 "\n\nDo you want to save the document or "
3056 "discard the changes?"), file) :
3057 bformat(_("The document %1$s has not been saved yet."
3058 "\n\nDo you want to save the document or "
3059 "discard it entirely?"), file);
3060 docstring const title = exists ?
3061 _("Save changed document?") : _("Save document?");
3062 ret = Alert::prompt(title, text, 0, 2,
3063 _("&Save"), _("&Discard"), _("&Cancel"));
3068 if (!saveBuffer(buf))
3072 // If we crash after this we could have no autosave file
3073 // but I guess this is really improbable (Jug).
3074 // Sometimes improbable things happen:
3075 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3076 // buf.removeAutosaveFile();
3078 // revert all changes
3089 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3091 Buffer & buf = wa->bufferView().buffer();
3093 for (int i = 0; i != d.splitter_->count(); ++i) {
3094 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3095 if (wa_ && wa_ != wa)
3098 return inOtherView(buf);
3102 bool GuiView::inOtherView(Buffer & buf)
3104 QList<int> const ids = guiApp->viewIds();
3106 for (int i = 0; i != ids.size(); ++i) {
3110 if (guiApp->view(ids[i]).workArea(buf))
3117 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3119 if (!documentBufferView())
3122 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3123 Buffer * const curbuf = &documentBufferView()->buffer();
3124 int nwa = twa->count();
3125 for (int i = 0; i < nwa; ++i) {
3126 if (&workArea(i)->bufferView().buffer() == curbuf) {
3128 if (np == NEXTBUFFER)
3129 next_index = (i == nwa - 1 ? 0 : i + 1);
3131 next_index = (i == 0 ? nwa - 1 : i - 1);
3133 twa->moveTab(i, next_index);
3135 setBuffer(&workArea(next_index)->bufferView().buffer());
3143 /// make sure the document is saved
3144 static bool ensureBufferClean(Buffer * buffer)
3146 LASSERT(buffer, return false);
3147 if (buffer->isClean() && !buffer->isUnnamed())
3150 docstring const file = buffer->fileName().displayName(30);
3153 if (!buffer->isUnnamed()) {
3154 text = bformat(_("The document %1$s has unsaved "
3155 "changes.\n\nDo you want to save "
3156 "the document?"), file);
3157 title = _("Save changed document?");
3160 text = bformat(_("The document %1$s has not been "
3161 "saved yet.\n\nDo you want to save "
3162 "the document?"), file);
3163 title = _("Save new document?");
3165 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3168 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3170 return buffer->isClean() && !buffer->isUnnamed();
3174 bool GuiView::reloadBuffer(Buffer & buf)
3176 currentBufferView()->cursor().reset();
3177 Buffer::ReadStatus status = buf.reload();
3178 return status == Buffer::ReadSuccess;
3182 void GuiView::checkExternallyModifiedBuffers()
3184 BufferList::iterator bit = theBufferList().begin();
3185 BufferList::iterator const bend = theBufferList().end();
3186 for (; bit != bend; ++bit) {
3187 Buffer * buf = *bit;
3188 if (buf->fileName().exists() && buf->isChecksumModified()) {
3189 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3190 " Reload now? Any local changes will be lost."),
3191 from_utf8(buf->absFileName()));
3192 int const ret = Alert::prompt(_("Reload externally changed document?"),
3193 text, 0, 1, _("&Reload"), _("&Cancel"));
3201 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3203 Buffer * buffer = documentBufferView()
3204 ? &(documentBufferView()->buffer()) : 0;
3206 switch (cmd.action()) {
3207 case LFUN_VC_REGISTER:
3208 if (!buffer || !ensureBufferClean(buffer))
3210 if (!buffer->lyxvc().inUse()) {
3211 if (buffer->lyxvc().registrer()) {
3212 reloadBuffer(*buffer);
3213 dr.clearMessageUpdate();
3218 case LFUN_VC_RENAME:
3219 case LFUN_VC_COPY: {
3220 if (!buffer || !ensureBufferClean(buffer))
3222 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3223 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3224 // Some changes are not yet committed.
3225 // We test here and not in getStatus(), since
3226 // this test is expensive.
3228 LyXVC::CommandResult ret =
3229 buffer->lyxvc().checkIn(log);
3231 if (ret == LyXVC::ErrorCommand ||
3232 ret == LyXVC::VCSuccess)
3233 reloadBuffer(*buffer);
3234 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3235 frontend::Alert::error(
3236 _("Revision control error."),
3237 _("Document could not be checked in."));
3241 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3242 LV_VC_RENAME : LV_VC_COPY;
3243 renameBuffer(*buffer, cmd.argument(), kind);
3248 case LFUN_VC_CHECK_IN:
3249 if (!buffer || !ensureBufferClean(buffer))
3251 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3253 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3255 // Only skip reloading if the checkin was cancelled or
3256 // an error occurred before the real checkin VCS command
3257 // was executed, since the VCS might have changed the
3258 // file even if it could not checkin successfully.
3259 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3260 reloadBuffer(*buffer);
3264 case LFUN_VC_CHECK_OUT:
3265 if (!buffer || !ensureBufferClean(buffer))
3267 if (buffer->lyxvc().inUse()) {
3268 dr.setMessage(buffer->lyxvc().checkOut());
3269 reloadBuffer(*buffer);
3273 case LFUN_VC_LOCKING_TOGGLE:
3274 LASSERT(buffer, return);
3275 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3277 if (buffer->lyxvc().inUse()) {
3278 string res = buffer->lyxvc().lockingToggle();
3280 frontend::Alert::error(_("Revision control error."),
3281 _("Error when setting the locking property."));
3284 reloadBuffer(*buffer);
3289 case LFUN_VC_REVERT:
3290 LASSERT(buffer, return);
3291 if (buffer->lyxvc().revert()) {
3292 reloadBuffer(*buffer);
3293 dr.clearMessageUpdate();
3297 case LFUN_VC_UNDO_LAST:
3298 LASSERT(buffer, return);
3299 buffer->lyxvc().undoLast();
3300 reloadBuffer(*buffer);
3301 dr.clearMessageUpdate();
3304 case LFUN_VC_REPO_UPDATE:
3305 LASSERT(buffer, return);
3306 if (ensureBufferClean(buffer)) {
3307 dr.setMessage(buffer->lyxvc().repoUpdate());
3308 checkExternallyModifiedBuffers();
3312 case LFUN_VC_COMMAND: {
3313 string flag = cmd.getArg(0);
3314 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3317 if (contains(flag, 'M')) {
3318 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3321 string path = cmd.getArg(1);
3322 if (contains(path, "$$p") && buffer)
3323 path = subst(path, "$$p", buffer->filePath());
3324 LYXERR(Debug::LYXVC, "Directory: " << path);
3326 if (!pp.isReadableDirectory()) {
3327 lyxerr << _("Directory is not accessible.") << endl;
3330 support::PathChanger p(pp);
3332 string command = cmd.getArg(2);
3333 if (command.empty())
3336 command = subst(command, "$$i", buffer->absFileName());
3337 command = subst(command, "$$p", buffer->filePath());
3339 command = subst(command, "$$m", to_utf8(message));
3340 LYXERR(Debug::LYXVC, "Command: " << command);
3342 one.startscript(Systemcall::Wait, command);
3346 if (contains(flag, 'I'))
3347 buffer->markDirty();
3348 if (contains(flag, 'R'))
3349 reloadBuffer(*buffer);
3354 case LFUN_VC_COMPARE: {
3355 if (cmd.argument().empty()) {
3356 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3360 string rev1 = cmd.getArg(0);
3365 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3368 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3369 f2 = buffer->absFileName();
3371 string rev2 = cmd.getArg(1);
3375 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3379 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3380 f1 << "\n" << f2 << "\n" );
3381 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3382 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3392 void GuiView::openChildDocument(string const & fname)
3394 LASSERT(documentBufferView(), return);
3395 Buffer & buffer = documentBufferView()->buffer();
3396 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3397 documentBufferView()->saveBookmark(false);
3399 if (theBufferList().exists(filename)) {
3400 child = theBufferList().getBuffer(filename);
3403 message(bformat(_("Opening child document %1$s..."),
3404 makeDisplayPath(filename.absFileName())));
3405 child = loadDocument(filename, false);
3407 // Set the parent name of the child document.
3408 // This makes insertion of citations and references in the child work,
3409 // when the target is in the parent or another child document.
3411 child->setParent(&buffer);
3415 bool GuiView::goToFileRow(string const & argument)
3419 size_t i = argument.find_last_of(' ');
3420 if (i != string::npos) {
3421 file_name = os::internal_path(trim(argument.substr(0, i)));
3422 istringstream is(argument.substr(i + 1));
3427 if (i == string::npos) {
3428 LYXERR0("Wrong argument: " << argument);
3432 string const abstmp = package().temp_dir().absFileName();
3433 string const realtmp = package().temp_dir().realPath();
3434 // We have to use os::path_prefix_is() here, instead of
3435 // simply prefixIs(), because the file name comes from
3436 // an external application and may need case adjustment.
3437 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3438 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3439 // Needed by inverse dvi search. If it is a file
3440 // in tmpdir, call the apropriated function.
3441 // If tmpdir is a symlink, we may have the real
3442 // path passed back, so we correct for that.
3443 if (!prefixIs(file_name, abstmp))
3444 file_name = subst(file_name, realtmp, abstmp);
3445 buf = theBufferList().getBufferFromTmp(file_name);
3447 // Must replace extension of the file to be .lyx
3448 // and get full path
3449 FileName const s = fileSearch(string(),
3450 support::changeExtension(file_name, ".lyx"), "lyx");
3451 // Either change buffer or load the file
3452 if (theBufferList().exists(s))
3453 buf = theBufferList().getBuffer(s);
3454 else if (s.exists()) {
3455 buf = loadDocument(s);
3460 _("File does not exist: %1$s"),
3461 makeDisplayPath(file_name)));
3467 _("No buffer for file: %1$s."),
3468 makeDisplayPath(file_name))
3473 bool success = documentBufferView()->setCursorFromRow(row);
3475 LYXERR(Debug::LATEX,
3476 "setCursorFromRow: invalid position for row " << row);
3477 frontend::Alert::error(_("Inverse Search Failed"),
3478 _("Invalid position requested by inverse search.\n"
3479 "You may need to update the viewed document."));
3485 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3487 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3488 menu->exec(QCursor::pos());
3493 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3495 Buffer::ExportStatus const status = func(format);
3497 // the cloning operation will have produced a clone of the entire set of
3498 // documents, starting from the master. so we must delete those.
3499 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3501 busyBuffers.remove(orig);
3506 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3508 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3509 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3513 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3515 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3516 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3520 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3522 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3523 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3527 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3528 string const & argument,
3529 Buffer const * used_buffer,
3530 docstring const & msg,
3531 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3532 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3533 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3538 string format = argument;
3540 format = used_buffer->params().getDefaultOutputFormat();
3541 processing_format = format;
3543 progress_->clearMessages();
3546 #if EXPORT_in_THREAD
3547 GuiViewPrivate::busyBuffers.insert(used_buffer);
3548 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3549 if (!cloned_buffer) {
3550 Alert::error(_("Export Error"),
3551 _("Error cloning the Buffer."));
3554 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3559 setPreviewFuture(f);
3560 last_export_format = used_buffer->params().bufferFormat();
3563 // We are asynchronous, so we don't know here anything about the success
3566 Buffer::ExportStatus status;
3568 status = (used_buffer->*syncFunc)(format, true);
3569 } else if (previewFunc) {
3570 status = (used_buffer->*previewFunc)(format);
3573 handleExportStatus(gv_, status, format);
3575 return (status == Buffer::ExportSuccess
3576 || status == Buffer::PreviewSuccess);
3580 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3582 BufferView * bv = currentBufferView();
3583 LASSERT(bv, return);
3585 // Let the current BufferView dispatch its own actions.
3586 bv->dispatch(cmd, dr);
3587 if (dr.dispatched())
3590 // Try with the document BufferView dispatch if any.
3591 BufferView * doc_bv = documentBufferView();
3592 if (doc_bv && doc_bv != bv) {
3593 doc_bv->dispatch(cmd, dr);
3594 if (dr.dispatched())
3598 // Then let the current Cursor dispatch its own actions.
3599 bv->cursor().dispatch(cmd);
3601 // update completion. We do it here and not in
3602 // processKeySym to avoid another redraw just for a
3603 // changed inline completion
3604 if (cmd.origin() == FuncRequest::KEYBOARD) {
3605 if (cmd.action() == LFUN_SELF_INSERT
3606 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3607 updateCompletion(bv->cursor(), true, true);
3608 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3609 updateCompletion(bv->cursor(), false, true);
3611 updateCompletion(bv->cursor(), false, false);
3614 dr = bv->cursor().result();
3618 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3620 BufferView * bv = currentBufferView();
3621 // By default we won't need any update.
3622 dr.screenUpdate(Update::None);
3623 // assume cmd will be dispatched
3624 dr.dispatched(true);
3626 Buffer * doc_buffer = documentBufferView()
3627 ? &(documentBufferView()->buffer()) : 0;
3629 if (cmd.origin() == FuncRequest::TOC) {
3630 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3631 // FIXME: do we need to pass a DispatchResult object here?
3632 toc->doDispatch(bv->cursor(), cmd);
3636 string const argument = to_utf8(cmd.argument());
3638 switch(cmd.action()) {
3639 case LFUN_BUFFER_CHILD_OPEN:
3640 openChildDocument(to_utf8(cmd.argument()));
3643 case LFUN_BUFFER_IMPORT:
3644 importDocument(to_utf8(cmd.argument()));
3647 case LFUN_BUFFER_EXPORT: {
3650 // GCC only sees strfwd.h when building merged
3651 if (::lyx::operator==(cmd.argument(), "custom")) {
3652 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3656 string const dest = cmd.getArg(1);
3657 FileName target_dir;
3658 if (!dest.empty() && FileName::isAbsolute(dest))
3659 target_dir = FileName(support::onlyPath(dest));
3661 target_dir = doc_buffer->fileName().onlyPath();
3663 string const format = (argument.empty() || argument == "default") ?
3664 doc_buffer->params().getDefaultOutputFormat() : argument;
3666 if ((dest.empty() && doc_buffer->isUnnamed())
3667 || !target_dir.isDirWritable()) {
3668 exportBufferAs(*doc_buffer, from_utf8(format));
3671 /* TODO/Review: Is it a problem to also export the children?
3672 See the update_unincluded flag */
3673 d.asyncBufferProcessing(format,
3676 &GuiViewPrivate::exportAndDestroy,
3679 // TODO Inform user about success
3683 case LFUN_BUFFER_EXPORT_AS: {
3684 LASSERT(doc_buffer, break);
3685 docstring f = cmd.argument();
3687 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3688 exportBufferAs(*doc_buffer, f);
3692 case LFUN_BUFFER_UPDATE: {
3693 d.asyncBufferProcessing(argument,
3696 &GuiViewPrivate::compileAndDestroy,
3701 case LFUN_BUFFER_VIEW: {
3702 d.asyncBufferProcessing(argument,
3704 _("Previewing ..."),
3705 &GuiViewPrivate::previewAndDestroy,
3710 case LFUN_MASTER_BUFFER_UPDATE: {
3711 d.asyncBufferProcessing(argument,
3712 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3714 &GuiViewPrivate::compileAndDestroy,
3719 case LFUN_MASTER_BUFFER_VIEW: {
3720 d.asyncBufferProcessing(argument,
3721 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3723 &GuiViewPrivate::previewAndDestroy,
3724 0, &Buffer::preview);
3727 case LFUN_BUFFER_SWITCH: {
3728 string const file_name = to_utf8(cmd.argument());
3729 if (!FileName::isAbsolute(file_name)) {
3731 dr.setMessage(_("Absolute filename expected."));
3735 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3738 dr.setMessage(_("Document not loaded"));
3742 // Do we open or switch to the buffer in this view ?
3743 if (workArea(*buffer)
3744 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3749 // Look for the buffer in other views
3750 QList<int> const ids = guiApp->viewIds();
3752 for (; i != ids.size(); ++i) {
3753 GuiView & gv = guiApp->view(ids[i]);
3754 if (gv.workArea(*buffer)) {
3756 gv.activateWindow();
3758 gv.setBuffer(buffer);
3763 // If necessary, open a new window as a last resort
3764 if (i == ids.size()) {
3765 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3771 case LFUN_BUFFER_NEXT:
3772 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3775 case LFUN_BUFFER_MOVE_NEXT:
3776 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3779 case LFUN_BUFFER_PREVIOUS:
3780 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3783 case LFUN_BUFFER_MOVE_PREVIOUS:
3784 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3787 case LFUN_COMMAND_EXECUTE: {
3788 command_execute_ = true;
3789 minibuffer_focus_ = true;
3792 case LFUN_DROP_LAYOUTS_CHOICE:
3793 d.layout_->showPopup();
3796 case LFUN_MENU_OPEN:
3797 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3798 menu->exec(QCursor::pos());
3801 case LFUN_FILE_INSERT:
3802 insertLyXFile(cmd.argument());
3805 case LFUN_FILE_INSERT_PLAINTEXT:
3806 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3807 string const fname = to_utf8(cmd.argument());
3808 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3809 dr.setMessage(_("Absolute filename expected."));
3813 FileName filename(fname);
3814 if (fname.empty()) {
3815 FileDialog dlg(qt_("Select file to insert"));
3817 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3818 QStringList(qt_("All Files (*)")));
3820 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3821 dr.setMessage(_("Canceled."));
3825 filename.set(fromqstr(result.second));
3829 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3830 bv->dispatch(new_cmd, dr);
3835 case LFUN_BUFFER_RELOAD: {
3836 LASSERT(doc_buffer, break);
3839 if (!doc_buffer->isClean()) {
3840 docstring const file =
3841 makeDisplayPath(doc_buffer->absFileName(), 20);
3842 if (doc_buffer->notifiesExternalModification()) {
3843 docstring text = _("The current version will be lost. "
3844 "Are you sure you want to load the version on disk "
3845 "of the document %1$s?");
3846 ret = Alert::prompt(_("Reload saved document?"),
3847 bformat(text, file), 1, 1,
3848 _("&Reload"), _("&Cancel"));
3850 docstring text = _("Any changes will be lost. "
3851 "Are you sure you want to revert to the saved version "
3852 "of the document %1$s?");
3853 ret = Alert::prompt(_("Revert to saved document?"),
3854 bformat(text, file), 1, 1,
3855 _("&Revert"), _("&Cancel"));
3860 doc_buffer->markClean();
3861 reloadBuffer(*doc_buffer);
3862 dr.forceBufferUpdate();
3867 case LFUN_BUFFER_WRITE:
3868 LASSERT(doc_buffer, break);
3869 saveBuffer(*doc_buffer);
3872 case LFUN_BUFFER_WRITE_AS:
3873 LASSERT(doc_buffer, break);
3874 renameBuffer(*doc_buffer, cmd.argument());
3877 case LFUN_BUFFER_WRITE_ALL: {
3878 Buffer * first = theBufferList().first();
3881 message(_("Saving all documents..."));
3882 // We cannot use a for loop as the buffer list cycles.
3885 if (!b->isClean()) {
3887 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3889 b = theBufferList().next(b);
3890 } while (b != first);
3891 dr.setMessage(_("All documents saved."));
3895 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3896 LASSERT(doc_buffer, break);
3897 doc_buffer->clearExternalModification();
3900 case LFUN_BUFFER_CLOSE:
3904 case LFUN_BUFFER_CLOSE_ALL:
3908 case LFUN_DEVEL_MODE_TOGGLE:
3909 devel_mode_ = !devel_mode_;
3911 dr.setMessage(_("Developer mode is now enabled."));
3913 dr.setMessage(_("Developer mode is now disabled."));
3916 case LFUN_TOOLBAR_TOGGLE: {
3917 string const name = cmd.getArg(0);
3918 if (GuiToolbar * t = toolbar(name))
3923 case LFUN_TOOLBAR_MOVABLE: {
3924 string const name = cmd.getArg(0);
3926 // toggle (all) toolbars movablility
3927 toolbarsMovable_ = !toolbarsMovable_;
3928 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3929 GuiToolbar * tb = toolbar(ti.name);
3930 if (tb && tb->isMovable() != toolbarsMovable_)
3931 // toggle toolbar movablity if it does not fit lock
3932 // (all) toolbars positions state silent = true, since
3933 // status bar notifications are slow
3936 if (toolbarsMovable_)
3937 dr.setMessage(_("Toolbars unlocked."));
3939 dr.setMessage(_("Toolbars locked."));
3940 } else if (GuiToolbar * t = toolbar(name)) {
3941 // toggle current toolbar movablity
3943 // update lock (all) toolbars positions
3944 updateLockToolbars();
3949 case LFUN_ICON_SIZE: {
3950 QSize size = d.iconSize(cmd.argument());
3952 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
3953 size.width(), size.height()));
3957 case LFUN_DIALOG_UPDATE: {
3958 string const name = to_utf8(cmd.argument());
3959 if (name == "prefs" || name == "document")
3960 updateDialog(name, string());
3961 else if (name == "paragraph")
3962 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
3963 else if (currentBufferView()) {
3964 Inset * inset = currentBufferView()->editedInset(name);
3965 // Can only update a dialog connected to an existing inset
3967 // FIXME: get rid of this indirection; GuiView ask the inset
3968 // if he is kind enough to update itself...
3969 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
3970 //FIXME: pass DispatchResult here?
3971 inset->dispatch(currentBufferView()->cursor(), fr);
3977 case LFUN_DIALOG_TOGGLE: {
3978 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
3979 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
3980 dispatch(FuncRequest(func_code, cmd.argument()), dr);
3984 case LFUN_DIALOG_DISCONNECT_INSET:
3985 disconnectDialog(to_utf8(cmd.argument()));
3988 case LFUN_DIALOG_HIDE: {
3989 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
3993 case LFUN_DIALOG_SHOW: {
3994 string const name = cmd.getArg(0);
3995 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
3997 if (name == "character") {
3998 data = freefont2string();
4000 showDialog("character", data);
4001 } else if (name == "latexlog") {
4002 // getStatus checks that
4003 LATTEST(doc_buffer);
4004 Buffer::LogType type;
4005 string const logfile = doc_buffer->logName(&type);
4007 case Buffer::latexlog:
4010 case Buffer::buildlog:
4014 data += Lexer::quoteString(logfile);
4015 showDialog("log", data);
4016 } else if (name == "vclog") {
4017 // getStatus checks that
4018 LATTEST(doc_buffer);
4019 string const data = "vc " +
4020 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4021 showDialog("log", data);
4022 } else if (name == "symbols") {
4023 data = bv->cursor().getEncoding()->name();
4025 showDialog("symbols", data);
4027 } else if (name == "prefs" && isFullScreen()) {
4028 lfunUiToggle("fullscreen");
4029 showDialog("prefs", data);
4031 showDialog(name, data);
4036 dr.setMessage(cmd.argument());
4039 case LFUN_UI_TOGGLE: {
4040 string arg = cmd.getArg(0);
4041 if (!lfunUiToggle(arg)) {
4042 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4043 dr.setMessage(bformat(msg, from_utf8(arg)));
4045 // Make sure the keyboard focus stays in the work area.
4050 case LFUN_VIEW_SPLIT: {
4051 LASSERT(doc_buffer, break);
4052 string const orientation = cmd.getArg(0);
4053 d.splitter_->setOrientation(orientation == "vertical"
4054 ? Qt::Vertical : Qt::Horizontal);
4055 TabWorkArea * twa = addTabWorkArea();
4056 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4057 setCurrentWorkArea(wa);
4060 case LFUN_TAB_GROUP_CLOSE:
4061 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4062 closeTabWorkArea(twa);
4063 d.current_work_area_ = 0;
4064 twa = d.currentTabWorkArea();
4065 // Switch to the next GuiWorkArea in the found TabWorkArea.
4067 // Make sure the work area is up to date.
4068 setCurrentWorkArea(twa->currentWorkArea());
4070 setCurrentWorkArea(0);
4075 case LFUN_VIEW_CLOSE:
4076 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4077 closeWorkArea(twa->currentWorkArea());
4078 d.current_work_area_ = 0;
4079 twa = d.currentTabWorkArea();
4080 // Switch to the next GuiWorkArea in the found TabWorkArea.
4082 // Make sure the work area is up to date.
4083 setCurrentWorkArea(twa->currentWorkArea());
4085 setCurrentWorkArea(0);
4090 case LFUN_COMPLETION_INLINE:
4091 if (d.current_work_area_)
4092 d.current_work_area_->completer().showInline();
4095 case LFUN_COMPLETION_POPUP:
4096 if (d.current_work_area_)
4097 d.current_work_area_->completer().showPopup();
4102 if (d.current_work_area_)
4103 d.current_work_area_->completer().tab();
4106 case LFUN_COMPLETION_CANCEL:
4107 if (d.current_work_area_) {
4108 if (d.current_work_area_->completer().popupVisible())
4109 d.current_work_area_->completer().hidePopup();
4111 d.current_work_area_->completer().hideInline();
4115 case LFUN_COMPLETION_ACCEPT:
4116 if (d.current_work_area_)
4117 d.current_work_area_->completer().activate();
4120 case LFUN_BUFFER_ZOOM_IN:
4121 case LFUN_BUFFER_ZOOM_OUT:
4122 case LFUN_BUFFER_ZOOM: {
4123 if (cmd.argument().empty()) {
4124 if (cmd.action() == LFUN_BUFFER_ZOOM)
4126 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4131 if (cmd.action() == LFUN_BUFFER_ZOOM)
4132 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4133 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4134 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4136 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4139 // Actual zoom value: default zoom + fractional extra value
4140 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4141 if (zoom < static_cast<int>(zoom_min_))
4144 lyxrc.currentZoom = zoom;
4146 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4147 lyxrc.currentZoom, lyxrc.defaultZoom));
4149 // The global QPixmapCache is used in GuiPainter to cache text
4150 // painting so we must reset it.
4151 QPixmapCache::clear();
4152 guiApp->fontLoader().update();
4153 dr.screenUpdate(Update::Force | Update::FitCursor);
4157 case LFUN_VC_REGISTER:
4158 case LFUN_VC_RENAME:
4160 case LFUN_VC_CHECK_IN:
4161 case LFUN_VC_CHECK_OUT:
4162 case LFUN_VC_REPO_UPDATE:
4163 case LFUN_VC_LOCKING_TOGGLE:
4164 case LFUN_VC_REVERT:
4165 case LFUN_VC_UNDO_LAST:
4166 case LFUN_VC_COMMAND:
4167 case LFUN_VC_COMPARE:
4168 dispatchVC(cmd, dr);
4171 case LFUN_SERVER_GOTO_FILE_ROW:
4172 if(goToFileRow(to_utf8(cmd.argument())))
4173 dr.screenUpdate(Update::Force | Update::FitCursor);
4176 case LFUN_LYX_ACTIVATE:
4180 case LFUN_FORWARD_SEARCH: {
4181 // it seems safe to assume we have a document buffer, since
4182 // getStatus wants one.
4183 LATTEST(doc_buffer);
4184 Buffer const * doc_master = doc_buffer->masterBuffer();
4185 FileName const path(doc_master->temppath());
4186 string const texname = doc_master->isChild(doc_buffer)
4187 ? DocFileName(changeExtension(
4188 doc_buffer->absFileName(),
4189 "tex")).mangledFileName()
4190 : doc_buffer->latexName();
4191 string const fulltexname =
4192 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4193 string const mastername =
4194 removeExtension(doc_master->latexName());
4195 FileName const dviname(addName(path.absFileName(),
4196 addExtension(mastername, "dvi")));
4197 FileName const pdfname(addName(path.absFileName(),
4198 addExtension(mastername, "pdf")));
4199 bool const have_dvi = dviname.exists();
4200 bool const have_pdf = pdfname.exists();
4201 if (!have_dvi && !have_pdf) {
4202 dr.setMessage(_("Please, preview the document first."));
4205 string outname = dviname.onlyFileName();
4206 string command = lyxrc.forward_search_dvi;
4207 if (!have_dvi || (have_pdf &&
4208 pdfname.lastModified() > dviname.lastModified())) {
4209 outname = pdfname.onlyFileName();
4210 command = lyxrc.forward_search_pdf;
4213 DocIterator cur = bv->cursor();
4214 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4215 LYXERR(Debug::ACTION, "Forward search: row:" << row
4217 if (row == -1 || command.empty()) {
4218 dr.setMessage(_("Couldn't proceed."));
4221 string texrow = convert<string>(row);
4223 command = subst(command, "$$n", texrow);
4224 command = subst(command, "$$f", fulltexname);
4225 command = subst(command, "$$t", texname);
4226 command = subst(command, "$$o", outname);
4228 PathChanger p(path);
4230 one.startscript(Systemcall::DontWait, command);
4234 case LFUN_SPELLING_CONTINUOUSLY:
4235 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4236 dr.screenUpdate(Update::Force);
4240 // The LFUN must be for one of BufferView, Buffer or Cursor;
4242 dispatchToBufferView(cmd, dr);
4246 // Part of automatic menu appearance feature.
4247 if (isFullScreen()) {
4248 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4252 // Need to update bv because many LFUNs here might have destroyed it
4253 bv = currentBufferView();
4255 // Clear non-empty selections
4256 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4258 Cursor & cur = bv->cursor();
4259 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4260 cur.clearSelection();
4266 bool GuiView::lfunUiToggle(string const & ui_component)
4268 if (ui_component == "scrollbar") {
4269 // hide() is of no help
4270 if (d.current_work_area_->verticalScrollBarPolicy() ==
4271 Qt::ScrollBarAlwaysOff)
4273 d.current_work_area_->setVerticalScrollBarPolicy(
4274 Qt::ScrollBarAsNeeded);
4276 d.current_work_area_->setVerticalScrollBarPolicy(
4277 Qt::ScrollBarAlwaysOff);
4278 } else if (ui_component == "statusbar") {
4279 statusBar()->setVisible(!statusBar()->isVisible());
4280 } else if (ui_component == "menubar") {
4281 menuBar()->setVisible(!menuBar()->isVisible());
4283 if (ui_component == "frame") {
4285 getContentsMargins(&l, &t, &r, &b);
4286 //are the frames in default state?
4287 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4289 setContentsMargins(-2, -2, -2, -2);
4291 setContentsMargins(0, 0, 0, 0);
4294 if (ui_component == "fullscreen") {
4302 void GuiView::toggleFullScreen()
4304 if (isFullScreen()) {
4305 for (int i = 0; i != d.splitter_->count(); ++i)
4306 d.tabWorkArea(i)->setFullScreen(false);
4307 setContentsMargins(0, 0, 0, 0);
4308 setWindowState(windowState() ^ Qt::WindowFullScreen);
4311 statusBar()->show();
4314 hideDialogs("prefs", 0);
4315 for (int i = 0; i != d.splitter_->count(); ++i)
4316 d.tabWorkArea(i)->setFullScreen(true);
4317 setContentsMargins(-2, -2, -2, -2);
4319 setWindowState(windowState() ^ Qt::WindowFullScreen);
4320 if (lyxrc.full_screen_statusbar)
4321 statusBar()->hide();
4322 if (lyxrc.full_screen_menubar)
4324 if (lyxrc.full_screen_toolbars) {
4325 ToolbarMap::iterator end = d.toolbars_.end();
4326 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4331 // give dialogs like the TOC a chance to adapt
4336 Buffer const * GuiView::updateInset(Inset const * inset)
4341 Buffer const * inset_buffer = &(inset->buffer());
4343 for (int i = 0; i != d.splitter_->count(); ++i) {
4344 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4347 Buffer const * buffer = &(wa->bufferView().buffer());
4348 if (inset_buffer == buffer)
4349 wa->scheduleRedraw(true);
4351 return inset_buffer;
4355 void GuiView::restartCaret()
4357 /* When we move around, or type, it's nice to be able to see
4358 * the caret immediately after the keypress.
4360 if (d.current_work_area_)
4361 d.current_work_area_->startBlinkingCaret();
4363 // Take this occasion to update the other GUI elements.
4369 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4371 if (d.current_work_area_)
4372 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4377 // This list should be kept in sync with the list of insets in
4378 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4379 // dialog should have the same name as the inset.
4380 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4381 // docs in LyXAction.cpp.
4383 char const * const dialognames[] = {
4385 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4386 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4387 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4388 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4389 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4390 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4391 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4392 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4394 char const * const * const end_dialognames =
4395 dialognames + (sizeof(dialognames) / sizeof(char *));
4399 cmpCStr(char const * name) : name_(name) {}
4400 bool operator()(char const * other) {
4401 return strcmp(other, name_) == 0;
4408 bool isValidName(string const & name)
4410 return find_if(dialognames, end_dialognames,
4411 cmpCStr(name.c_str())) != end_dialognames;
4417 void GuiView::resetDialogs()
4419 // Make sure that no LFUN uses any GuiView.
4420 guiApp->setCurrentView(0);
4424 constructToolbars();
4425 guiApp->menus().fillMenuBar(menuBar(), this, false);
4426 d.layout_->updateContents(true);
4427 // Now update controls with current buffer.
4428 guiApp->setCurrentView(this);
4434 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4436 if (!isValidName(name))
4439 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4441 if (it != d.dialogs_.end()) {
4443 it->second->hideView();
4444 return it->second.get();
4447 Dialog * dialog = build(name);
4448 d.dialogs_[name].reset(dialog);
4449 if (lyxrc.allow_geometry_session)
4450 dialog->restoreSession();
4457 void GuiView::showDialog(string const & name, string const & data,
4460 triggerShowDialog(toqstr(name), toqstr(data), inset);
4464 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4470 const string name = fromqstr(qname);
4471 const string data = fromqstr(qdata);
4475 Dialog * dialog = findOrBuild(name, false);
4477 bool const visible = dialog->isVisibleView();
4478 dialog->showData(data);
4479 if (inset && currentBufferView())
4480 currentBufferView()->editInset(name, inset);
4481 // We only set the focus to the new dialog if it was not yet
4482 // visible in order not to change the existing previous behaviour
4484 // activateWindow is needed for floating dockviews
4485 dialog->asQWidget()->raise();
4486 dialog->asQWidget()->activateWindow();
4487 dialog->asQWidget()->setFocus();
4491 catch (ExceptionMessage const & ex) {
4499 bool GuiView::isDialogVisible(string const & name) const
4501 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4502 if (it == d.dialogs_.end())
4504 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4508 void GuiView::hideDialog(string const & name, Inset * inset)
4510 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4511 if (it == d.dialogs_.end())
4515 if (!currentBufferView())
4517 if (inset != currentBufferView()->editedInset(name))
4521 Dialog * const dialog = it->second.get();
4522 if (dialog->isVisibleView())
4524 if (currentBufferView())
4525 currentBufferView()->editInset(name, 0);
4529 void GuiView::disconnectDialog(string const & name)
4531 if (!isValidName(name))
4533 if (currentBufferView())
4534 currentBufferView()->editInset(name, 0);
4538 void GuiView::hideAll() const
4540 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4541 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4543 for(; it != end; ++it)
4544 it->second->hideView();
4548 void GuiView::updateDialogs()
4550 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4551 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4553 for(; it != end; ++it) {
4554 Dialog * dialog = it->second.get();
4556 if (dialog->needBufferOpen() && !documentBufferView())
4557 hideDialog(fromqstr(dialog->name()), 0);
4558 else if (dialog->isVisibleView())
4559 dialog->checkStatus();
4566 Dialog * createDialog(GuiView & lv, string const & name);
4568 // will be replaced by a proper factory...
4569 Dialog * createGuiAbout(GuiView & lv);
4570 Dialog * createGuiBibtex(GuiView & lv);
4571 Dialog * createGuiChanges(GuiView & lv);
4572 Dialog * createGuiCharacter(GuiView & lv);
4573 Dialog * createGuiCitation(GuiView & lv);
4574 Dialog * createGuiCompare(GuiView & lv);
4575 Dialog * createGuiCompareHistory(GuiView & lv);
4576 Dialog * createGuiDelimiter(GuiView & lv);
4577 Dialog * createGuiDocument(GuiView & lv);
4578 Dialog * createGuiErrorList(GuiView & lv);
4579 Dialog * createGuiExternal(GuiView & lv);
4580 Dialog * createGuiGraphics(GuiView & lv);
4581 Dialog * createGuiInclude(GuiView & lv);
4582 Dialog * createGuiIndex(GuiView & lv);
4583 Dialog * createGuiListings(GuiView & lv);
4584 Dialog * createGuiLog(GuiView & lv);
4585 Dialog * createGuiMathMatrix(GuiView & lv);
4586 Dialog * createGuiNote(GuiView & lv);
4587 Dialog * createGuiParagraph(GuiView & lv);
4588 Dialog * createGuiPhantom(GuiView & lv);
4589 Dialog * createGuiPreferences(GuiView & lv);
4590 Dialog * createGuiPrint(GuiView & lv);
4591 Dialog * createGuiPrintindex(GuiView & lv);
4592 Dialog * createGuiRef(GuiView & lv);
4593 Dialog * createGuiSearch(GuiView & lv);
4594 Dialog * createGuiSearchAdv(GuiView & lv);
4595 Dialog * createGuiSendTo(GuiView & lv);
4596 Dialog * createGuiShowFile(GuiView & lv);
4597 Dialog * createGuiSpellchecker(GuiView & lv);
4598 Dialog * createGuiSymbols(GuiView & lv);
4599 Dialog * createGuiTabularCreate(GuiView & lv);
4600 Dialog * createGuiTexInfo(GuiView & lv);
4601 Dialog * createGuiToc(GuiView & lv);
4602 Dialog * createGuiThesaurus(GuiView & lv);
4603 Dialog * createGuiViewSource(GuiView & lv);
4604 Dialog * createGuiWrap(GuiView & lv);
4605 Dialog * createGuiProgressView(GuiView & lv);
4609 Dialog * GuiView::build(string const & name)
4611 LASSERT(isValidName(name), return 0);
4613 Dialog * dialog = createDialog(*this, name);
4617 if (name == "aboutlyx")
4618 return createGuiAbout(*this);
4619 if (name == "bibtex")
4620 return createGuiBibtex(*this);
4621 if (name == "changes")
4622 return createGuiChanges(*this);
4623 if (name == "character")
4624 return createGuiCharacter(*this);
4625 if (name == "citation")
4626 return createGuiCitation(*this);
4627 if (name == "compare")
4628 return createGuiCompare(*this);
4629 if (name == "comparehistory")
4630 return createGuiCompareHistory(*this);
4631 if (name == "document")
4632 return createGuiDocument(*this);
4633 if (name == "errorlist")
4634 return createGuiErrorList(*this);
4635 if (name == "external")
4636 return createGuiExternal(*this);
4638 return createGuiShowFile(*this);
4639 if (name == "findreplace")
4640 return createGuiSearch(*this);
4641 if (name == "findreplaceadv")
4642 return createGuiSearchAdv(*this);
4643 if (name == "graphics")
4644 return createGuiGraphics(*this);
4645 if (name == "include")
4646 return createGuiInclude(*this);
4647 if (name == "index")
4648 return createGuiIndex(*this);
4649 if (name == "index_print")
4650 return createGuiPrintindex(*this);
4651 if (name == "listings")
4652 return createGuiListings(*this);
4654 return createGuiLog(*this);
4655 if (name == "mathdelimiter")
4656 return createGuiDelimiter(*this);
4657 if (name == "mathmatrix")
4658 return createGuiMathMatrix(*this);
4660 return createGuiNote(*this);
4661 if (name == "paragraph")
4662 return createGuiParagraph(*this);
4663 if (name == "phantom")
4664 return createGuiPhantom(*this);
4665 if (name == "prefs")
4666 return createGuiPreferences(*this);
4668 return createGuiRef(*this);
4669 if (name == "sendto")
4670 return createGuiSendTo(*this);
4671 if (name == "spellchecker")
4672 return createGuiSpellchecker(*this);
4673 if (name == "symbols")
4674 return createGuiSymbols(*this);
4675 if (name == "tabularcreate")
4676 return createGuiTabularCreate(*this);
4677 if (name == "texinfo")
4678 return createGuiTexInfo(*this);
4679 if (name == "thesaurus")
4680 return createGuiThesaurus(*this);
4682 return createGuiToc(*this);
4683 if (name == "view-source")
4684 return createGuiViewSource(*this);
4686 return createGuiWrap(*this);
4687 if (name == "progress")
4688 return createGuiProgressView(*this);
4694 SEMenu::SEMenu(QWidget * parent)
4696 QAction * action = addAction(qt_("Disable Shell Escape"));
4697 connect(action, SIGNAL(triggered()),
4698 parent, SLOT(disableShellEscape()));
4702 } // namespace frontend
4705 #include "moc_GuiView.cpp"