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);
704 case Buffer::ExportKilled:
705 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
712 void GuiView::processingThreadStarted()
717 void GuiView::processingThreadFinished()
719 QFutureWatcher<Buffer::ExportStatus> const * watcher =
720 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
722 Buffer::ExportStatus const status = watcher->result();
723 handleExportStatus(this, status, d.processing_format);
726 BufferView const * const bv = currentBufferView();
727 if (bv && !bv->buffer().errorList("Export").empty()) {
732 bool const error = (status != Buffer::ExportSuccess &&
733 status != Buffer::PreviewSuccess &&
734 status != Buffer::ExportCancel);
736 ErrorList & el = bv->buffer().errorList(d.last_export_format);
737 // at this point, we do not know if buffer-view or
738 // master-buffer-view was called. If there was an export error,
739 // and the current buffer's error log is empty, we guess that
740 // it must be master-buffer-view that was called so we set
742 errors(d.last_export_format, el.empty());
747 void GuiView::autoSaveThreadFinished()
749 QFutureWatcher<docstring> const * watcher =
750 static_cast<QFutureWatcher<docstring> const *>(sender());
751 message(watcher->result());
756 void GuiView::saveLayout() const
759 settings.setValue("zoom_ratio", zoom_ratio_);
760 settings.setValue("devel_mode", devel_mode_);
761 settings.beginGroup("views");
762 settings.beginGroup(QString::number(id_));
763 #if defined(Q_WS_X11) || defined(QPA_XCB)
764 settings.setValue("pos", pos());
765 settings.setValue("size", size());
767 settings.setValue("geometry", saveGeometry());
769 settings.setValue("layout", saveState(0));
770 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
774 void GuiView::saveUISettings() const
778 // Save the toolbar private states
779 ToolbarMap::iterator end = d.toolbars_.end();
780 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
781 it->second->saveSession(settings);
782 // Now take care of all other dialogs
783 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
784 for (; it!= d.dialogs_.end(); ++it)
785 it->second->saveSession(settings);
789 bool GuiView::restoreLayout()
792 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
793 // Actual zoom value: default zoom + fractional offset
794 int zoom = lyxrc.defaultZoom * zoom_ratio_;
795 if (zoom < static_cast<int>(zoom_min_))
797 lyxrc.currentZoom = zoom;
798 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
799 settings.beginGroup("views");
800 settings.beginGroup(QString::number(id_));
801 QString const icon_key = "icon_size";
802 if (!settings.contains(icon_key))
805 //code below is skipped when when ~/.config/LyX is (re)created
806 setIconSize(d.iconSize(settings.value(icon_key).toString()));
808 #if defined(Q_WS_X11) || defined(QPA_XCB)
809 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
810 QSize size = settings.value("size", QSize(690, 510)).toSize();
814 // Work-around for bug #6034: the window ends up in an undetermined
815 // state when trying to restore a maximized window when it is
816 // already maximized.
817 if (!(windowState() & Qt::WindowMaximized))
818 if (!restoreGeometry(settings.value("geometry").toByteArray()))
819 setGeometry(50, 50, 690, 510);
821 // Make sure layout is correctly oriented.
822 setLayoutDirection(qApp->layoutDirection());
824 // Allow the toc and view-source dock widget to be restored if needed.
826 if ((dialog = findOrBuild("toc", true)))
827 // see bug 5082. At least setup title and enabled state.
828 // Visibility will be adjusted by restoreState below.
829 dialog->prepareView();
830 if ((dialog = findOrBuild("view-source", true)))
831 dialog->prepareView();
832 if ((dialog = findOrBuild("progress", true)))
833 dialog->prepareView();
835 if (!restoreState(settings.value("layout").toByteArray(), 0))
838 // init the toolbars that have not been restored
839 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
840 Toolbars::Infos::iterator end = guiApp->toolbars().end();
841 for (; cit != end; ++cit) {
842 GuiToolbar * tb = toolbar(cit->name);
843 if (tb && !tb->isRestored())
844 initToolbar(cit->name);
847 // update lock (all) toolbars positions
848 updateLockToolbars();
855 GuiToolbar * GuiView::toolbar(string const & name)
857 ToolbarMap::iterator it = d.toolbars_.find(name);
858 if (it != d.toolbars_.end())
861 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
866 void GuiView::updateLockToolbars()
868 toolbarsMovable_ = false;
869 for (ToolbarInfo const & info : guiApp->toolbars()) {
870 GuiToolbar * tb = toolbar(info.name);
871 if (tb && tb->isMovable())
872 toolbarsMovable_ = true;
877 void GuiView::constructToolbars()
879 ToolbarMap::iterator it = d.toolbars_.begin();
880 for (; it != d.toolbars_.end(); ++it)
884 // I don't like doing this here, but the standard toolbar
885 // destroys this object when it's destroyed itself (vfr)
886 d.layout_ = new LayoutBox(*this);
887 d.stack_widget_->addWidget(d.layout_);
888 d.layout_->move(0,0);
890 // extracts the toolbars from the backend
891 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
892 Toolbars::Infos::iterator end = guiApp->toolbars().end();
893 for (; cit != end; ++cit)
894 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
898 void GuiView::initToolbars()
900 // extracts the toolbars from the backend
901 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
902 Toolbars::Infos::iterator end = guiApp->toolbars().end();
903 for (; cit != end; ++cit)
904 initToolbar(cit->name);
908 void GuiView::initToolbar(string const & name)
910 GuiToolbar * tb = toolbar(name);
913 int const visibility = guiApp->toolbars().defaultVisibility(name);
914 bool newline = !(visibility & Toolbars::SAMEROW);
915 tb->setVisible(false);
916 tb->setVisibility(visibility);
918 if (visibility & Toolbars::TOP) {
920 addToolBarBreak(Qt::TopToolBarArea);
921 addToolBar(Qt::TopToolBarArea, tb);
924 if (visibility & Toolbars::BOTTOM) {
926 addToolBarBreak(Qt::BottomToolBarArea);
927 addToolBar(Qt::BottomToolBarArea, tb);
930 if (visibility & Toolbars::LEFT) {
932 addToolBarBreak(Qt::LeftToolBarArea);
933 addToolBar(Qt::LeftToolBarArea, tb);
936 if (visibility & Toolbars::RIGHT) {
938 addToolBarBreak(Qt::RightToolBarArea);
939 addToolBar(Qt::RightToolBarArea, tb);
942 if (visibility & Toolbars::ON)
943 tb->setVisible(true);
945 tb->setMovable(true);
949 TocModels & GuiView::tocModels()
951 return d.toc_models_;
955 void GuiView::setFocus()
957 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
958 QMainWindow::setFocus();
962 bool GuiView::hasFocus() const
964 if (currentWorkArea())
965 return currentWorkArea()->hasFocus();
966 if (currentMainWorkArea())
967 return currentMainWorkArea()->hasFocus();
968 return d.bg_widget_->hasFocus();
972 void GuiView::focusInEvent(QFocusEvent * e)
974 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
975 QMainWindow::focusInEvent(e);
976 // Make sure guiApp points to the correct view.
977 guiApp->setCurrentView(this);
978 if (currentWorkArea())
979 currentWorkArea()->setFocus();
980 else if (currentMainWorkArea())
981 currentMainWorkArea()->setFocus();
983 d.bg_widget_->setFocus();
987 void GuiView::showEvent(QShowEvent * e)
989 LYXERR(Debug::GUI, "Passed Geometry "
990 << size().height() << "x" << size().width()
991 << "+" << pos().x() << "+" << pos().y());
993 if (d.splitter_->count() == 0)
994 // No work area, switch to the background widget.
998 QMainWindow::showEvent(e);
1002 bool GuiView::closeScheduled()
1009 bool GuiView::prepareAllBuffersForLogout()
1011 Buffer * first = theBufferList().first();
1015 // First, iterate over all buffers and ask the users if unsaved
1016 // changes should be saved.
1017 // We cannot use a for loop as the buffer list cycles.
1020 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1022 b = theBufferList().next(b);
1023 } while (b != first);
1025 // Next, save session state
1026 // When a view/window was closed before without quitting LyX, there
1027 // are already entries in the lastOpened list.
1028 theSession().lastOpened().clear();
1035 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1036 ** is responsibility of the container (e.g., dialog)
1038 void GuiView::closeEvent(QCloseEvent * close_event)
1040 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1042 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1043 Alert::warning(_("Exit LyX"),
1044 _("LyX could not be closed because documents are being processed by LyX."));
1045 close_event->setAccepted(false);
1049 // If the user pressed the x (so we didn't call closeView
1050 // programmatically), we want to clear all existing entries.
1052 theSession().lastOpened().clear();
1057 // it can happen that this event arrives without selecting the view,
1058 // e.g. when clicking the close button on a background window.
1060 if (!closeWorkAreaAll()) {
1062 close_event->ignore();
1066 // Make sure that nothing will use this to be closed View.
1067 guiApp->unregisterView(this);
1069 if (isFullScreen()) {
1070 // Switch off fullscreen before closing.
1075 // Make sure the timer time out will not trigger a statusbar update.
1076 d.statusbar_timer_.stop();
1078 // Saving fullscreen requires additional tweaks in the toolbar code.
1079 // It wouldn't also work under linux natively.
1080 if (lyxrc.allow_geometry_session) {
1085 close_event->accept();
1089 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1091 if (event->mimeData()->hasUrls())
1093 /// \todo Ask lyx-devel is this is enough:
1094 /// if (event->mimeData()->hasFormat("text/plain"))
1095 /// event->acceptProposedAction();
1099 void GuiView::dropEvent(QDropEvent * event)
1101 QList<QUrl> files = event->mimeData()->urls();
1102 if (files.isEmpty())
1105 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1106 for (int i = 0; i != files.size(); ++i) {
1107 string const file = os::internal_path(fromqstr(
1108 files.at(i).toLocalFile()));
1112 string const ext = support::getExtension(file);
1113 vector<const Format *> found_formats;
1115 // Find all formats that have the correct extension.
1116 vector<const Format *> const & import_formats
1117 = theConverters().importableFormats();
1118 vector<const Format *>::const_iterator it = import_formats.begin();
1119 for (; it != import_formats.end(); ++it)
1120 if ((*it)->hasExtension(ext))
1121 found_formats.push_back(*it);
1124 if (found_formats.size() >= 1) {
1125 if (found_formats.size() > 1) {
1126 //FIXME: show a dialog to choose the correct importable format
1127 LYXERR(Debug::FILES,
1128 "Multiple importable formats found, selecting first");
1130 string const arg = found_formats[0]->name() + " " + file;
1131 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1134 //FIXME: do we have to explicitly check whether it's a lyx file?
1135 LYXERR(Debug::FILES,
1136 "No formats found, trying to open it as a lyx file");
1137 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1139 // add the functions to the queue
1140 guiApp->addToFuncRequestQueue(cmd);
1143 // now process the collected functions. We perform the events
1144 // asynchronously. This prevents potential problems in case the
1145 // BufferView is closed within an event.
1146 guiApp->processFuncRequestQueueAsync();
1150 void GuiView::message(docstring const & str)
1152 if (ForkedProcess::iAmAChild())
1155 // call is moved to GUI-thread by GuiProgress
1156 d.progress_->appendMessage(toqstr(str));
1160 void GuiView::clearMessageText()
1162 message(docstring());
1166 void GuiView::updateStatusBarMessage(QString const & str)
1168 statusBar()->showMessage(str);
1169 d.statusbar_timer_.stop();
1170 d.statusbar_timer_.start(3000);
1174 void GuiView::clearMessage()
1176 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1177 // the hasFocus function mostly returns false, even if the focus is on
1178 // a workarea in this view.
1182 d.statusbar_timer_.stop();
1186 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1188 if (wa != d.current_work_area_
1189 || wa->bufferView().buffer().isInternal())
1191 Buffer const & buf = wa->bufferView().buffer();
1192 // Set the windows title
1193 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1194 if (buf.notifiesExternalModification()) {
1195 title = bformat(_("%1$s (modified externally)"), title);
1196 // If the external modification status has changed, then maybe the status of
1197 // buffer-save has changed too.
1201 title += from_ascii(" - LyX");
1203 setWindowTitle(toqstr(title));
1204 // Sets the path for the window: this is used by OSX to
1205 // allow a context click on the title bar showing a menu
1206 // with the path up to the file
1207 setWindowFilePath(toqstr(buf.absFileName()));
1208 // Tell Qt whether the current document is changed
1209 setWindowModified(!buf.isClean());
1211 if (buf.params().shell_escape)
1212 shell_escape_->show();
1214 shell_escape_->hide();
1216 if (buf.hasReadonlyFlag())
1221 if (buf.lyxvc().inUse()) {
1222 version_control_->show();
1223 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1225 version_control_->hide();
1229 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1231 if (d.current_work_area_)
1232 // disconnect the current work area from all slots
1233 QObject::disconnect(d.current_work_area_, 0, this, 0);
1235 disconnectBufferView();
1236 connectBufferView(wa->bufferView());
1237 connectBuffer(wa->bufferView().buffer());
1238 d.current_work_area_ = wa;
1239 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1240 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1241 QObject::connect(wa, SIGNAL(busy(bool)),
1242 this, SLOT(setBusy(bool)));
1243 // connection of a signal to a signal
1244 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1245 this, SIGNAL(bufferViewChanged()));
1246 Q_EMIT updateWindowTitle(wa);
1247 Q_EMIT bufferViewChanged();
1251 void GuiView::onBufferViewChanged()
1254 // Buffer-dependent dialogs must be updated. This is done here because
1255 // some dialogs require buffer()->text.
1260 void GuiView::on_lastWorkAreaRemoved()
1263 // We already are in a close event. Nothing more to do.
1266 if (d.splitter_->count() > 1)
1267 // We have a splitter so don't close anything.
1270 // Reset and updates the dialogs.
1271 Q_EMIT bufferViewChanged();
1276 if (lyxrc.open_buffers_in_tabs)
1277 // Nothing more to do, the window should stay open.
1280 if (guiApp->viewIds().size() > 1) {
1286 // On Mac we also close the last window because the application stay
1287 // resident in memory. On other platforms we don't close the last
1288 // window because this would quit the application.
1294 void GuiView::updateStatusBar()
1296 // let the user see the explicit message
1297 if (d.statusbar_timer_.isActive())
1304 void GuiView::showMessage()
1308 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1309 if (msg.isEmpty()) {
1310 BufferView const * bv = currentBufferView();
1312 msg = toqstr(bv->cursor().currentState(devel_mode_));
1314 msg = qt_("Welcome to LyX!");
1316 statusBar()->showMessage(msg);
1320 bool GuiView::event(QEvent * e)
1324 // Useful debug code:
1325 //case QEvent::ActivationChange:
1326 //case QEvent::WindowDeactivate:
1327 //case QEvent::Paint:
1328 //case QEvent::Enter:
1329 //case QEvent::Leave:
1330 //case QEvent::HoverEnter:
1331 //case QEvent::HoverLeave:
1332 //case QEvent::HoverMove:
1333 //case QEvent::StatusTip:
1334 //case QEvent::DragEnter:
1335 //case QEvent::DragLeave:
1336 //case QEvent::Drop:
1339 case QEvent::WindowActivate: {
1340 GuiView * old_view = guiApp->currentView();
1341 if (this == old_view) {
1343 return QMainWindow::event(e);
1345 if (old_view && old_view->currentBufferView()) {
1346 // save current selection to the selection buffer to allow
1347 // middle-button paste in this window.
1348 cap::saveSelection(old_view->currentBufferView()->cursor());
1350 guiApp->setCurrentView(this);
1351 if (d.current_work_area_)
1352 on_currentWorkAreaChanged(d.current_work_area_);
1356 return QMainWindow::event(e);
1359 case QEvent::ShortcutOverride: {
1361 if (isFullScreen() && menuBar()->isHidden()) {
1362 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1363 // FIXME: we should also try to detect special LyX shortcut such as
1364 // Alt-P and Alt-M. Right now there is a hack in
1365 // GuiWorkArea::processKeySym() that hides again the menubar for
1367 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1369 return QMainWindow::event(e);
1372 return QMainWindow::event(e);
1376 return QMainWindow::event(e);
1380 void GuiView::resetWindowTitle()
1382 setWindowTitle(qt_("LyX"));
1385 bool GuiView::focusNextPrevChild(bool /*next*/)
1392 bool GuiView::busy() const
1398 void GuiView::setBusy(bool busy)
1400 bool const busy_before = busy_ > 0;
1401 busy ? ++busy_ : --busy_;
1402 if ((busy_ > 0) == busy_before)
1403 // busy state didn't change
1407 QApplication::setOverrideCursor(Qt::WaitCursor);
1410 QApplication::restoreOverrideCursor();
1415 void GuiView::resetCommandExecute()
1417 command_execute_ = false;
1422 double GuiView::pixelRatio() const
1424 #if QT_VERSION >= 0x050000
1425 return qt_scale_factor * devicePixelRatio();
1432 GuiWorkArea * GuiView::workArea(int index)
1434 if (TabWorkArea * twa = d.currentTabWorkArea())
1435 if (index < twa->count())
1436 return twa->workArea(index);
1441 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1443 if (currentWorkArea()
1444 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1445 return currentWorkArea();
1446 if (TabWorkArea * twa = d.currentTabWorkArea())
1447 return twa->workArea(buffer);
1452 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1454 // Automatically create a TabWorkArea if there are none yet.
1455 TabWorkArea * tab_widget = d.splitter_->count()
1456 ? d.currentTabWorkArea() : addTabWorkArea();
1457 return tab_widget->addWorkArea(buffer, *this);
1461 TabWorkArea * GuiView::addTabWorkArea()
1463 TabWorkArea * twa = new TabWorkArea;
1464 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1465 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1466 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1467 this, SLOT(on_lastWorkAreaRemoved()));
1469 d.splitter_->addWidget(twa);
1470 d.stack_widget_->setCurrentWidget(d.splitter_);
1475 GuiWorkArea const * GuiView::currentWorkArea() const
1477 return d.current_work_area_;
1481 GuiWorkArea * GuiView::currentWorkArea()
1483 return d.current_work_area_;
1487 GuiWorkArea const * GuiView::currentMainWorkArea() const
1489 if (!d.currentTabWorkArea())
1491 return d.currentTabWorkArea()->currentWorkArea();
1495 GuiWorkArea * GuiView::currentMainWorkArea()
1497 if (!d.currentTabWorkArea())
1499 return d.currentTabWorkArea()->currentWorkArea();
1503 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1505 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1507 d.current_work_area_ = 0;
1509 Q_EMIT bufferViewChanged();
1513 // FIXME: I've no clue why this is here and why it accesses
1514 // theGuiApp()->currentView, which might be 0 (bug 6464).
1515 // See also 27525 (vfr).
1516 if (theGuiApp()->currentView() == this
1517 && theGuiApp()->currentView()->currentWorkArea() == wa)
1520 if (currentBufferView())
1521 cap::saveSelection(currentBufferView()->cursor());
1523 theGuiApp()->setCurrentView(this);
1524 d.current_work_area_ = wa;
1526 // We need to reset this now, because it will need to be
1527 // right if the tabWorkArea gets reset in the for loop. We
1528 // will change it back if we aren't in that case.
1529 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1530 d.current_main_work_area_ = wa;
1532 for (int i = 0; i != d.splitter_->count(); ++i) {
1533 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1534 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1535 << ", Current main wa: " << currentMainWorkArea());
1540 d.current_main_work_area_ = old_cmwa;
1542 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1543 on_currentWorkAreaChanged(wa);
1544 BufferView & bv = wa->bufferView();
1545 bv.cursor().fixIfBroken();
1547 wa->setUpdatesEnabled(true);
1548 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1552 void GuiView::removeWorkArea(GuiWorkArea * wa)
1554 LASSERT(wa, return);
1555 if (wa == d.current_work_area_) {
1557 disconnectBufferView();
1558 d.current_work_area_ = 0;
1559 d.current_main_work_area_ = 0;
1562 bool found_twa = false;
1563 for (int i = 0; i != d.splitter_->count(); ++i) {
1564 TabWorkArea * twa = d.tabWorkArea(i);
1565 if (twa->removeWorkArea(wa)) {
1566 // Found in this tab group, and deleted the GuiWorkArea.
1568 if (twa->count() != 0) {
1569 if (d.current_work_area_ == 0)
1570 // This means that we are closing the current GuiWorkArea, so
1571 // switch to the next GuiWorkArea in the found TabWorkArea.
1572 setCurrentWorkArea(twa->currentWorkArea());
1574 // No more WorkAreas in this tab group, so delete it.
1581 // It is not a tabbed work area (i.e., the search work area), so it
1582 // should be deleted by other means.
1583 LASSERT(found_twa, return);
1585 if (d.current_work_area_ == 0) {
1586 if (d.splitter_->count() != 0) {
1587 TabWorkArea * twa = d.currentTabWorkArea();
1588 setCurrentWorkArea(twa->currentWorkArea());
1590 // No more work areas, switch to the background widget.
1591 setCurrentWorkArea(0);
1597 LayoutBox * GuiView::getLayoutDialog() const
1603 void GuiView::updateLayoutList()
1606 d.layout_->updateContents(false);
1610 void GuiView::updateToolbars()
1612 ToolbarMap::iterator end = d.toolbars_.end();
1613 if (d.current_work_area_) {
1615 if (d.current_work_area_->bufferView().cursor().inMathed()
1616 && !d.current_work_area_->bufferView().cursor().inRegexped())
1617 context |= Toolbars::MATH;
1618 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1619 context |= Toolbars::TABLE;
1620 if (currentBufferView()->buffer().areChangesPresent()
1621 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1622 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1623 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1624 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1625 context |= Toolbars::REVIEW;
1626 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1627 context |= Toolbars::MATHMACROTEMPLATE;
1628 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1629 context |= Toolbars::IPA;
1630 if (command_execute_)
1631 context |= Toolbars::MINIBUFFER;
1632 if (minibuffer_focus_) {
1633 context |= Toolbars::MINIBUFFER_FOCUS;
1634 minibuffer_focus_ = false;
1637 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1638 it->second->update(context);
1640 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1641 it->second->update();
1645 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1647 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1648 LASSERT(newBuffer, return);
1650 GuiWorkArea * wa = workArea(*newBuffer);
1653 newBuffer->masterBuffer()->updateBuffer();
1655 wa = addWorkArea(*newBuffer);
1656 // scroll to the position when the BufferView was last closed
1657 if (lyxrc.use_lastfilepos) {
1658 LastFilePosSection::FilePos filepos =
1659 theSession().lastFilePos().load(newBuffer->fileName());
1660 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1663 //Disconnect the old buffer...there's no new one.
1666 connectBuffer(*newBuffer);
1667 connectBufferView(wa->bufferView());
1669 setCurrentWorkArea(wa);
1673 void GuiView::connectBuffer(Buffer & buf)
1675 buf.setGuiDelegate(this);
1679 void GuiView::disconnectBuffer()
1681 if (d.current_work_area_)
1682 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1686 void GuiView::connectBufferView(BufferView & bv)
1688 bv.setGuiDelegate(this);
1692 void GuiView::disconnectBufferView()
1694 if (d.current_work_area_)
1695 d.current_work_area_->bufferView().setGuiDelegate(0);
1699 void GuiView::errors(string const & error_type, bool from_master)
1701 BufferView const * const bv = currentBufferView();
1705 ErrorList const & el = from_master ?
1706 bv->buffer().masterBuffer()->errorList(error_type) :
1707 bv->buffer().errorList(error_type);
1712 string err = error_type;
1714 err = "from_master|" + error_type;
1715 showDialog("errorlist", err);
1719 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1721 d.toc_models_.updateItem(toqstr(type), dit);
1725 void GuiView::structureChanged()
1727 // This is called from the Buffer, which has no way to ensure that cursors
1728 // in BufferView remain valid.
1729 if (documentBufferView())
1730 documentBufferView()->cursor().sanitize();
1731 // FIXME: This is slightly expensive, though less than the tocBackend update
1732 // (#9880). This also resets the view in the Toc Widget (#6675).
1733 d.toc_models_.reset(documentBufferView());
1734 // Navigator needs more than a simple update in this case. It needs to be
1736 updateDialog("toc", "");
1740 void GuiView::updateDialog(string const & name, string const & sdata)
1742 if (!isDialogVisible(name))
1745 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1746 if (it == d.dialogs_.end())
1749 Dialog * const dialog = it->second.get();
1750 if (dialog->isVisibleView())
1751 dialog->initialiseParams(sdata);
1755 BufferView * GuiView::documentBufferView()
1757 return currentMainWorkArea()
1758 ? ¤tMainWorkArea()->bufferView()
1763 BufferView const * GuiView::documentBufferView() const
1765 return currentMainWorkArea()
1766 ? ¤tMainWorkArea()->bufferView()
1771 BufferView * GuiView::currentBufferView()
1773 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1777 BufferView const * GuiView::currentBufferView() const
1779 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1783 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1784 Buffer const * orig, Buffer * clone)
1786 bool const success = clone->autoSave();
1788 busyBuffers.remove(orig);
1790 ? _("Automatic save done.")
1791 : _("Automatic save failed!");
1795 void GuiView::autoSave()
1797 LYXERR(Debug::INFO, "Running autoSave()");
1799 Buffer * buffer = documentBufferView()
1800 ? &documentBufferView()->buffer() : 0;
1802 resetAutosaveTimers();
1806 GuiViewPrivate::busyBuffers.insert(buffer);
1807 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1808 buffer, buffer->cloneBufferOnly());
1809 d.autosave_watcher_.setFuture(f);
1810 resetAutosaveTimers();
1814 void GuiView::resetAutosaveTimers()
1817 d.autosave_timeout_.restart();
1821 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1824 Buffer * buf = currentBufferView()
1825 ? ¤tBufferView()->buffer() : 0;
1826 Buffer * doc_buffer = documentBufferView()
1827 ? &(documentBufferView()->buffer()) : 0;
1830 /* In LyX/Mac, when a dialog is open, the menus of the
1831 application can still be accessed without giving focus to
1832 the main window. In this case, we want to disable the menu
1833 entries that are buffer-related.
1834 This code must not be used on Linux and Windows, since it
1835 would disable buffer-related entries when hovering over the
1836 menu (see bug #9574).
1838 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1844 // Check whether we need a buffer
1845 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1846 // no, exit directly
1847 flag.message(from_utf8(N_("Command not allowed with"
1848 "out any document open")));
1849 flag.setEnabled(false);
1853 if (cmd.origin() == FuncRequest::TOC) {
1854 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1855 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1856 flag.setEnabled(false);
1860 switch(cmd.action()) {
1861 case LFUN_BUFFER_IMPORT:
1864 case LFUN_MASTER_BUFFER_UPDATE:
1865 case LFUN_MASTER_BUFFER_VIEW:
1867 && (doc_buffer->parent() != 0
1868 || doc_buffer->hasChildren())
1869 && !d.processing_thread_watcher_.isRunning();
1872 case LFUN_BUFFER_UPDATE:
1873 case LFUN_BUFFER_VIEW: {
1874 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1878 string format = to_utf8(cmd.argument());
1879 if (cmd.argument().empty())
1880 format = doc_buffer->params().getDefaultOutputFormat();
1881 enable = doc_buffer->params().isExportable(format, true);
1885 case LFUN_BUFFER_RELOAD:
1886 enable = doc_buffer && !doc_buffer->isUnnamed()
1887 && doc_buffer->fileName().exists()
1888 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1891 case LFUN_BUFFER_CHILD_OPEN:
1892 enable = doc_buffer != 0;
1895 case LFUN_BUFFER_WRITE:
1896 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1899 //FIXME: This LFUN should be moved to GuiApplication.
1900 case LFUN_BUFFER_WRITE_ALL: {
1901 // We enable the command only if there are some modified buffers
1902 Buffer * first = theBufferList().first();
1907 // We cannot use a for loop as the buffer list is a cycle.
1909 if (!b->isClean()) {
1913 b = theBufferList().next(b);
1914 } while (b != first);
1918 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1919 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1922 case LFUN_BUFFER_EXPORT: {
1923 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1927 return doc_buffer->getStatus(cmd, flag);
1931 case LFUN_BUFFER_EXPORT_AS:
1932 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1937 case LFUN_BUFFER_WRITE_AS:
1938 enable = doc_buffer != 0;
1941 case LFUN_EXPORT_CANCEL:
1942 enable = d.processing_thread_watcher_.isRunning();
1945 case LFUN_BUFFER_CLOSE:
1946 case LFUN_VIEW_CLOSE:
1947 enable = doc_buffer != 0;
1950 case LFUN_BUFFER_CLOSE_ALL:
1951 enable = theBufferList().last() != theBufferList().first();
1954 case LFUN_BUFFER_CHKTEX: {
1955 // hide if we have no checktex command
1956 if (lyxrc.chktex_command.empty()) {
1957 flag.setUnknown(true);
1961 if (!doc_buffer || !doc_buffer->params().isLatex()
1962 || d.processing_thread_watcher_.isRunning()) {
1963 // grey out, don't hide
1971 case LFUN_VIEW_SPLIT:
1972 if (cmd.getArg(0) == "vertical")
1973 enable = doc_buffer && (d.splitter_->count() == 1 ||
1974 d.splitter_->orientation() == Qt::Vertical);
1976 enable = doc_buffer && (d.splitter_->count() == 1 ||
1977 d.splitter_->orientation() == Qt::Horizontal);
1980 case LFUN_TAB_GROUP_CLOSE:
1981 enable = d.tabWorkAreaCount() > 1;
1984 case LFUN_DEVEL_MODE_TOGGLE:
1985 flag.setOnOff(devel_mode_);
1988 case LFUN_TOOLBAR_TOGGLE: {
1989 string const name = cmd.getArg(0);
1990 if (GuiToolbar * t = toolbar(name))
1991 flag.setOnOff(t->isVisible());
1994 docstring const msg =
1995 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2001 case LFUN_TOOLBAR_MOVABLE: {
2002 string const name = cmd.getArg(0);
2003 // use negation since locked == !movable
2005 // toolbar name * locks all toolbars
2006 flag.setOnOff(!toolbarsMovable_);
2007 else if (GuiToolbar * t = toolbar(name))
2008 flag.setOnOff(!(t->isMovable()));
2011 docstring const msg =
2012 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2018 case LFUN_ICON_SIZE:
2019 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2022 case LFUN_DROP_LAYOUTS_CHOICE:
2026 case LFUN_UI_TOGGLE:
2027 flag.setOnOff(isFullScreen());
2030 case LFUN_DIALOG_DISCONNECT_INSET:
2033 case LFUN_DIALOG_HIDE:
2034 // FIXME: should we check if the dialog is shown?
2037 case LFUN_DIALOG_TOGGLE:
2038 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2041 case LFUN_DIALOG_SHOW: {
2042 string const name = cmd.getArg(0);
2044 enable = name == "aboutlyx"
2045 || name == "file" //FIXME: should be removed.
2047 || name == "texinfo"
2048 || name == "progress"
2049 || name == "compare";
2050 else if (name == "character" || name == "symbols"
2051 || name == "mathdelimiter" || name == "mathmatrix") {
2052 if (!buf || buf->isReadonly())
2055 Cursor const & cur = currentBufferView()->cursor();
2056 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2059 else if (name == "latexlog")
2060 enable = FileName(doc_buffer->logName()).isReadableFile();
2061 else if (name == "spellchecker")
2062 enable = theSpellChecker()
2063 && !doc_buffer->isReadonly()
2064 && !doc_buffer->text().empty();
2065 else if (name == "vclog")
2066 enable = doc_buffer->lyxvc().inUse();
2070 case LFUN_DIALOG_UPDATE: {
2071 string const name = cmd.getArg(0);
2073 enable = name == "prefs";
2077 case LFUN_COMMAND_EXECUTE:
2079 case LFUN_MENU_OPEN:
2080 // Nothing to check.
2083 case LFUN_COMPLETION_INLINE:
2084 if (!d.current_work_area_
2085 || !d.current_work_area_->completer().inlinePossible(
2086 currentBufferView()->cursor()))
2090 case LFUN_COMPLETION_POPUP:
2091 if (!d.current_work_area_
2092 || !d.current_work_area_->completer().popupPossible(
2093 currentBufferView()->cursor()))
2098 if (!d.current_work_area_
2099 || !d.current_work_area_->completer().inlinePossible(
2100 currentBufferView()->cursor()))
2104 case LFUN_COMPLETION_ACCEPT:
2105 if (!d.current_work_area_
2106 || (!d.current_work_area_->completer().popupVisible()
2107 && !d.current_work_area_->completer().inlineVisible()
2108 && !d.current_work_area_->completer().completionAvailable()))
2112 case LFUN_COMPLETION_CANCEL:
2113 if (!d.current_work_area_
2114 || (!d.current_work_area_->completer().popupVisible()
2115 && !d.current_work_area_->completer().inlineVisible()))
2119 case LFUN_BUFFER_ZOOM_OUT:
2120 case LFUN_BUFFER_ZOOM_IN: {
2121 // only diff between these two is that the default for ZOOM_OUT
2123 bool const neg_zoom =
2124 convert<int>(cmd.argument()) < 0 ||
2125 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2126 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2127 docstring const msg =
2128 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2132 enable = doc_buffer;
2136 case LFUN_BUFFER_ZOOM: {
2137 bool const less_than_min_zoom =
2138 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2139 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2140 docstring const msg =
2141 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2146 enable = doc_buffer;
2150 case LFUN_BUFFER_MOVE_NEXT:
2151 case LFUN_BUFFER_MOVE_PREVIOUS:
2152 // we do not cycle when moving
2153 case LFUN_BUFFER_NEXT:
2154 case LFUN_BUFFER_PREVIOUS:
2155 // because we cycle, it doesn't matter whether on first or last
2156 enable = (d.currentTabWorkArea()->count() > 1);
2158 case LFUN_BUFFER_SWITCH:
2159 // toggle on the current buffer, but do not toggle off
2160 // the other ones (is that a good idea?)
2162 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2163 flag.setOnOff(true);
2166 case LFUN_VC_REGISTER:
2167 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2169 case LFUN_VC_RENAME:
2170 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2173 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2175 case LFUN_VC_CHECK_IN:
2176 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2178 case LFUN_VC_CHECK_OUT:
2179 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2181 case LFUN_VC_LOCKING_TOGGLE:
2182 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2183 && doc_buffer->lyxvc().lockingToggleEnabled();
2184 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2186 case LFUN_VC_REVERT:
2187 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2188 && !doc_buffer->hasReadonlyFlag();
2190 case LFUN_VC_UNDO_LAST:
2191 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2193 case LFUN_VC_REPO_UPDATE:
2194 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2196 case LFUN_VC_COMMAND: {
2197 if (cmd.argument().empty())
2199 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2203 case LFUN_VC_COMPARE:
2204 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2207 case LFUN_SERVER_GOTO_FILE_ROW:
2208 case LFUN_LYX_ACTIVATE:
2210 case LFUN_FORWARD_SEARCH:
2211 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2214 case LFUN_FILE_INSERT_PLAINTEXT:
2215 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2216 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2219 case LFUN_SPELLING_CONTINUOUSLY:
2220 flag.setOnOff(lyxrc.spellcheck_continuously);
2228 flag.setEnabled(false);
2234 static FileName selectTemplateFile()
2236 FileDialog dlg(qt_("Select template file"));
2237 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2238 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2240 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2241 QStringList(qt_("LyX Documents (*.lyx)")));
2243 if (result.first == FileDialog::Later)
2245 if (result.second.isEmpty())
2247 return FileName(fromqstr(result.second));
2251 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2255 Buffer * newBuffer = 0;
2257 newBuffer = checkAndLoadLyXFile(filename);
2258 } catch (ExceptionMessage const & e) {
2265 message(_("Document not loaded."));
2269 setBuffer(newBuffer);
2270 newBuffer->errors("Parse");
2273 theSession().lastFiles().add(filename);
2274 theSession().writeFile();
2281 void GuiView::openDocument(string const & fname)
2283 string initpath = lyxrc.document_path;
2285 if (documentBufferView()) {
2286 string const trypath = documentBufferView()->buffer().filePath();
2287 // If directory is writeable, use this as default.
2288 if (FileName(trypath).isDirWritable())
2294 if (fname.empty()) {
2295 FileDialog dlg(qt_("Select document to open"));
2296 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2297 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2299 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2300 FileDialog::Result result =
2301 dlg.open(toqstr(initpath), filter);
2303 if (result.first == FileDialog::Later)
2306 filename = fromqstr(result.second);
2308 // check selected filename
2309 if (filename.empty()) {
2310 message(_("Canceled."));
2316 // get absolute path of file and add ".lyx" to the filename if
2318 FileName const fullname =
2319 fileSearch(string(), filename, "lyx", support::may_not_exist);
2320 if (!fullname.empty())
2321 filename = fullname.absFileName();
2323 if (!fullname.onlyPath().isDirectory()) {
2324 Alert::warning(_("Invalid filename"),
2325 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2326 from_utf8(fullname.absFileName())));
2330 // if the file doesn't exist and isn't already open (bug 6645),
2331 // let the user create one
2332 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2333 !LyXVC::file_not_found_hook(fullname)) {
2334 // the user specifically chose this name. Believe him.
2335 Buffer * const b = newFile(filename, string(), true);
2341 docstring const disp_fn = makeDisplayPath(filename);
2342 message(bformat(_("Opening document %1$s..."), disp_fn));
2345 Buffer * buf = loadDocument(fullname);
2347 str2 = bformat(_("Document %1$s opened."), disp_fn);
2348 if (buf->lyxvc().inUse())
2349 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2350 " " + _("Version control detected.");
2352 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2357 // FIXME: clean that
2358 static bool import(GuiView * lv, FileName const & filename,
2359 string const & format, ErrorList & errorList)
2361 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2363 string loader_format;
2364 vector<string> loaders = theConverters().loaders();
2365 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2366 vector<string>::const_iterator it = loaders.begin();
2367 vector<string>::const_iterator en = loaders.end();
2368 for (; it != en; ++it) {
2369 if (!theConverters().isReachable(format, *it))
2372 string const tofile =
2373 support::changeExtension(filename.absFileName(),
2374 theFormats().extension(*it));
2375 if (theConverters().convert(0, filename, FileName(tofile),
2376 filename, format, *it, errorList) != Converters::SUCCESS)
2378 loader_format = *it;
2381 if (loader_format.empty()) {
2382 frontend::Alert::error(_("Couldn't import file"),
2383 bformat(_("No information for importing the format %1$s."),
2384 theFormats().prettyName(format)));
2388 loader_format = format;
2390 if (loader_format == "lyx") {
2391 Buffer * buf = lv->loadDocument(lyxfile);
2395 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2399 bool as_paragraphs = loader_format == "textparagraph";
2400 string filename2 = (loader_format == format) ? filename.absFileName()
2401 : support::changeExtension(filename.absFileName(),
2402 theFormats().extension(loader_format));
2403 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2405 guiApp->setCurrentView(lv);
2406 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2413 void GuiView::importDocument(string const & argument)
2416 string filename = split(argument, format, ' ');
2418 LYXERR(Debug::INFO, format << " file: " << filename);
2420 // need user interaction
2421 if (filename.empty()) {
2422 string initpath = lyxrc.document_path;
2423 if (documentBufferView()) {
2424 string const trypath = documentBufferView()->buffer().filePath();
2425 // If directory is writeable, use this as default.
2426 if (FileName(trypath).isDirWritable())
2430 docstring const text = bformat(_("Select %1$s file to import"),
2431 theFormats().prettyName(format));
2433 FileDialog dlg(toqstr(text));
2434 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2435 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2437 docstring filter = theFormats().prettyName(format);
2440 filter += from_utf8(theFormats().extensions(format));
2443 FileDialog::Result result =
2444 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2446 if (result.first == FileDialog::Later)
2449 filename = fromqstr(result.second);
2451 // check selected filename
2452 if (filename.empty())
2453 message(_("Canceled."));
2456 if (filename.empty())
2459 // get absolute path of file
2460 FileName const fullname(support::makeAbsPath(filename));
2462 // Can happen if the user entered a path into the dialog
2464 if (fullname.onlyFileName().empty()) {
2465 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2466 "Aborting import."),
2467 from_utf8(fullname.absFileName()));
2468 frontend::Alert::error(_("File name error"), msg);
2469 message(_("Canceled."));
2474 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2476 // Check if the document already is open
2477 Buffer * buf = theBufferList().getBuffer(lyxfile);
2480 if (!closeBuffer()) {
2481 message(_("Canceled."));
2486 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2488 // if the file exists already, and we didn't do
2489 // -i lyx thefile.lyx, warn
2490 if (lyxfile.exists() && fullname != lyxfile) {
2492 docstring text = bformat(_("The document %1$s already exists.\n\n"
2493 "Do you want to overwrite that document?"), displaypath);
2494 int const ret = Alert::prompt(_("Overwrite document?"),
2495 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2498 message(_("Canceled."));
2503 message(bformat(_("Importing %1$s..."), displaypath));
2504 ErrorList errorList;
2505 if (import(this, fullname, format, errorList))
2506 message(_("imported."));
2508 message(_("file not imported!"));
2510 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2514 void GuiView::newDocument(string const & filename, bool from_template)
2516 FileName initpath(lyxrc.document_path);
2517 if (documentBufferView()) {
2518 FileName const trypath(documentBufferView()->buffer().filePath());
2519 // If directory is writeable, use this as default.
2520 if (trypath.isDirWritable())
2524 string templatefile;
2525 if (from_template) {
2526 templatefile = selectTemplateFile().absFileName();
2527 if (templatefile.empty())
2532 if (filename.empty())
2533 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2535 b = newFile(filename, templatefile, true);
2540 // If no new document could be created, it is unsure
2541 // whether there is a valid BufferView.
2542 if (currentBufferView())
2543 // Ensure the cursor is correctly positioned on screen.
2544 currentBufferView()->showCursor();
2548 void GuiView::insertLyXFile(docstring const & fname)
2550 BufferView * bv = documentBufferView();
2555 FileName filename(to_utf8(fname));
2556 if (filename.empty()) {
2557 // Launch a file browser
2559 string initpath = lyxrc.document_path;
2560 string const trypath = bv->buffer().filePath();
2561 // If directory is writeable, use this as default.
2562 if (FileName(trypath).isDirWritable())
2566 FileDialog dlg(qt_("Select LyX document to insert"));
2567 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2568 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2570 FileDialog::Result result = dlg.open(toqstr(initpath),
2571 QStringList(qt_("LyX Documents (*.lyx)")));
2573 if (result.first == FileDialog::Later)
2577 filename.set(fromqstr(result.second));
2579 // check selected filename
2580 if (filename.empty()) {
2581 // emit message signal.
2582 message(_("Canceled."));
2587 bv->insertLyXFile(filename);
2588 bv->buffer().errors("Parse");
2592 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2594 FileName fname = b.fileName();
2595 FileName const oldname = fname;
2597 if (!newname.empty()) {
2599 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2601 // Switch to this Buffer.
2604 // No argument? Ask user through dialog.
2606 FileDialog dlg(qt_("Choose a filename to save document as"));
2607 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2608 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2610 if (!isLyXFileName(fname.absFileName()))
2611 fname.changeExtension(".lyx");
2613 FileDialog::Result result =
2614 dlg.save(toqstr(fname.onlyPath().absFileName()),
2615 QStringList(qt_("LyX Documents (*.lyx)")),
2616 toqstr(fname.onlyFileName()));
2618 if (result.first == FileDialog::Later)
2621 fname.set(fromqstr(result.second));
2626 if (!isLyXFileName(fname.absFileName()))
2627 fname.changeExtension(".lyx");
2630 // fname is now the new Buffer location.
2632 // if there is already a Buffer open with this name, we do not want
2633 // to have another one. (the second test makes sure we're not just
2634 // trying to overwrite ourselves, which is fine.)
2635 if (theBufferList().exists(fname) && fname != oldname
2636 && theBufferList().getBuffer(fname) != &b) {
2637 docstring const text =
2638 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2639 "Please close it before attempting to overwrite it.\n"
2640 "Do you want to choose a new filename?"),
2641 from_utf8(fname.absFileName()));
2642 int const ret = Alert::prompt(_("Chosen File Already Open"),
2643 text, 0, 1, _("&Rename"), _("&Cancel"));
2645 case 0: return renameBuffer(b, docstring(), kind);
2646 case 1: return false;
2651 bool const existsLocal = fname.exists();
2652 bool const existsInVC = LyXVC::fileInVC(fname);
2653 if (existsLocal || existsInVC) {
2654 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2655 if (kind != LV_WRITE_AS && existsInVC) {
2656 // renaming to a name that is already in VC
2658 docstring text = bformat(_("The document %1$s "
2659 "is already registered.\n\n"
2660 "Do you want to choose a new name?"),
2662 docstring const title = (kind == LV_VC_RENAME) ?
2663 _("Rename document?") : _("Copy document?");
2664 docstring const button = (kind == LV_VC_RENAME) ?
2665 _("&Rename") : _("&Copy");
2666 int const ret = Alert::prompt(title, text, 0, 1,
2667 button, _("&Cancel"));
2669 case 0: return renameBuffer(b, docstring(), kind);
2670 case 1: return false;
2675 docstring text = bformat(_("The document %1$s "
2676 "already exists.\n\n"
2677 "Do you want to overwrite that document?"),
2679 int const ret = Alert::prompt(_("Overwrite document?"),
2680 text, 0, 2, _("&Overwrite"),
2681 _("&Rename"), _("&Cancel"));
2684 case 1: return renameBuffer(b, docstring(), kind);
2685 case 2: return false;
2691 case LV_VC_RENAME: {
2692 string msg = b.lyxvc().rename(fname);
2695 message(from_utf8(msg));
2699 string msg = b.lyxvc().copy(fname);
2702 message(from_utf8(msg));
2708 // LyXVC created the file already in case of LV_VC_RENAME or
2709 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2710 // relative paths of included stuff right if we moved e.g. from
2711 // /a/b.lyx to /a/c/b.lyx.
2713 bool const saved = saveBuffer(b, fname);
2720 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2722 FileName fname = b.fileName();
2724 FileDialog dlg(qt_("Choose a filename to export the document as"));
2725 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2728 QString const anyformat = qt_("Guess from extension (*.*)");
2731 vector<Format const *> export_formats;
2732 for (Format const & f : theFormats())
2733 if (f.documentFormat())
2734 export_formats.push_back(&f);
2735 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2736 map<QString, string> fmap;
2739 for (Format const * f : export_formats) {
2740 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2741 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2743 from_ascii(f->extension())));
2744 types << loc_filter;
2745 fmap[loc_filter] = f->name();
2746 if (from_ascii(f->name()) == iformat) {
2747 filter = loc_filter;
2748 ext = f->extension();
2751 string ofname = fname.onlyFileName();
2753 ofname = support::changeExtension(ofname, ext);
2754 FileDialog::Result result =
2755 dlg.save(toqstr(fname.onlyPath().absFileName()),
2759 if (result.first != FileDialog::Chosen)
2763 fname.set(fromqstr(result.second));
2764 if (filter == anyformat)
2765 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2767 fmt_name = fmap[filter];
2768 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2769 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2771 if (fmt_name.empty() || fname.empty())
2774 // fname is now the new Buffer location.
2775 if (FileName(fname).exists()) {
2776 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2777 docstring text = bformat(_("The document %1$s already "
2778 "exists.\n\nDo you want to "
2779 "overwrite that document?"),
2781 int const ret = Alert::prompt(_("Overwrite document?"),
2782 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2785 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2786 case 2: return false;
2790 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2793 return dr.dispatched();
2797 bool GuiView::saveBuffer(Buffer & b)
2799 return saveBuffer(b, FileName());
2803 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2805 if (workArea(b) && workArea(b)->inDialogMode())
2808 if (fn.empty() && b.isUnnamed())
2809 return renameBuffer(b, docstring());
2811 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2813 theSession().lastFiles().add(b.fileName());
2814 theSession().writeFile();
2818 // Switch to this Buffer.
2821 // FIXME: we don't tell the user *WHY* the save failed !!
2822 docstring const file = makeDisplayPath(b.absFileName(), 30);
2823 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2824 "Do you want to rename the document and "
2825 "try again?"), file);
2826 int const ret = Alert::prompt(_("Rename and save?"),
2827 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2830 if (!renameBuffer(b, docstring()))
2839 return saveBuffer(b, fn);
2843 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2845 return closeWorkArea(wa, false);
2849 // We only want to close the buffer if it is not visible in other workareas
2850 // of the same view, nor in other views, and if this is not a child
2851 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2853 Buffer & buf = wa->bufferView().buffer();
2855 bool last_wa = d.countWorkAreasOf(buf) == 1
2856 && !inOtherView(buf) && !buf.parent();
2858 bool close_buffer = last_wa;
2861 if (lyxrc.close_buffer_with_last_view == "yes")
2863 else if (lyxrc.close_buffer_with_last_view == "no")
2864 close_buffer = false;
2867 if (buf.isUnnamed())
2868 file = from_utf8(buf.fileName().onlyFileName());
2870 file = buf.fileName().displayName(30);
2871 docstring const text = bformat(
2872 _("Last view on document %1$s is being closed.\n"
2873 "Would you like to close or hide the document?\n"
2875 "Hidden documents can be displayed back through\n"
2876 "the menu: View->Hidden->...\n"
2878 "To remove this question, set your preference in:\n"
2879 " Tools->Preferences->Look&Feel->UserInterface\n"
2881 int ret = Alert::prompt(_("Close or hide document?"),
2882 text, 0, 1, _("&Close"), _("&Hide"));
2883 close_buffer = (ret == 0);
2887 return closeWorkArea(wa, close_buffer);
2891 bool GuiView::closeBuffer()
2893 GuiWorkArea * wa = currentMainWorkArea();
2894 // coverity complained about this
2895 // it seems unnecessary, but perhaps is worth the check
2896 LASSERT(wa, return false);
2898 setCurrentWorkArea(wa);
2899 Buffer & buf = wa->bufferView().buffer();
2900 return closeWorkArea(wa, !buf.parent());
2904 void GuiView::writeSession() const {
2905 GuiWorkArea const * active_wa = currentMainWorkArea();
2906 for (int i = 0; i < d.splitter_->count(); ++i) {
2907 TabWorkArea * twa = d.tabWorkArea(i);
2908 for (int j = 0; j < twa->count(); ++j) {
2909 GuiWorkArea * wa = twa->workArea(j);
2910 Buffer & buf = wa->bufferView().buffer();
2911 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2917 bool GuiView::closeBufferAll()
2919 // Close the workareas in all other views
2920 QList<int> const ids = guiApp->viewIds();
2921 for (int i = 0; i != ids.size(); ++i) {
2922 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2926 // Close our own workareas
2927 if (!closeWorkAreaAll())
2930 // Now close the hidden buffers. We prevent hidden buffers from being
2931 // dirty, so we can just close them.
2932 theBufferList().closeAll();
2937 bool GuiView::closeWorkAreaAll()
2939 setCurrentWorkArea(currentMainWorkArea());
2941 // We might be in a situation that there is still a tabWorkArea, but
2942 // there are no tabs anymore. This can happen when we get here after a
2943 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2944 // many TabWorkArea's have no documents anymore.
2947 // We have to call count() each time, because it can happen that
2948 // more than one splitter will disappear in one iteration (bug 5998).
2949 while (d.splitter_->count() > empty_twa) {
2950 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2952 if (twa->count() == 0)
2955 setCurrentWorkArea(twa->currentWorkArea());
2956 if (!closeTabWorkArea(twa))
2964 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2969 Buffer & buf = wa->bufferView().buffer();
2971 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2972 Alert::warning(_("Close document"),
2973 _("Document could not be closed because it is being processed by LyX."));
2978 return closeBuffer(buf);
2980 if (!inMultiTabs(wa))
2981 if (!saveBufferIfNeeded(buf, true))
2989 bool GuiView::closeBuffer(Buffer & buf)
2991 // If we are in a close_event all children will be closed in some time,
2992 // so no need to do it here. This will ensure that the children end up
2993 // in the session file in the correct order. If we close the master
2994 // buffer, we can close or release the child buffers here too.
2995 bool success = true;
2997 ListOfBuffers clist = buf.getChildren();
2998 ListOfBuffers::const_iterator it = clist.begin();
2999 ListOfBuffers::const_iterator const bend = clist.end();
3000 for (; it != bend; ++it) {
3001 Buffer * child_buf = *it;
3002 if (theBufferList().isOthersChild(&buf, child_buf)) {
3003 child_buf->setParent(0);
3007 // FIXME: should we look in other tabworkareas?
3008 // ANSWER: I don't think so. I've tested, and if the child is
3009 // open in some other window, it closes without a problem.
3010 GuiWorkArea * child_wa = workArea(*child_buf);
3012 success = closeWorkArea(child_wa, true);
3016 // In this case the child buffer is open but hidden.
3017 // It therefore should not (MUST NOT) be dirty!
3018 LATTEST(child_buf->isClean());
3019 theBufferList().release(child_buf);
3024 // goto bookmark to update bookmark pit.
3025 // FIXME: we should update only the bookmarks related to this buffer!
3026 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3027 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3028 guiApp->gotoBookmark(i+1, false, false);
3030 if (saveBufferIfNeeded(buf, false)) {
3031 buf.removeAutosaveFile();
3032 theBufferList().release(&buf);
3036 // open all children again to avoid a crash because of dangling
3037 // pointers (bug 6603)
3043 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3045 while (twa == d.currentTabWorkArea()) {
3046 twa->setCurrentIndex(twa->count() - 1);
3048 GuiWorkArea * wa = twa->currentWorkArea();
3049 Buffer & b = wa->bufferView().buffer();
3051 // We only want to close the buffer if the same buffer is not visible
3052 // in another view, and if this is not a child and if we are closing
3053 // a view (not a tabgroup).
3054 bool const close_buffer =
3055 !inOtherView(b) && !b.parent() && closing_;
3057 if (!closeWorkArea(wa, close_buffer))
3064 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3066 if (buf.isClean() || buf.paragraphs().empty())
3069 // Switch to this Buffer.
3075 if (buf.isUnnamed()) {
3076 file = from_utf8(buf.fileName().onlyFileName());
3079 FileName filename = buf.fileName();
3081 file = filename.displayName(30);
3082 exists = filename.exists();
3085 // Bring this window to top before asking questions.
3090 if (hiding && buf.isUnnamed()) {
3091 docstring const text = bformat(_("The document %1$s has not been "
3092 "saved yet.\n\nDo you want to save "
3093 "the document?"), file);
3094 ret = Alert::prompt(_("Save new document?"),
3095 text, 0, 1, _("&Save"), _("&Cancel"));
3099 docstring const text = exists ?
3100 bformat(_("The document %1$s has unsaved changes."
3101 "\n\nDo you want to save the document or "
3102 "discard the changes?"), file) :
3103 bformat(_("The document %1$s has not been saved yet."
3104 "\n\nDo you want to save the document or "
3105 "discard it entirely?"), file);
3106 docstring const title = exists ?
3107 _("Save changed document?") : _("Save document?");
3108 ret = Alert::prompt(title, text, 0, 2,
3109 _("&Save"), _("&Discard"), _("&Cancel"));
3114 if (!saveBuffer(buf))
3118 // If we crash after this we could have no autosave file
3119 // but I guess this is really improbable (Jug).
3120 // Sometimes improbable things happen:
3121 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3122 // buf.removeAutosaveFile();
3124 // revert all changes
3135 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3137 Buffer & buf = wa->bufferView().buffer();
3139 for (int i = 0; i != d.splitter_->count(); ++i) {
3140 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3141 if (wa_ && wa_ != wa)
3144 return inOtherView(buf);
3148 bool GuiView::inOtherView(Buffer & buf)
3150 QList<int> const ids = guiApp->viewIds();
3152 for (int i = 0; i != ids.size(); ++i) {
3156 if (guiApp->view(ids[i]).workArea(buf))
3163 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3165 if (!documentBufferView())
3168 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3169 Buffer * const curbuf = &documentBufferView()->buffer();
3170 int nwa = twa->count();
3171 for (int i = 0; i < nwa; ++i) {
3172 if (&workArea(i)->bufferView().buffer() == curbuf) {
3174 if (np == NEXTBUFFER)
3175 next_index = (i == nwa - 1 ? 0 : i + 1);
3177 next_index = (i == 0 ? nwa - 1 : i - 1);
3179 twa->moveTab(i, next_index);
3181 setBuffer(&workArea(next_index)->bufferView().buffer());
3189 /// make sure the document is saved
3190 static bool ensureBufferClean(Buffer * buffer)
3192 LASSERT(buffer, return false);
3193 if (buffer->isClean() && !buffer->isUnnamed())
3196 docstring const file = buffer->fileName().displayName(30);
3199 if (!buffer->isUnnamed()) {
3200 text = bformat(_("The document %1$s has unsaved "
3201 "changes.\n\nDo you want to save "
3202 "the document?"), file);
3203 title = _("Save changed document?");
3206 text = bformat(_("The document %1$s has not been "
3207 "saved yet.\n\nDo you want to save "
3208 "the document?"), file);
3209 title = _("Save new document?");
3211 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3214 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3216 return buffer->isClean() && !buffer->isUnnamed();
3220 bool GuiView::reloadBuffer(Buffer & buf)
3222 currentBufferView()->cursor().reset();
3223 Buffer::ReadStatus status = buf.reload();
3224 return status == Buffer::ReadSuccess;
3228 void GuiView::checkExternallyModifiedBuffers()
3230 BufferList::iterator bit = theBufferList().begin();
3231 BufferList::iterator const bend = theBufferList().end();
3232 for (; bit != bend; ++bit) {
3233 Buffer * buf = *bit;
3234 if (buf->fileName().exists() && buf->isChecksumModified()) {
3235 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3236 " Reload now? Any local changes will be lost."),
3237 from_utf8(buf->absFileName()));
3238 int const ret = Alert::prompt(_("Reload externally changed document?"),
3239 text, 0, 1, _("&Reload"), _("&Cancel"));
3247 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3249 Buffer * buffer = documentBufferView()
3250 ? &(documentBufferView()->buffer()) : 0;
3252 switch (cmd.action()) {
3253 case LFUN_VC_REGISTER:
3254 if (!buffer || !ensureBufferClean(buffer))
3256 if (!buffer->lyxvc().inUse()) {
3257 if (buffer->lyxvc().registrer()) {
3258 reloadBuffer(*buffer);
3259 dr.clearMessageUpdate();
3264 case LFUN_VC_RENAME:
3265 case LFUN_VC_COPY: {
3266 if (!buffer || !ensureBufferClean(buffer))
3268 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3269 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3270 // Some changes are not yet committed.
3271 // We test here and not in getStatus(), since
3272 // this test is expensive.
3274 LyXVC::CommandResult ret =
3275 buffer->lyxvc().checkIn(log);
3277 if (ret == LyXVC::ErrorCommand ||
3278 ret == LyXVC::VCSuccess)
3279 reloadBuffer(*buffer);
3280 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3281 frontend::Alert::error(
3282 _("Revision control error."),
3283 _("Document could not be checked in."));
3287 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3288 LV_VC_RENAME : LV_VC_COPY;
3289 renameBuffer(*buffer, cmd.argument(), kind);
3294 case LFUN_VC_CHECK_IN:
3295 if (!buffer || !ensureBufferClean(buffer))
3297 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3299 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3301 // Only skip reloading if the checkin was cancelled or
3302 // an error occurred before the real checkin VCS command
3303 // was executed, since the VCS might have changed the
3304 // file even if it could not checkin successfully.
3305 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3306 reloadBuffer(*buffer);
3310 case LFUN_VC_CHECK_OUT:
3311 if (!buffer || !ensureBufferClean(buffer))
3313 if (buffer->lyxvc().inUse()) {
3314 dr.setMessage(buffer->lyxvc().checkOut());
3315 reloadBuffer(*buffer);
3319 case LFUN_VC_LOCKING_TOGGLE:
3320 LASSERT(buffer, return);
3321 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3323 if (buffer->lyxvc().inUse()) {
3324 string res = buffer->lyxvc().lockingToggle();
3326 frontend::Alert::error(_("Revision control error."),
3327 _("Error when setting the locking property."));
3330 reloadBuffer(*buffer);
3335 case LFUN_VC_REVERT:
3336 LASSERT(buffer, return);
3337 if (buffer->lyxvc().revert()) {
3338 reloadBuffer(*buffer);
3339 dr.clearMessageUpdate();
3343 case LFUN_VC_UNDO_LAST:
3344 LASSERT(buffer, return);
3345 buffer->lyxvc().undoLast();
3346 reloadBuffer(*buffer);
3347 dr.clearMessageUpdate();
3350 case LFUN_VC_REPO_UPDATE:
3351 LASSERT(buffer, return);
3352 if (ensureBufferClean(buffer)) {
3353 dr.setMessage(buffer->lyxvc().repoUpdate());
3354 checkExternallyModifiedBuffers();
3358 case LFUN_VC_COMMAND: {
3359 string flag = cmd.getArg(0);
3360 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3363 if (contains(flag, 'M')) {
3364 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3367 string path = cmd.getArg(1);
3368 if (contains(path, "$$p") && buffer)
3369 path = subst(path, "$$p", buffer->filePath());
3370 LYXERR(Debug::LYXVC, "Directory: " << path);
3372 if (!pp.isReadableDirectory()) {
3373 lyxerr << _("Directory is not accessible.") << endl;
3376 support::PathChanger p(pp);
3378 string command = cmd.getArg(2);
3379 if (command.empty())
3382 command = subst(command, "$$i", buffer->absFileName());
3383 command = subst(command, "$$p", buffer->filePath());
3385 command = subst(command, "$$m", to_utf8(message));
3386 LYXERR(Debug::LYXVC, "Command: " << command);
3388 one.startscript(Systemcall::Wait, command);
3392 if (contains(flag, 'I'))
3393 buffer->markDirty();
3394 if (contains(flag, 'R'))
3395 reloadBuffer(*buffer);
3400 case LFUN_VC_COMPARE: {
3401 if (cmd.argument().empty()) {
3402 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3406 string rev1 = cmd.getArg(0);
3411 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3414 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3415 f2 = buffer->absFileName();
3417 string rev2 = cmd.getArg(1);
3421 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3425 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3426 f1 << "\n" << f2 << "\n" );
3427 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3428 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3438 void GuiView::openChildDocument(string const & fname)
3440 LASSERT(documentBufferView(), return);
3441 Buffer & buffer = documentBufferView()->buffer();
3442 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3443 documentBufferView()->saveBookmark(false);
3445 if (theBufferList().exists(filename)) {
3446 child = theBufferList().getBuffer(filename);
3449 message(bformat(_("Opening child document %1$s..."),
3450 makeDisplayPath(filename.absFileName())));
3451 child = loadDocument(filename, false);
3453 // Set the parent name of the child document.
3454 // This makes insertion of citations and references in the child work,
3455 // when the target is in the parent or another child document.
3457 child->setParent(&buffer);
3461 bool GuiView::goToFileRow(string const & argument)
3465 size_t i = argument.find_last_of(' ');
3466 if (i != string::npos) {
3467 file_name = os::internal_path(trim(argument.substr(0, i)));
3468 istringstream is(argument.substr(i + 1));
3473 if (i == string::npos) {
3474 LYXERR0("Wrong argument: " << argument);
3478 string const abstmp = package().temp_dir().absFileName();
3479 string const realtmp = package().temp_dir().realPath();
3480 // We have to use os::path_prefix_is() here, instead of
3481 // simply prefixIs(), because the file name comes from
3482 // an external application and may need case adjustment.
3483 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3484 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3485 // Needed by inverse dvi search. If it is a file
3486 // in tmpdir, call the apropriated function.
3487 // If tmpdir is a symlink, we may have the real
3488 // path passed back, so we correct for that.
3489 if (!prefixIs(file_name, abstmp))
3490 file_name = subst(file_name, realtmp, abstmp);
3491 buf = theBufferList().getBufferFromTmp(file_name);
3493 // Must replace extension of the file to be .lyx
3494 // and get full path
3495 FileName const s = fileSearch(string(),
3496 support::changeExtension(file_name, ".lyx"), "lyx");
3497 // Either change buffer or load the file
3498 if (theBufferList().exists(s))
3499 buf = theBufferList().getBuffer(s);
3500 else if (s.exists()) {
3501 buf = loadDocument(s);
3506 _("File does not exist: %1$s"),
3507 makeDisplayPath(file_name)));
3513 _("No buffer for file: %1$s."),
3514 makeDisplayPath(file_name))
3519 bool success = documentBufferView()->setCursorFromRow(row);
3521 LYXERR(Debug::LATEX,
3522 "setCursorFromRow: invalid position for row " << row);
3523 frontend::Alert::error(_("Inverse Search Failed"),
3524 _("Invalid position requested by inverse search.\n"
3525 "You may need to update the viewed document."));
3531 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3533 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3534 menu->exec(QCursor::pos());
3539 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3541 Buffer::ExportStatus const status = func(format);
3543 // the cloning operation will have produced a clone of the entire set of
3544 // documents, starting from the master. so we must delete those.
3545 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3547 busyBuffers.remove(orig);
3552 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3554 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3555 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3559 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3561 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3562 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3566 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3568 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3569 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3573 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3574 string const & argument,
3575 Buffer const * used_buffer,
3576 docstring const & msg,
3577 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3578 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3579 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3584 string format = argument;
3586 format = used_buffer->params().getDefaultOutputFormat();
3587 processing_format = format;
3589 progress_->clearMessages();
3592 #if EXPORT_in_THREAD
3593 GuiViewPrivate::busyBuffers.insert(used_buffer);
3594 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3595 if (!cloned_buffer) {
3596 Alert::error(_("Export Error"),
3597 _("Error cloning the Buffer."));
3600 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3605 setPreviewFuture(f);
3606 last_export_format = used_buffer->params().bufferFormat();
3609 // We are asynchronous, so we don't know here anything about the success
3612 Buffer::ExportStatus status;
3614 status = (used_buffer->*syncFunc)(format, true);
3615 } else if (previewFunc) {
3616 status = (used_buffer->*previewFunc)(format);
3619 handleExportStatus(gv_, status, format);
3621 return (status == Buffer::ExportSuccess
3622 || status == Buffer::PreviewSuccess);
3626 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3628 BufferView * bv = currentBufferView();
3629 LASSERT(bv, return);
3631 // Let the current BufferView dispatch its own actions.
3632 bv->dispatch(cmd, dr);
3633 if (dr.dispatched())
3636 // Try with the document BufferView dispatch if any.
3637 BufferView * doc_bv = documentBufferView();
3638 if (doc_bv && doc_bv != bv) {
3639 doc_bv->dispatch(cmd, dr);
3640 if (dr.dispatched())
3644 // Then let the current Cursor dispatch its own actions.
3645 bv->cursor().dispatch(cmd);
3647 // update completion. We do it here and not in
3648 // processKeySym to avoid another redraw just for a
3649 // changed inline completion
3650 if (cmd.origin() == FuncRequest::KEYBOARD) {
3651 if (cmd.action() == LFUN_SELF_INSERT
3652 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3653 updateCompletion(bv->cursor(), true, true);
3654 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3655 updateCompletion(bv->cursor(), false, true);
3657 updateCompletion(bv->cursor(), false, false);
3660 dr = bv->cursor().result();
3664 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3666 BufferView * bv = currentBufferView();
3667 // By default we won't need any update.
3668 dr.screenUpdate(Update::None);
3669 // assume cmd will be dispatched
3670 dr.dispatched(true);
3672 Buffer * doc_buffer = documentBufferView()
3673 ? &(documentBufferView()->buffer()) : 0;
3675 if (cmd.origin() == FuncRequest::TOC) {
3676 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3677 // FIXME: do we need to pass a DispatchResult object here?
3678 toc->doDispatch(bv->cursor(), cmd);
3682 string const argument = to_utf8(cmd.argument());
3684 switch(cmd.action()) {
3685 case LFUN_BUFFER_CHILD_OPEN:
3686 openChildDocument(to_utf8(cmd.argument()));
3689 case LFUN_BUFFER_IMPORT:
3690 importDocument(to_utf8(cmd.argument()));
3693 case LFUN_BUFFER_EXPORT: {
3696 // GCC only sees strfwd.h when building merged
3697 if (::lyx::operator==(cmd.argument(), "custom")) {
3698 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3702 string const dest = cmd.getArg(1);
3703 FileName target_dir;
3704 if (!dest.empty() && FileName::isAbsolute(dest))
3705 target_dir = FileName(support::onlyPath(dest));
3707 target_dir = doc_buffer->fileName().onlyPath();
3709 string const format = (argument.empty() || argument == "default") ?
3710 doc_buffer->params().getDefaultOutputFormat() : argument;
3712 if ((dest.empty() && doc_buffer->isUnnamed())
3713 || !target_dir.isDirWritable()) {
3714 exportBufferAs(*doc_buffer, from_utf8(format));
3717 /* TODO/Review: Is it a problem to also export the children?
3718 See the update_unincluded flag */
3719 d.asyncBufferProcessing(format,
3722 &GuiViewPrivate::exportAndDestroy,
3725 // TODO Inform user about success
3729 case LFUN_BUFFER_EXPORT_AS: {
3730 LASSERT(doc_buffer, break);
3731 docstring f = cmd.argument();
3733 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3734 exportBufferAs(*doc_buffer, f);
3738 case LFUN_BUFFER_UPDATE: {
3739 d.asyncBufferProcessing(argument,
3742 &GuiViewPrivate::compileAndDestroy,
3747 case LFUN_BUFFER_VIEW: {
3748 d.asyncBufferProcessing(argument,
3750 _("Previewing ..."),
3751 &GuiViewPrivate::previewAndDestroy,
3756 case LFUN_MASTER_BUFFER_UPDATE: {
3757 d.asyncBufferProcessing(argument,
3758 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3760 &GuiViewPrivate::compileAndDestroy,
3765 case LFUN_MASTER_BUFFER_VIEW: {
3766 d.asyncBufferProcessing(argument,
3767 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3769 &GuiViewPrivate::previewAndDestroy,
3770 0, &Buffer::preview);
3773 case LFUN_EXPORT_CANCEL: {
3774 Systemcall::killscript();
3777 case LFUN_BUFFER_SWITCH: {
3778 string const file_name = to_utf8(cmd.argument());
3779 if (!FileName::isAbsolute(file_name)) {
3781 dr.setMessage(_("Absolute filename expected."));
3785 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3788 dr.setMessage(_("Document not loaded"));
3792 // Do we open or switch to the buffer in this view ?
3793 if (workArea(*buffer)
3794 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3799 // Look for the buffer in other views
3800 QList<int> const ids = guiApp->viewIds();
3802 for (; i != ids.size(); ++i) {
3803 GuiView & gv = guiApp->view(ids[i]);
3804 if (gv.workArea(*buffer)) {
3806 gv.activateWindow();
3808 gv.setBuffer(buffer);
3813 // If necessary, open a new window as a last resort
3814 if (i == ids.size()) {
3815 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3821 case LFUN_BUFFER_NEXT:
3822 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3825 case LFUN_BUFFER_MOVE_NEXT:
3826 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3829 case LFUN_BUFFER_PREVIOUS:
3830 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3833 case LFUN_BUFFER_MOVE_PREVIOUS:
3834 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3837 case LFUN_BUFFER_CHKTEX:
3838 LASSERT(doc_buffer, break);
3839 doc_buffer->runChktex();
3842 case LFUN_COMMAND_EXECUTE: {
3843 command_execute_ = true;
3844 minibuffer_focus_ = true;
3847 case LFUN_DROP_LAYOUTS_CHOICE:
3848 d.layout_->showPopup();
3851 case LFUN_MENU_OPEN:
3852 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3853 menu->exec(QCursor::pos());
3856 case LFUN_FILE_INSERT:
3857 insertLyXFile(cmd.argument());
3860 case LFUN_FILE_INSERT_PLAINTEXT:
3861 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3862 string const fname = to_utf8(cmd.argument());
3863 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3864 dr.setMessage(_("Absolute filename expected."));
3868 FileName filename(fname);
3869 if (fname.empty()) {
3870 FileDialog dlg(qt_("Select file to insert"));
3872 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3873 QStringList(qt_("All Files (*)")));
3875 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3876 dr.setMessage(_("Canceled."));
3880 filename.set(fromqstr(result.second));
3884 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3885 bv->dispatch(new_cmd, dr);
3890 case LFUN_BUFFER_RELOAD: {
3891 LASSERT(doc_buffer, break);
3894 bool drop = (cmd.argument()=="dump");
3897 if (!drop && !doc_buffer->isClean()) {
3898 docstring const file =
3899 makeDisplayPath(doc_buffer->absFileName(), 20);
3900 if (doc_buffer->notifiesExternalModification()) {
3901 docstring text = _("The current version will be lost. "
3902 "Are you sure you want to load the version on disk "
3903 "of the document %1$s?");
3904 ret = Alert::prompt(_("Reload saved document?"),
3905 bformat(text, file), 1, 1,
3906 _("&Reload"), _("&Cancel"));
3908 docstring text = _("Any changes will be lost. "
3909 "Are you sure you want to revert to the saved version "
3910 "of the document %1$s?");
3911 ret = Alert::prompt(_("Revert to saved document?"),
3912 bformat(text, file), 1, 1,
3913 _("&Revert"), _("&Cancel"));
3918 doc_buffer->markClean();
3919 reloadBuffer(*doc_buffer);
3920 dr.forceBufferUpdate();
3925 case LFUN_BUFFER_WRITE:
3926 LASSERT(doc_buffer, break);
3927 saveBuffer(*doc_buffer);
3930 case LFUN_BUFFER_WRITE_AS:
3931 LASSERT(doc_buffer, break);
3932 renameBuffer(*doc_buffer, cmd.argument());
3935 case LFUN_BUFFER_WRITE_ALL: {
3936 Buffer * first = theBufferList().first();
3939 message(_("Saving all documents..."));
3940 // We cannot use a for loop as the buffer list cycles.
3943 if (!b->isClean()) {
3945 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3947 b = theBufferList().next(b);
3948 } while (b != first);
3949 dr.setMessage(_("All documents saved."));
3953 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3954 LASSERT(doc_buffer, break);
3955 doc_buffer->clearExternalModification();
3958 case LFUN_BUFFER_CLOSE:
3962 case LFUN_BUFFER_CLOSE_ALL:
3966 case LFUN_DEVEL_MODE_TOGGLE:
3967 devel_mode_ = !devel_mode_;
3969 dr.setMessage(_("Developer mode is now enabled."));
3971 dr.setMessage(_("Developer mode is now disabled."));
3974 case LFUN_TOOLBAR_TOGGLE: {
3975 string const name = cmd.getArg(0);
3976 if (GuiToolbar * t = toolbar(name))
3981 case LFUN_TOOLBAR_MOVABLE: {
3982 string const name = cmd.getArg(0);
3984 // toggle (all) toolbars movablility
3985 toolbarsMovable_ = !toolbarsMovable_;
3986 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3987 GuiToolbar * tb = toolbar(ti.name);
3988 if (tb && tb->isMovable() != toolbarsMovable_)
3989 // toggle toolbar movablity if it does not fit lock
3990 // (all) toolbars positions state silent = true, since
3991 // status bar notifications are slow
3994 if (toolbarsMovable_)
3995 dr.setMessage(_("Toolbars unlocked."));
3997 dr.setMessage(_("Toolbars locked."));
3998 } else if (GuiToolbar * t = toolbar(name)) {
3999 // toggle current toolbar movablity
4001 // update lock (all) toolbars positions
4002 updateLockToolbars();
4007 case LFUN_ICON_SIZE: {
4008 QSize size = d.iconSize(cmd.argument());
4010 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4011 size.width(), size.height()));
4015 case LFUN_DIALOG_UPDATE: {
4016 string const name = to_utf8(cmd.argument());
4017 if (name == "prefs" || name == "document")
4018 updateDialog(name, string());
4019 else if (name == "paragraph")
4020 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4021 else if (currentBufferView()) {
4022 Inset * inset = currentBufferView()->editedInset(name);
4023 // Can only update a dialog connected to an existing inset
4025 // FIXME: get rid of this indirection; GuiView ask the inset
4026 // if he is kind enough to update itself...
4027 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4028 //FIXME: pass DispatchResult here?
4029 inset->dispatch(currentBufferView()->cursor(), fr);
4035 case LFUN_DIALOG_TOGGLE: {
4036 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4037 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4038 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4042 case LFUN_DIALOG_DISCONNECT_INSET:
4043 disconnectDialog(to_utf8(cmd.argument()));
4046 case LFUN_DIALOG_HIDE: {
4047 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4051 case LFUN_DIALOG_SHOW: {
4052 string const name = cmd.getArg(0);
4053 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4055 if (name == "character") {
4056 sdata = freefont2string();
4058 showDialog("character", sdata);
4059 } else if (name == "latexlog") {
4060 // gettatus checks that
4061 LATTEST(doc_buffer);
4062 Buffer::LogType type;
4063 string const logfile = doc_buffer->logName(&type);
4065 case Buffer::latexlog:
4068 case Buffer::buildlog:
4069 sdata = "literate ";
4072 sdata += Lexer::quoteString(logfile);
4073 showDialog("log", sdata);
4074 } else if (name == "vclog") {
4075 // getStatus checks that
4076 LATTEST(doc_buffer);
4077 string const sdata2 = "vc " +
4078 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4079 showDialog("log", sdata2);
4080 } else if (name == "symbols") {
4081 sdata = bv->cursor().getEncoding()->name();
4083 showDialog("symbols", sdata);
4085 } else if (name == "prefs" && isFullScreen()) {
4086 lfunUiToggle("fullscreen");
4087 showDialog("prefs", sdata);
4089 showDialog(name, sdata);
4094 dr.setMessage(cmd.argument());
4097 case LFUN_UI_TOGGLE: {
4098 string arg = cmd.getArg(0);
4099 if (!lfunUiToggle(arg)) {
4100 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4101 dr.setMessage(bformat(msg, from_utf8(arg)));
4103 // Make sure the keyboard focus stays in the work area.
4108 case LFUN_VIEW_SPLIT: {
4109 LASSERT(doc_buffer, break);
4110 string const orientation = cmd.getArg(0);
4111 d.splitter_->setOrientation(orientation == "vertical"
4112 ? Qt::Vertical : Qt::Horizontal);
4113 TabWorkArea * twa = addTabWorkArea();
4114 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4115 setCurrentWorkArea(wa);
4118 case LFUN_TAB_GROUP_CLOSE:
4119 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4120 closeTabWorkArea(twa);
4121 d.current_work_area_ = 0;
4122 twa = d.currentTabWorkArea();
4123 // Switch to the next GuiWorkArea in the found TabWorkArea.
4125 // Make sure the work area is up to date.
4126 setCurrentWorkArea(twa->currentWorkArea());
4128 setCurrentWorkArea(0);
4133 case LFUN_VIEW_CLOSE:
4134 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4135 closeWorkArea(twa->currentWorkArea());
4136 d.current_work_area_ = 0;
4137 twa = d.currentTabWorkArea();
4138 // Switch to the next GuiWorkArea in the found TabWorkArea.
4140 // Make sure the work area is up to date.
4141 setCurrentWorkArea(twa->currentWorkArea());
4143 setCurrentWorkArea(0);
4148 case LFUN_COMPLETION_INLINE:
4149 if (d.current_work_area_)
4150 d.current_work_area_->completer().showInline();
4153 case LFUN_COMPLETION_POPUP:
4154 if (d.current_work_area_)
4155 d.current_work_area_->completer().showPopup();
4160 if (d.current_work_area_)
4161 d.current_work_area_->completer().tab();
4164 case LFUN_COMPLETION_CANCEL:
4165 if (d.current_work_area_) {
4166 if (d.current_work_area_->completer().popupVisible())
4167 d.current_work_area_->completer().hidePopup();
4169 d.current_work_area_->completer().hideInline();
4173 case LFUN_COMPLETION_ACCEPT:
4174 if (d.current_work_area_)
4175 d.current_work_area_->completer().activate();
4178 case LFUN_BUFFER_ZOOM_IN:
4179 case LFUN_BUFFER_ZOOM_OUT:
4180 case LFUN_BUFFER_ZOOM: {
4181 if (cmd.argument().empty()) {
4182 if (cmd.action() == LFUN_BUFFER_ZOOM)
4184 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4189 if (cmd.action() == LFUN_BUFFER_ZOOM)
4190 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4191 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4192 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4194 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4197 // Actual zoom value: default zoom + fractional extra value
4198 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4199 if (zoom < static_cast<int>(zoom_min_))
4202 lyxrc.currentZoom = zoom;
4204 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4205 lyxrc.currentZoom, lyxrc.defaultZoom));
4207 // The global QPixmapCache is used in GuiPainter to cache text
4208 // painting so we must reset it.
4209 QPixmapCache::clear();
4210 guiApp->fontLoader().update();
4211 dr.screenUpdate(Update::Force | Update::FitCursor);
4215 case LFUN_VC_REGISTER:
4216 case LFUN_VC_RENAME:
4218 case LFUN_VC_CHECK_IN:
4219 case LFUN_VC_CHECK_OUT:
4220 case LFUN_VC_REPO_UPDATE:
4221 case LFUN_VC_LOCKING_TOGGLE:
4222 case LFUN_VC_REVERT:
4223 case LFUN_VC_UNDO_LAST:
4224 case LFUN_VC_COMMAND:
4225 case LFUN_VC_COMPARE:
4226 dispatchVC(cmd, dr);
4229 case LFUN_SERVER_GOTO_FILE_ROW:
4230 if(goToFileRow(to_utf8(cmd.argument())))
4231 dr.screenUpdate(Update::Force | Update::FitCursor);
4234 case LFUN_LYX_ACTIVATE:
4238 case LFUN_FORWARD_SEARCH: {
4239 // it seems safe to assume we have a document buffer, since
4240 // getStatus wants one.
4241 LATTEST(doc_buffer);
4242 Buffer const * doc_master = doc_buffer->masterBuffer();
4243 FileName const path(doc_master->temppath());
4244 string const texname = doc_master->isChild(doc_buffer)
4245 ? DocFileName(changeExtension(
4246 doc_buffer->absFileName(),
4247 "tex")).mangledFileName()
4248 : doc_buffer->latexName();
4249 string const fulltexname =
4250 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4251 string const mastername =
4252 removeExtension(doc_master->latexName());
4253 FileName const dviname(addName(path.absFileName(),
4254 addExtension(mastername, "dvi")));
4255 FileName const pdfname(addName(path.absFileName(),
4256 addExtension(mastername, "pdf")));
4257 bool const have_dvi = dviname.exists();
4258 bool const have_pdf = pdfname.exists();
4259 if (!have_dvi && !have_pdf) {
4260 dr.setMessage(_("Please, preview the document first."));
4263 string outname = dviname.onlyFileName();
4264 string command = lyxrc.forward_search_dvi;
4265 if (!have_dvi || (have_pdf &&
4266 pdfname.lastModified() > dviname.lastModified())) {
4267 outname = pdfname.onlyFileName();
4268 command = lyxrc.forward_search_pdf;
4271 DocIterator cur = bv->cursor();
4272 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4273 LYXERR(Debug::ACTION, "Forward search: row:" << row
4275 if (row == -1 || command.empty()) {
4276 dr.setMessage(_("Couldn't proceed."));
4279 string texrow = convert<string>(row);
4281 command = subst(command, "$$n", texrow);
4282 command = subst(command, "$$f", fulltexname);
4283 command = subst(command, "$$t", texname);
4284 command = subst(command, "$$o", outname);
4286 PathChanger p(path);
4288 one.startscript(Systemcall::DontWait, command);
4292 case LFUN_SPELLING_CONTINUOUSLY:
4293 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4294 dr.screenUpdate(Update::Force);
4298 // The LFUN must be for one of BufferView, Buffer or Cursor;
4300 dispatchToBufferView(cmd, dr);
4304 // Part of automatic menu appearance feature.
4305 if (isFullScreen()) {
4306 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4310 // Need to update bv because many LFUNs here might have destroyed it
4311 bv = currentBufferView();
4313 // Clear non-empty selections
4314 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4316 Cursor & cur = bv->cursor();
4317 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4318 cur.clearSelection();
4324 bool GuiView::lfunUiToggle(string const & ui_component)
4326 if (ui_component == "scrollbar") {
4327 // hide() is of no help
4328 if (d.current_work_area_->verticalScrollBarPolicy() ==
4329 Qt::ScrollBarAlwaysOff)
4331 d.current_work_area_->setVerticalScrollBarPolicy(
4332 Qt::ScrollBarAsNeeded);
4334 d.current_work_area_->setVerticalScrollBarPolicy(
4335 Qt::ScrollBarAlwaysOff);
4336 } else if (ui_component == "statusbar") {
4337 statusBar()->setVisible(!statusBar()->isVisible());
4338 } else if (ui_component == "menubar") {
4339 menuBar()->setVisible(!menuBar()->isVisible());
4341 if (ui_component == "frame") {
4343 getContentsMargins(&l, &t, &r, &b);
4344 //are the frames in default state?
4345 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4347 setContentsMargins(-2, -2, -2, -2);
4349 setContentsMargins(0, 0, 0, 0);
4352 if (ui_component == "fullscreen") {
4360 void GuiView::toggleFullScreen()
4362 if (isFullScreen()) {
4363 for (int i = 0; i != d.splitter_->count(); ++i)
4364 d.tabWorkArea(i)->setFullScreen(false);
4365 setContentsMargins(0, 0, 0, 0);
4366 setWindowState(windowState() ^ Qt::WindowFullScreen);
4369 statusBar()->show();
4372 hideDialogs("prefs", 0);
4373 for (int i = 0; i != d.splitter_->count(); ++i)
4374 d.tabWorkArea(i)->setFullScreen(true);
4375 setContentsMargins(-2, -2, -2, -2);
4377 setWindowState(windowState() ^ Qt::WindowFullScreen);
4378 if (lyxrc.full_screen_statusbar)
4379 statusBar()->hide();
4380 if (lyxrc.full_screen_menubar)
4382 if (lyxrc.full_screen_toolbars) {
4383 ToolbarMap::iterator end = d.toolbars_.end();
4384 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4389 // give dialogs like the TOC a chance to adapt
4394 Buffer const * GuiView::updateInset(Inset const * inset)
4399 Buffer const * inset_buffer = &(inset->buffer());
4401 for (int i = 0; i != d.splitter_->count(); ++i) {
4402 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4405 Buffer const * buffer = &(wa->bufferView().buffer());
4406 if (inset_buffer == buffer)
4407 wa->scheduleRedraw(true);
4409 return inset_buffer;
4413 void GuiView::restartCaret()
4415 /* When we move around, or type, it's nice to be able to see
4416 * the caret immediately after the keypress.
4418 if (d.current_work_area_)
4419 d.current_work_area_->startBlinkingCaret();
4421 // Take this occasion to update the other GUI elements.
4427 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4429 if (d.current_work_area_)
4430 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4435 // This list should be kept in sync with the list of insets in
4436 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4437 // dialog should have the same name as the inset.
4438 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4439 // docs in LyXAction.cpp.
4441 char const * const dialognames[] = {
4443 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4444 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4445 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4446 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4447 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4448 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4449 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4450 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4452 char const * const * const end_dialognames =
4453 dialognames + (sizeof(dialognames) / sizeof(char *));
4457 cmpCStr(char const * name) : name_(name) {}
4458 bool operator()(char const * other) {
4459 return strcmp(other, name_) == 0;
4466 bool isValidName(string const & name)
4468 return find_if(dialognames, end_dialognames,
4469 cmpCStr(name.c_str())) != end_dialognames;
4475 void GuiView::resetDialogs()
4477 // Make sure that no LFUN uses any GuiView.
4478 guiApp->setCurrentView(0);
4482 constructToolbars();
4483 guiApp->menus().fillMenuBar(menuBar(), this, false);
4484 d.layout_->updateContents(true);
4485 // Now update controls with current buffer.
4486 guiApp->setCurrentView(this);
4492 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4494 if (!isValidName(name))
4497 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4499 if (it != d.dialogs_.end()) {
4501 it->second->hideView();
4502 return it->second.get();
4505 Dialog * dialog = build(name);
4506 d.dialogs_[name].reset(dialog);
4507 if (lyxrc.allow_geometry_session)
4508 dialog->restoreSession();
4515 void GuiView::showDialog(string const & name, string const & sdata,
4518 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4522 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4528 const string name = fromqstr(qname);
4529 const string sdata = fromqstr(qdata);
4533 Dialog * dialog = findOrBuild(name, false);
4535 bool const visible = dialog->isVisibleView();
4536 dialog->showData(sdata);
4537 if (inset && currentBufferView())
4538 currentBufferView()->editInset(name, inset);
4539 // We only set the focus to the new dialog if it was not yet
4540 // visible in order not to change the existing previous behaviour
4542 // activateWindow is needed for floating dockviews
4543 dialog->asQWidget()->raise();
4544 dialog->asQWidget()->activateWindow();
4545 dialog->asQWidget()->setFocus();
4549 catch (ExceptionMessage const & ex) {
4557 bool GuiView::isDialogVisible(string const & name) const
4559 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4560 if (it == d.dialogs_.end())
4562 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4566 void GuiView::hideDialog(string const & name, Inset * inset)
4568 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4569 if (it == d.dialogs_.end())
4573 if (!currentBufferView())
4575 if (inset != currentBufferView()->editedInset(name))
4579 Dialog * const dialog = it->second.get();
4580 if (dialog->isVisibleView())
4582 if (currentBufferView())
4583 currentBufferView()->editInset(name, 0);
4587 void GuiView::disconnectDialog(string const & name)
4589 if (!isValidName(name))
4591 if (currentBufferView())
4592 currentBufferView()->editInset(name, 0);
4596 void GuiView::hideAll() const
4598 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4599 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4601 for(; it != end; ++it)
4602 it->second->hideView();
4606 void GuiView::updateDialogs()
4608 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4609 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4611 for(; it != end; ++it) {
4612 Dialog * dialog = it->second.get();
4614 if (dialog->needBufferOpen() && !documentBufferView())
4615 hideDialog(fromqstr(dialog->name()), 0);
4616 else if (dialog->isVisibleView())
4617 dialog->checkStatus();
4624 Dialog * createDialog(GuiView & lv, string const & name);
4626 // will be replaced by a proper factory...
4627 Dialog * createGuiAbout(GuiView & lv);
4628 Dialog * createGuiBibtex(GuiView & lv);
4629 Dialog * createGuiChanges(GuiView & lv);
4630 Dialog * createGuiCharacter(GuiView & lv);
4631 Dialog * createGuiCitation(GuiView & lv);
4632 Dialog * createGuiCompare(GuiView & lv);
4633 Dialog * createGuiCompareHistory(GuiView & lv);
4634 Dialog * createGuiDelimiter(GuiView & lv);
4635 Dialog * createGuiDocument(GuiView & lv);
4636 Dialog * createGuiErrorList(GuiView & lv);
4637 Dialog * createGuiExternal(GuiView & lv);
4638 Dialog * createGuiGraphics(GuiView & lv);
4639 Dialog * createGuiInclude(GuiView & lv);
4640 Dialog * createGuiIndex(GuiView & lv);
4641 Dialog * createGuiListings(GuiView & lv);
4642 Dialog * createGuiLog(GuiView & lv);
4643 Dialog * createGuiMathMatrix(GuiView & lv);
4644 Dialog * createGuiNote(GuiView & lv);
4645 Dialog * createGuiParagraph(GuiView & lv);
4646 Dialog * createGuiPhantom(GuiView & lv);
4647 Dialog * createGuiPreferences(GuiView & lv);
4648 Dialog * createGuiPrint(GuiView & lv);
4649 Dialog * createGuiPrintindex(GuiView & lv);
4650 Dialog * createGuiRef(GuiView & lv);
4651 Dialog * createGuiSearch(GuiView & lv);
4652 Dialog * createGuiSearchAdv(GuiView & lv);
4653 Dialog * createGuiSendTo(GuiView & lv);
4654 Dialog * createGuiShowFile(GuiView & lv);
4655 Dialog * createGuiSpellchecker(GuiView & lv);
4656 Dialog * createGuiSymbols(GuiView & lv);
4657 Dialog * createGuiTabularCreate(GuiView & lv);
4658 Dialog * createGuiTexInfo(GuiView & lv);
4659 Dialog * createGuiToc(GuiView & lv);
4660 Dialog * createGuiThesaurus(GuiView & lv);
4661 Dialog * createGuiViewSource(GuiView & lv);
4662 Dialog * createGuiWrap(GuiView & lv);
4663 Dialog * createGuiProgressView(GuiView & lv);
4667 Dialog * GuiView::build(string const & name)
4669 LASSERT(isValidName(name), return 0);
4671 Dialog * dialog = createDialog(*this, name);
4675 if (name == "aboutlyx")
4676 return createGuiAbout(*this);
4677 if (name == "bibtex")
4678 return createGuiBibtex(*this);
4679 if (name == "changes")
4680 return createGuiChanges(*this);
4681 if (name == "character")
4682 return createGuiCharacter(*this);
4683 if (name == "citation")
4684 return createGuiCitation(*this);
4685 if (name == "compare")
4686 return createGuiCompare(*this);
4687 if (name == "comparehistory")
4688 return createGuiCompareHistory(*this);
4689 if (name == "document")
4690 return createGuiDocument(*this);
4691 if (name == "errorlist")
4692 return createGuiErrorList(*this);
4693 if (name == "external")
4694 return createGuiExternal(*this);
4696 return createGuiShowFile(*this);
4697 if (name == "findreplace")
4698 return createGuiSearch(*this);
4699 if (name == "findreplaceadv")
4700 return createGuiSearchAdv(*this);
4701 if (name == "graphics")
4702 return createGuiGraphics(*this);
4703 if (name == "include")
4704 return createGuiInclude(*this);
4705 if (name == "index")
4706 return createGuiIndex(*this);
4707 if (name == "index_print")
4708 return createGuiPrintindex(*this);
4709 if (name == "listings")
4710 return createGuiListings(*this);
4712 return createGuiLog(*this);
4713 if (name == "mathdelimiter")
4714 return createGuiDelimiter(*this);
4715 if (name == "mathmatrix")
4716 return createGuiMathMatrix(*this);
4718 return createGuiNote(*this);
4719 if (name == "paragraph")
4720 return createGuiParagraph(*this);
4721 if (name == "phantom")
4722 return createGuiPhantom(*this);
4723 if (name == "prefs")
4724 return createGuiPreferences(*this);
4726 return createGuiRef(*this);
4727 if (name == "sendto")
4728 return createGuiSendTo(*this);
4729 if (name == "spellchecker")
4730 return createGuiSpellchecker(*this);
4731 if (name == "symbols")
4732 return createGuiSymbols(*this);
4733 if (name == "tabularcreate")
4734 return createGuiTabularCreate(*this);
4735 if (name == "texinfo")
4736 return createGuiTexInfo(*this);
4737 if (name == "thesaurus")
4738 return createGuiThesaurus(*this);
4740 return createGuiToc(*this);
4741 if (name == "view-source")
4742 return createGuiViewSource(*this);
4744 return createGuiWrap(*this);
4745 if (name == "progress")
4746 return createGuiProgressView(*this);
4752 SEMenu::SEMenu(QWidget * parent)
4754 QAction * action = addAction(qt_("Disable Shell Escape"));
4755 connect(action, SIGNAL(triggered()),
4756 parent, SLOT(disableShellEscape()));
4759 } // namespace frontend
4762 #include "moc_GuiView.cpp"