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,
504 QVector<GuiWorkArea*> guiWorkAreas();
507 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
510 GuiView::GuiView(int id)
511 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
512 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
515 connect(this, SIGNAL(bufferViewChanged()),
516 this, SLOT(onBufferViewChanged()));
518 // GuiToolbars *must* be initialised before the menu bar.
519 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
522 // set ourself as the current view. This is needed for the menu bar
523 // filling, at least for the static special menu item on Mac. Otherwise
524 // they are greyed out.
525 guiApp->setCurrentView(this);
527 // Fill up the menu bar.
528 guiApp->menus().fillMenuBar(menuBar(), this, true);
530 setCentralWidget(d.stack_widget_);
532 // Start autosave timer
533 if (lyxrc.autosave) {
534 // The connection is closed when this is destroyed.
535 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
536 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
537 d.autosave_timeout_.start();
539 connect(&d.statusbar_timer_, SIGNAL(timeout()),
540 this, SLOT(clearMessage()));
542 // We don't want to keep the window in memory if it is closed.
543 setAttribute(Qt::WA_DeleteOnClose, true);
545 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
546 // QIcon::fromTheme was introduced in Qt 4.6
547 #if (QT_VERSION >= 0x040600)
548 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
549 // since the icon is provided in the application bundle. We use a themed
550 // version when available and use the bundled one as fallback.
551 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
553 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
559 // use tabbed dock area for multiple docks
560 // (such as "source" and "messages")
561 setDockOptions(QMainWindow::ForceTabbedDocks);
564 setAcceptDrops(true);
566 // add busy indicator to statusbar
567 QLabel * busylabel = new QLabel(statusBar());
568 statusBar()->addPermanentWidget(busylabel);
569 search_mode mode = theGuiApp()->imageSearchMode();
570 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
571 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
572 busylabel->setMovie(busyanim);
576 connect(&d.processing_thread_watcher_, SIGNAL(started()),
577 busylabel, SLOT(show()));
578 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
579 busylabel, SLOT(hide()));
581 QFontMetrics const fm(statusBar()->fontMetrics());
582 int const iconheight = max(int(d.normalIconSize), fm.height());
583 QSize const iconsize(iconheight, iconheight);
585 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
586 shell_escape_ = new QLabel(statusBar());
587 shell_escape_->setPixmap(shellescape);
588 shell_escape_->setScaledContents(true);
589 shell_escape_->setAlignment(Qt::AlignCenter);
590 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
591 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
592 "external commands for this document. "
593 "Right click to change."));
594 SEMenu * menu = new SEMenu(this);
595 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
596 menu, SLOT(showMenu(QPoint)));
597 shell_escape_->hide();
598 statusBar()->addPermanentWidget(shell_escape_);
600 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
601 read_only_ = new QLabel(statusBar());
602 read_only_->setPixmap(readonly);
603 read_only_->setScaledContents(true);
604 read_only_->setAlignment(Qt::AlignCenter);
606 statusBar()->addPermanentWidget(read_only_);
608 version_control_ = new QLabel(statusBar());
609 version_control_->setAlignment(Qt::AlignCenter);
610 version_control_->setFrameStyle(QFrame::StyledPanel);
611 version_control_->hide();
612 statusBar()->addPermanentWidget(version_control_);
614 statusBar()->setSizeGripEnabled(true);
617 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
618 SLOT(autoSaveThreadFinished()));
620 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
621 SLOT(processingThreadStarted()));
622 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
623 SLOT(processingThreadFinished()));
625 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
626 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
628 // set custom application bars context menu, e.g. tool bar and menu bar
629 setContextMenuPolicy(Qt::CustomContextMenu);
630 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
631 SLOT(toolBarPopup(const QPoint &)));
633 // Forbid too small unresizable window because it can happen
634 // with some window manager under X11.
635 setMinimumSize(300, 200);
637 if (lyxrc.allow_geometry_session) {
638 // Now take care of session management.
643 // no session handling, default to a sane size.
644 setGeometry(50, 50, 690, 510);
647 // clear session data if any.
649 settings.remove("views");
659 void GuiView::disableShellEscape()
661 BufferView * bv = documentBufferView();
664 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
665 bv->buffer().params().shell_escape = false;
666 bv->processUpdateFlags(Update::Force);
670 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
672 QVector<GuiWorkArea*> areas;
673 for (int i = 0; i < tabWorkAreaCount(); i++) {
674 TabWorkArea* ta = tabWorkArea(i);
675 for (int u = 0; u < ta->count(); u++) {
676 areas << ta->workArea(u);
682 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
683 string const & format)
685 docstring const fmt = theFormats().prettyName(format);
688 case Buffer::ExportSuccess:
689 msg = bformat(_("Successful export to format: %1$s"), fmt);
691 case Buffer::ExportCancel:
692 msg = _("Document export cancelled.");
694 case Buffer::ExportError:
695 case Buffer::ExportNoPathToFormat:
696 case Buffer::ExportTexPathHasSpaces:
697 case Buffer::ExportConverterError:
698 msg = bformat(_("Error while exporting format: %1$s"), fmt);
700 case Buffer::PreviewSuccess:
701 msg = bformat(_("Successful preview of format: %1$s"), fmt);
703 case Buffer::PreviewError:
704 msg = bformat(_("Error while previewing format: %1$s"), fmt);
711 void GuiView::processingThreadStarted()
716 void GuiView::processingThreadFinished()
718 QFutureWatcher<Buffer::ExportStatus> const * watcher =
719 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
721 Buffer::ExportStatus const status = watcher->result();
722 handleExportStatus(this, status, d.processing_format);
725 BufferView const * const bv = currentBufferView();
726 if (bv && !bv->buffer().errorList("Export").empty()) {
730 if (status != Buffer::ExportSuccess && status != Buffer::PreviewSuccess &&
731 status != Buffer::ExportCancel) {
732 errors(d.last_export_format);
737 void GuiView::autoSaveThreadFinished()
739 QFutureWatcher<docstring> const * watcher =
740 static_cast<QFutureWatcher<docstring> const *>(sender());
741 message(watcher->result());
746 void GuiView::saveLayout() const
749 settings.setValue("zoom_ratio", zoom_ratio_);
750 settings.setValue("devel_mode", devel_mode_);
751 settings.beginGroup("views");
752 settings.beginGroup(QString::number(id_));
753 #if defined(Q_WS_X11) || defined(QPA_XCB)
754 settings.setValue("pos", pos());
755 settings.setValue("size", size());
757 settings.setValue("geometry", saveGeometry());
759 settings.setValue("layout", saveState(0));
760 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
764 void GuiView::saveUISettings() const
768 // Save the toolbar private states
769 ToolbarMap::iterator end = d.toolbars_.end();
770 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
771 it->second->saveSession(settings);
772 // Now take care of all other dialogs
773 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
774 for (; it!= d.dialogs_.end(); ++it)
775 it->second->saveSession(settings);
779 bool GuiView::restoreLayout()
782 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
783 // Actual zoom value: default zoom + fractional offset
784 int zoom = lyxrc.defaultZoom * zoom_ratio_;
785 if (zoom < static_cast<int>(zoom_min_))
787 lyxrc.currentZoom = zoom;
788 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
789 settings.beginGroup("views");
790 settings.beginGroup(QString::number(id_));
791 QString const icon_key = "icon_size";
792 if (!settings.contains(icon_key))
795 //code below is skipped when when ~/.config/LyX is (re)created
796 setIconSize(d.iconSize(settings.value(icon_key).toString()));
798 #if defined(Q_WS_X11) || defined(QPA_XCB)
799 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
800 QSize size = settings.value("size", QSize(690, 510)).toSize();
804 // Work-around for bug #6034: the window ends up in an undetermined
805 // state when trying to restore a maximized window when it is
806 // already maximized.
807 if (!(windowState() & Qt::WindowMaximized))
808 if (!restoreGeometry(settings.value("geometry").toByteArray()))
809 setGeometry(50, 50, 690, 510);
811 // Make sure layout is correctly oriented.
812 setLayoutDirection(qApp->layoutDirection());
814 // Allow the toc and view-source dock widget to be restored if needed.
816 if ((dialog = findOrBuild("toc", true)))
817 // see bug 5082. At least setup title and enabled state.
818 // Visibility will be adjusted by restoreState below.
819 dialog->prepareView();
820 if ((dialog = findOrBuild("view-source", true)))
821 dialog->prepareView();
822 if ((dialog = findOrBuild("progress", true)))
823 dialog->prepareView();
825 if (!restoreState(settings.value("layout").toByteArray(), 0))
828 // init the toolbars that have not been restored
829 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
830 Toolbars::Infos::iterator end = guiApp->toolbars().end();
831 for (; cit != end; ++cit) {
832 GuiToolbar * tb = toolbar(cit->name);
833 if (tb && !tb->isRestored())
834 initToolbar(cit->name);
837 // update lock (all) toolbars positions
838 updateLockToolbars();
845 GuiToolbar * GuiView::toolbar(string const & name)
847 ToolbarMap::iterator it = d.toolbars_.find(name);
848 if (it != d.toolbars_.end())
851 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
856 void GuiView::updateLockToolbars()
858 toolbarsMovable_ = false;
859 for (ToolbarInfo const & info : guiApp->toolbars()) {
860 GuiToolbar * tb = toolbar(info.name);
861 if (tb && tb->isMovable())
862 toolbarsMovable_ = true;
867 void GuiView::constructToolbars()
869 ToolbarMap::iterator it = d.toolbars_.begin();
870 for (; it != d.toolbars_.end(); ++it)
874 // I don't like doing this here, but the standard toolbar
875 // destroys this object when it's destroyed itself (vfr)
876 d.layout_ = new LayoutBox(*this);
877 d.stack_widget_->addWidget(d.layout_);
878 d.layout_->move(0,0);
880 // extracts the toolbars from the backend
881 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
882 Toolbars::Infos::iterator end = guiApp->toolbars().end();
883 for (; cit != end; ++cit)
884 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
888 void GuiView::initToolbars()
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 initToolbar(cit->name);
898 void GuiView::initToolbar(string const & name)
900 GuiToolbar * tb = toolbar(name);
903 int const visibility = guiApp->toolbars().defaultVisibility(name);
904 bool newline = !(visibility & Toolbars::SAMEROW);
905 tb->setVisible(false);
906 tb->setVisibility(visibility);
908 if (visibility & Toolbars::TOP) {
910 addToolBarBreak(Qt::TopToolBarArea);
911 addToolBar(Qt::TopToolBarArea, tb);
914 if (visibility & Toolbars::BOTTOM) {
916 addToolBarBreak(Qt::BottomToolBarArea);
917 addToolBar(Qt::BottomToolBarArea, tb);
920 if (visibility & Toolbars::LEFT) {
922 addToolBarBreak(Qt::LeftToolBarArea);
923 addToolBar(Qt::LeftToolBarArea, tb);
926 if (visibility & Toolbars::RIGHT) {
928 addToolBarBreak(Qt::RightToolBarArea);
929 addToolBar(Qt::RightToolBarArea, tb);
932 if (visibility & Toolbars::ON)
933 tb->setVisible(true);
935 tb->setMovable(true);
939 TocModels & GuiView::tocModels()
941 return d.toc_models_;
945 void GuiView::setFocus()
947 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
948 QMainWindow::setFocus();
952 bool GuiView::hasFocus() const
954 if (currentWorkArea())
955 return currentWorkArea()->hasFocus();
956 if (currentMainWorkArea())
957 return currentMainWorkArea()->hasFocus();
958 return d.bg_widget_->hasFocus();
962 void GuiView::focusInEvent(QFocusEvent * e)
964 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
965 QMainWindow::focusInEvent(e);
966 // Make sure guiApp points to the correct view.
967 guiApp->setCurrentView(this);
968 if (currentWorkArea())
969 currentWorkArea()->setFocus();
970 else if (currentMainWorkArea())
971 currentMainWorkArea()->setFocus();
973 d.bg_widget_->setFocus();
977 void GuiView::showEvent(QShowEvent * e)
979 LYXERR(Debug::GUI, "Passed Geometry "
980 << size().height() << "x" << size().width()
981 << "+" << pos().x() << "+" << pos().y());
983 if (d.splitter_->count() == 0)
984 // No work area, switch to the background widget.
988 QMainWindow::showEvent(e);
992 bool GuiView::closeScheduled()
999 bool GuiView::prepareAllBuffersForLogout()
1001 Buffer * first = theBufferList().first();
1005 // First, iterate over all buffers and ask the users if unsaved
1006 // changes should be saved.
1007 // We cannot use a for loop as the buffer list cycles.
1010 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1012 b = theBufferList().next(b);
1013 } while (b != first);
1015 // Next, save session state
1016 // When a view/window was closed before without quitting LyX, there
1017 // are already entries in the lastOpened list.
1018 theSession().lastOpened().clear();
1025 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1026 ** is responsibility of the container (e.g., dialog)
1028 void GuiView::closeEvent(QCloseEvent * close_event)
1030 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1032 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1033 Alert::warning(_("Exit LyX"),
1034 _("LyX could not be closed because documents are being processed by LyX."));
1035 close_event->setAccepted(false);
1039 // If the user pressed the x (so we didn't call closeView
1040 // programmatically), we want to clear all existing entries.
1042 theSession().lastOpened().clear();
1047 // it can happen that this event arrives without selecting the view,
1048 // e.g. when clicking the close button on a background window.
1050 if (!closeWorkAreaAll()) {
1052 close_event->ignore();
1056 // Make sure that nothing will use this to be closed View.
1057 guiApp->unregisterView(this);
1059 if (isFullScreen()) {
1060 // Switch off fullscreen before closing.
1065 // Make sure the timer time out will not trigger a statusbar update.
1066 d.statusbar_timer_.stop();
1068 // Saving fullscreen requires additional tweaks in the toolbar code.
1069 // It wouldn't also work under linux natively.
1070 if (lyxrc.allow_geometry_session) {
1075 close_event->accept();
1079 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1081 if (event->mimeData()->hasUrls())
1083 /// \todo Ask lyx-devel is this is enough:
1084 /// if (event->mimeData()->hasFormat("text/plain"))
1085 /// event->acceptProposedAction();
1089 void GuiView::dropEvent(QDropEvent * event)
1091 QList<QUrl> files = event->mimeData()->urls();
1092 if (files.isEmpty())
1095 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1096 for (int i = 0; i != files.size(); ++i) {
1097 string const file = os::internal_path(fromqstr(
1098 files.at(i).toLocalFile()));
1102 string const ext = support::getExtension(file);
1103 vector<const Format *> found_formats;
1105 // Find all formats that have the correct extension.
1106 vector<const Format *> const & import_formats
1107 = theConverters().importableFormats();
1108 vector<const Format *>::const_iterator it = import_formats.begin();
1109 for (; it != import_formats.end(); ++it)
1110 if ((*it)->hasExtension(ext))
1111 found_formats.push_back(*it);
1114 if (found_formats.size() >= 1) {
1115 if (found_formats.size() > 1) {
1116 //FIXME: show a dialog to choose the correct importable format
1117 LYXERR(Debug::FILES,
1118 "Multiple importable formats found, selecting first");
1120 string const arg = found_formats[0]->name() + " " + file;
1121 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1124 //FIXME: do we have to explicitly check whether it's a lyx file?
1125 LYXERR(Debug::FILES,
1126 "No formats found, trying to open it as a lyx file");
1127 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1129 // add the functions to the queue
1130 guiApp->addToFuncRequestQueue(cmd);
1133 // now process the collected functions. We perform the events
1134 // asynchronously. This prevents potential problems in case the
1135 // BufferView is closed within an event.
1136 guiApp->processFuncRequestQueueAsync();
1140 void GuiView::message(docstring const & str)
1142 if (ForkedProcess::iAmAChild())
1145 // call is moved to GUI-thread by GuiProgress
1146 d.progress_->appendMessage(toqstr(str));
1150 void GuiView::clearMessageText()
1152 message(docstring());
1156 void GuiView::updateStatusBarMessage(QString const & str)
1158 statusBar()->showMessage(str);
1159 d.statusbar_timer_.stop();
1160 d.statusbar_timer_.start(3000);
1164 void GuiView::clearMessage()
1166 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1167 // the hasFocus function mostly returns false, even if the focus is on
1168 // a workarea in this view.
1172 d.statusbar_timer_.stop();
1176 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1178 if (wa != d.current_work_area_
1179 || wa->bufferView().buffer().isInternal())
1181 Buffer const & buf = wa->bufferView().buffer();
1182 // Set the windows title
1183 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1184 if (buf.notifiesExternalModification()) {
1185 title = bformat(_("%1$s (modified externally)"), title);
1186 // If the external modification status has changed, then maybe the status of
1187 // buffer-save has changed too.
1191 title += from_ascii(" - LyX");
1193 setWindowTitle(toqstr(title));
1194 // Sets the path for the window: this is used by OSX to
1195 // allow a context click on the title bar showing a menu
1196 // with the path up to the file
1197 setWindowFilePath(toqstr(buf.absFileName()));
1198 // Tell Qt whether the current document is changed
1199 setWindowModified(!buf.isClean());
1201 if (buf.params().shell_escape)
1202 shell_escape_->show();
1204 shell_escape_->hide();
1206 if (buf.hasReadonlyFlag())
1211 if (buf.lyxvc().inUse()) {
1212 version_control_->show();
1213 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1215 version_control_->hide();
1219 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1221 if (d.current_work_area_)
1222 // disconnect the current work area from all slots
1223 QObject::disconnect(d.current_work_area_, 0, this, 0);
1225 disconnectBufferView();
1226 connectBufferView(wa->bufferView());
1227 connectBuffer(wa->bufferView().buffer());
1228 d.current_work_area_ = wa;
1229 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1230 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1231 QObject::connect(wa, SIGNAL(busy(bool)),
1232 this, SLOT(setBusy(bool)));
1233 // connection of a signal to a signal
1234 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1235 this, SIGNAL(bufferViewChanged()));
1236 Q_EMIT updateWindowTitle(wa);
1237 Q_EMIT bufferViewChanged();
1241 void GuiView::onBufferViewChanged()
1244 // Buffer-dependent dialogs must be updated. This is done here because
1245 // some dialogs require buffer()->text.
1250 void GuiView::on_lastWorkAreaRemoved()
1253 // We already are in a close event. Nothing more to do.
1256 if (d.splitter_->count() > 1)
1257 // We have a splitter so don't close anything.
1260 // Reset and updates the dialogs.
1261 Q_EMIT bufferViewChanged();
1266 if (lyxrc.open_buffers_in_tabs)
1267 // Nothing more to do, the window should stay open.
1270 if (guiApp->viewIds().size() > 1) {
1276 // On Mac we also close the last window because the application stay
1277 // resident in memory. On other platforms we don't close the last
1278 // window because this would quit the application.
1284 void GuiView::updateStatusBar()
1286 // let the user see the explicit message
1287 if (d.statusbar_timer_.isActive())
1294 void GuiView::showMessage()
1298 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1299 if (msg.isEmpty()) {
1300 BufferView const * bv = currentBufferView();
1302 msg = toqstr(bv->cursor().currentState(devel_mode_));
1304 msg = qt_("Welcome to LyX!");
1306 statusBar()->showMessage(msg);
1310 bool GuiView::event(QEvent * e)
1314 // Useful debug code:
1315 //case QEvent::ActivationChange:
1316 //case QEvent::WindowDeactivate:
1317 //case QEvent::Paint:
1318 //case QEvent::Enter:
1319 //case QEvent::Leave:
1320 //case QEvent::HoverEnter:
1321 //case QEvent::HoverLeave:
1322 //case QEvent::HoverMove:
1323 //case QEvent::StatusTip:
1324 //case QEvent::DragEnter:
1325 //case QEvent::DragLeave:
1326 //case QEvent::Drop:
1329 case QEvent::WindowActivate: {
1330 GuiView * old_view = guiApp->currentView();
1331 if (this == old_view) {
1333 return QMainWindow::event(e);
1335 if (old_view && old_view->currentBufferView()) {
1336 // save current selection to the selection buffer to allow
1337 // middle-button paste in this window.
1338 cap::saveSelection(old_view->currentBufferView()->cursor());
1340 guiApp->setCurrentView(this);
1341 if (d.current_work_area_)
1342 on_currentWorkAreaChanged(d.current_work_area_);
1346 return QMainWindow::event(e);
1349 case QEvent::ShortcutOverride: {
1351 if (isFullScreen() && menuBar()->isHidden()) {
1352 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1353 // FIXME: we should also try to detect special LyX shortcut such as
1354 // Alt-P and Alt-M. Right now there is a hack in
1355 // GuiWorkArea::processKeySym() that hides again the menubar for
1357 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1359 return QMainWindow::event(e);
1362 return QMainWindow::event(e);
1366 return QMainWindow::event(e);
1370 void GuiView::resetWindowTitle()
1372 setWindowTitle(qt_("LyX"));
1375 bool GuiView::focusNextPrevChild(bool /*next*/)
1382 bool GuiView::busy() const
1388 void GuiView::setBusy(bool busy)
1390 bool const busy_before = busy_ > 0;
1391 busy ? ++busy_ : --busy_;
1392 if ((busy_ > 0) == busy_before)
1393 // busy state didn't change
1397 QApplication::setOverrideCursor(Qt::WaitCursor);
1400 QApplication::restoreOverrideCursor();
1405 void GuiView::resetCommandExecute()
1407 command_execute_ = false;
1412 double GuiView::pixelRatio() const
1414 #if QT_VERSION >= 0x050000
1415 return qt_scale_factor * devicePixelRatio();
1422 GuiWorkArea * GuiView::workArea(int index)
1424 if (TabWorkArea * twa = d.currentTabWorkArea())
1425 if (index < twa->count())
1426 return twa->workArea(index);
1431 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1433 if (currentWorkArea()
1434 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1435 return currentWorkArea();
1436 if (TabWorkArea * twa = d.currentTabWorkArea())
1437 return twa->workArea(buffer);
1442 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1444 // Automatically create a TabWorkArea if there are none yet.
1445 TabWorkArea * tab_widget = d.splitter_->count()
1446 ? d.currentTabWorkArea() : addTabWorkArea();
1447 return tab_widget->addWorkArea(buffer, *this);
1451 TabWorkArea * GuiView::addTabWorkArea()
1453 TabWorkArea * twa = new TabWorkArea;
1454 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1455 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1456 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1457 this, SLOT(on_lastWorkAreaRemoved()));
1459 d.splitter_->addWidget(twa);
1460 d.stack_widget_->setCurrentWidget(d.splitter_);
1465 GuiWorkArea const * GuiView::currentWorkArea() const
1467 return d.current_work_area_;
1471 GuiWorkArea * GuiView::currentWorkArea()
1473 return d.current_work_area_;
1477 GuiWorkArea const * GuiView::currentMainWorkArea() const
1479 if (!d.currentTabWorkArea())
1481 return d.currentTabWorkArea()->currentWorkArea();
1485 GuiWorkArea * GuiView::currentMainWorkArea()
1487 if (!d.currentTabWorkArea())
1489 return d.currentTabWorkArea()->currentWorkArea();
1493 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1495 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1497 d.current_work_area_ = 0;
1499 Q_EMIT bufferViewChanged();
1503 // FIXME: I've no clue why this is here and why it accesses
1504 // theGuiApp()->currentView, which might be 0 (bug 6464).
1505 // See also 27525 (vfr).
1506 if (theGuiApp()->currentView() == this
1507 && theGuiApp()->currentView()->currentWorkArea() == wa)
1510 if (currentBufferView())
1511 cap::saveSelection(currentBufferView()->cursor());
1513 theGuiApp()->setCurrentView(this);
1514 d.current_work_area_ = wa;
1516 // We need to reset this now, because it will need to be
1517 // right if the tabWorkArea gets reset in the for loop. We
1518 // will change it back if we aren't in that case.
1519 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1520 d.current_main_work_area_ = wa;
1522 for (int i = 0; i != d.splitter_->count(); ++i) {
1523 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1524 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1525 << ", Current main wa: " << currentMainWorkArea());
1530 d.current_main_work_area_ = old_cmwa;
1532 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1533 on_currentWorkAreaChanged(wa);
1534 BufferView & bv = wa->bufferView();
1535 bv.cursor().fixIfBroken();
1537 wa->setUpdatesEnabled(true);
1538 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1542 void GuiView::removeWorkArea(GuiWorkArea * wa)
1544 LASSERT(wa, return);
1545 if (wa == d.current_work_area_) {
1547 disconnectBufferView();
1548 d.current_work_area_ = 0;
1549 d.current_main_work_area_ = 0;
1552 bool found_twa = false;
1553 for (int i = 0; i != d.splitter_->count(); ++i) {
1554 TabWorkArea * twa = d.tabWorkArea(i);
1555 if (twa->removeWorkArea(wa)) {
1556 // Found in this tab group, and deleted the GuiWorkArea.
1558 if (twa->count() != 0) {
1559 if (d.current_work_area_ == 0)
1560 // This means that we are closing the current GuiWorkArea, so
1561 // switch to the next GuiWorkArea in the found TabWorkArea.
1562 setCurrentWorkArea(twa->currentWorkArea());
1564 // No more WorkAreas in this tab group, so delete it.
1571 // It is not a tabbed work area (i.e., the search work area), so it
1572 // should be deleted by other means.
1573 LASSERT(found_twa, return);
1575 if (d.current_work_area_ == 0) {
1576 if (d.splitter_->count() != 0) {
1577 TabWorkArea * twa = d.currentTabWorkArea();
1578 setCurrentWorkArea(twa->currentWorkArea());
1580 // No more work areas, switch to the background widget.
1581 setCurrentWorkArea(0);
1587 LayoutBox * GuiView::getLayoutDialog() const
1593 void GuiView::updateLayoutList()
1596 d.layout_->updateContents(false);
1600 void GuiView::updateToolbars()
1602 ToolbarMap::iterator end = d.toolbars_.end();
1603 if (d.current_work_area_) {
1605 if (d.current_work_area_->bufferView().cursor().inMathed()
1606 && !d.current_work_area_->bufferView().cursor().inRegexped())
1607 context |= Toolbars::MATH;
1608 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1609 context |= Toolbars::TABLE;
1610 if (currentBufferView()->buffer().areChangesPresent()
1611 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1612 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1613 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1614 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1615 context |= Toolbars::REVIEW;
1616 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1617 context |= Toolbars::MATHMACROTEMPLATE;
1618 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1619 context |= Toolbars::IPA;
1620 if (command_execute_)
1621 context |= Toolbars::MINIBUFFER;
1622 if (minibuffer_focus_) {
1623 context |= Toolbars::MINIBUFFER_FOCUS;
1624 minibuffer_focus_ = false;
1627 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1628 it->second->update(context);
1630 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1631 it->second->update();
1635 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1637 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1638 LASSERT(newBuffer, return);
1640 GuiWorkArea * wa = workArea(*newBuffer);
1643 newBuffer->masterBuffer()->updateBuffer();
1645 wa = addWorkArea(*newBuffer);
1646 // scroll to the position when the BufferView was last closed
1647 if (lyxrc.use_lastfilepos) {
1648 LastFilePosSection::FilePos filepos =
1649 theSession().lastFilePos().load(newBuffer->fileName());
1650 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1653 //Disconnect the old buffer...there's no new one.
1656 connectBuffer(*newBuffer);
1657 connectBufferView(wa->bufferView());
1659 setCurrentWorkArea(wa);
1663 void GuiView::connectBuffer(Buffer & buf)
1665 buf.setGuiDelegate(this);
1669 void GuiView::disconnectBuffer()
1671 if (d.current_work_area_)
1672 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1676 void GuiView::connectBufferView(BufferView & bv)
1678 bv.setGuiDelegate(this);
1682 void GuiView::disconnectBufferView()
1684 if (d.current_work_area_)
1685 d.current_work_area_->bufferView().setGuiDelegate(0);
1689 void GuiView::errors(string const & error_type, bool from_master)
1691 BufferView const * const bv = currentBufferView();
1695 #if EXPORT_in_THREAD
1696 // We are called with from_master == false by default, so we
1697 // have to figure out whether that is the case or not.
1698 ErrorList & el = bv->buffer().errorList(error_type);
1700 el = bv->buffer().masterBuffer()->errorList(error_type);
1704 ErrorList const & el = from_master ?
1705 bv->buffer().masterBuffer()->errorList(error_type) :
1706 bv->buffer().errorList(error_type);
1712 string data = error_type;
1714 data = "from_master|" + error_type;
1715 showDialog("errorlist", data);
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 & data)
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(data);
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_EXPORT:
1866 && (doc_buffer->parent() != 0
1867 || doc_buffer->hasChildren())
1868 && !d.processing_thread_watcher_.isRunning()
1869 // this launches a dialog, which would be in the wrong Buffer
1870 && !(::lyx::operator==(cmd.argument(), "custom"));
1873 case LFUN_MASTER_BUFFER_UPDATE:
1874 case LFUN_MASTER_BUFFER_VIEW:
1876 && (doc_buffer->parent() != 0
1877 || doc_buffer->hasChildren())
1878 && !d.processing_thread_watcher_.isRunning();
1881 case LFUN_BUFFER_UPDATE:
1882 case LFUN_BUFFER_VIEW: {
1883 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1887 string format = to_utf8(cmd.argument());
1888 if (cmd.argument().empty())
1889 format = doc_buffer->params().getDefaultOutputFormat();
1890 enable = doc_buffer->params().isExportable(format, true);
1894 case LFUN_BUFFER_RELOAD:
1895 enable = doc_buffer && !doc_buffer->isUnnamed()
1896 && doc_buffer->fileName().exists()
1897 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1900 case LFUN_BUFFER_CHILD_OPEN:
1901 enable = doc_buffer != 0;
1904 case LFUN_BUFFER_WRITE:
1905 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1908 //FIXME: This LFUN should be moved to GuiApplication.
1909 case LFUN_BUFFER_WRITE_ALL: {
1910 // We enable the command only if there are some modified buffers
1911 Buffer * first = theBufferList().first();
1916 // We cannot use a for loop as the buffer list is a cycle.
1918 if (!b->isClean()) {
1922 b = theBufferList().next(b);
1923 } while (b != first);
1927 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1928 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1931 case LFUN_BUFFER_EXPORT: {
1932 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1936 return doc_buffer->getStatus(cmd, flag);
1940 case LFUN_BUFFER_EXPORT_AS:
1941 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1946 case LFUN_BUFFER_WRITE_AS:
1947 enable = doc_buffer != 0;
1950 case LFUN_BUFFER_CLOSE:
1951 case LFUN_VIEW_CLOSE:
1952 enable = doc_buffer != 0;
1955 case LFUN_BUFFER_CLOSE_ALL:
1956 enable = theBufferList().last() != theBufferList().first();
1959 case LFUN_BUFFER_CHKTEX: {
1960 // hide if we have no checktex command
1961 if (lyxrc.chktex_command.empty()) {
1962 flag.setUnknown(true);
1966 if (!doc_buffer || !doc_buffer->params().isLatex()
1967 || d.processing_thread_watcher_.isRunning()) {
1968 // grey out, don't hide
1976 case LFUN_VIEW_SPLIT:
1977 if (cmd.getArg(0) == "vertical")
1978 enable = doc_buffer && (d.splitter_->count() == 1 ||
1979 d.splitter_->orientation() == Qt::Vertical);
1981 enable = doc_buffer && (d.splitter_->count() == 1 ||
1982 d.splitter_->orientation() == Qt::Horizontal);
1985 case LFUN_TAB_GROUP_CLOSE:
1986 enable = d.tabWorkAreaCount() > 1;
1989 case LFUN_DEVEL_MODE_TOGGLE:
1990 flag.setOnOff(devel_mode_);
1993 case LFUN_TOOLBAR_TOGGLE: {
1994 string const name = cmd.getArg(0);
1995 if (GuiToolbar * t = toolbar(name))
1996 flag.setOnOff(t->isVisible());
1999 docstring const msg =
2000 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2006 case LFUN_TOOLBAR_MOVABLE: {
2007 string const name = cmd.getArg(0);
2008 // use negation since locked == !movable
2010 // toolbar name * locks all toolbars
2011 flag.setOnOff(!toolbarsMovable_);
2012 else if (GuiToolbar * t = toolbar(name))
2013 flag.setOnOff(!(t->isMovable()));
2016 docstring const msg =
2017 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2023 case LFUN_ICON_SIZE:
2024 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2027 case LFUN_DROP_LAYOUTS_CHOICE:
2031 case LFUN_UI_TOGGLE:
2032 flag.setOnOff(isFullScreen());
2035 case LFUN_DIALOG_DISCONNECT_INSET:
2038 case LFUN_DIALOG_HIDE:
2039 // FIXME: should we check if the dialog is shown?
2042 case LFUN_DIALOG_TOGGLE:
2043 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2046 case LFUN_DIALOG_SHOW: {
2047 string const name = cmd.getArg(0);
2049 enable = name == "aboutlyx"
2050 || name == "file" //FIXME: should be removed.
2052 || name == "texinfo"
2053 || name == "progress"
2054 || name == "compare";
2055 else if (name == "character" || name == "symbols"
2056 || name == "mathdelimiter" || name == "mathmatrix") {
2057 if (!buf || buf->isReadonly())
2060 Cursor const & cur = currentBufferView()->cursor();
2061 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2064 else if (name == "latexlog")
2065 enable = FileName(doc_buffer->logName()).isReadableFile();
2066 else if (name == "spellchecker")
2067 enable = theSpellChecker()
2068 && !doc_buffer->isReadonly()
2069 && !doc_buffer->text().empty();
2070 else if (name == "vclog")
2071 enable = doc_buffer->lyxvc().inUse();
2075 case LFUN_DIALOG_UPDATE: {
2076 string const name = cmd.getArg(0);
2078 enable = name == "prefs";
2082 case LFUN_COMMAND_EXECUTE:
2084 case LFUN_MENU_OPEN:
2085 // Nothing to check.
2088 case LFUN_COMPLETION_INLINE:
2089 if (!d.current_work_area_
2090 || !d.current_work_area_->completer().inlinePossible(
2091 currentBufferView()->cursor()))
2095 case LFUN_COMPLETION_POPUP:
2096 if (!d.current_work_area_
2097 || !d.current_work_area_->completer().popupPossible(
2098 currentBufferView()->cursor()))
2103 if (!d.current_work_area_
2104 || !d.current_work_area_->completer().inlinePossible(
2105 currentBufferView()->cursor()))
2109 case LFUN_COMPLETION_ACCEPT:
2110 if (!d.current_work_area_
2111 || (!d.current_work_area_->completer().popupVisible()
2112 && !d.current_work_area_->completer().inlineVisible()
2113 && !d.current_work_area_->completer().completionAvailable()))
2117 case LFUN_COMPLETION_CANCEL:
2118 if (!d.current_work_area_
2119 || (!d.current_work_area_->completer().popupVisible()
2120 && !d.current_work_area_->completer().inlineVisible()))
2124 case LFUN_BUFFER_ZOOM_OUT:
2125 case LFUN_BUFFER_ZOOM_IN: {
2126 // only diff between these two is that the default for ZOOM_OUT
2128 bool const neg_zoom =
2129 convert<int>(cmd.argument()) < 0 ||
2130 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2131 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2132 docstring const msg =
2133 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2137 enable = doc_buffer;
2141 case LFUN_BUFFER_ZOOM: {
2142 bool const less_than_min_zoom =
2143 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2144 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2145 docstring const msg =
2146 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2151 enable = doc_buffer;
2155 case LFUN_BUFFER_MOVE_NEXT:
2156 case LFUN_BUFFER_MOVE_PREVIOUS:
2157 // we do not cycle when moving
2158 case LFUN_BUFFER_NEXT:
2159 case LFUN_BUFFER_PREVIOUS:
2160 // because we cycle, it doesn't matter whether on first or last
2161 enable = (d.currentTabWorkArea()->count() > 1);
2163 case LFUN_BUFFER_SWITCH:
2164 // toggle on the current buffer, but do not toggle off
2165 // the other ones (is that a good idea?)
2167 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2168 flag.setOnOff(true);
2171 case LFUN_VC_REGISTER:
2172 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2174 case LFUN_VC_RENAME:
2175 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2178 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2180 case LFUN_VC_CHECK_IN:
2181 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2183 case LFUN_VC_CHECK_OUT:
2184 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2186 case LFUN_VC_LOCKING_TOGGLE:
2187 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2188 && doc_buffer->lyxvc().lockingToggleEnabled();
2189 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2191 case LFUN_VC_REVERT:
2192 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2193 && !doc_buffer->hasReadonlyFlag();
2195 case LFUN_VC_UNDO_LAST:
2196 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2198 case LFUN_VC_REPO_UPDATE:
2199 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2201 case LFUN_VC_COMMAND: {
2202 if (cmd.argument().empty())
2204 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2208 case LFUN_VC_COMPARE:
2209 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2212 case LFUN_SERVER_GOTO_FILE_ROW:
2213 case LFUN_LYX_ACTIVATE:
2215 case LFUN_FORWARD_SEARCH:
2216 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2219 case LFUN_FILE_INSERT_PLAINTEXT:
2220 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2221 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2224 case LFUN_SPELLING_CONTINUOUSLY:
2225 flag.setOnOff(lyxrc.spellcheck_continuously);
2233 flag.setEnabled(false);
2239 static FileName selectTemplateFile()
2241 FileDialog dlg(qt_("Select template file"));
2242 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2243 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2245 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2246 QStringList(qt_("LyX Documents (*.lyx)")));
2248 if (result.first == FileDialog::Later)
2250 if (result.second.isEmpty())
2252 return FileName(fromqstr(result.second));
2256 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2260 Buffer * newBuffer = 0;
2262 newBuffer = checkAndLoadLyXFile(filename);
2263 } catch (ExceptionMessage const & e) {
2270 message(_("Document not loaded."));
2274 setBuffer(newBuffer);
2275 newBuffer->errors("Parse");
2278 theSession().lastFiles().add(filename);
2279 theSession().writeFile();
2286 void GuiView::openDocument(string const & fname)
2288 string initpath = lyxrc.document_path;
2290 if (documentBufferView()) {
2291 string const trypath = documentBufferView()->buffer().filePath();
2292 // If directory is writeable, use this as default.
2293 if (FileName(trypath).isDirWritable())
2299 if (fname.empty()) {
2300 FileDialog dlg(qt_("Select document to open"));
2301 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2302 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2304 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2305 FileDialog::Result result =
2306 dlg.open(toqstr(initpath), filter);
2308 if (result.first == FileDialog::Later)
2311 filename = fromqstr(result.second);
2313 // check selected filename
2314 if (filename.empty()) {
2315 message(_("Canceled."));
2321 // get absolute path of file and add ".lyx" to the filename if
2323 FileName const fullname =
2324 fileSearch(string(), filename, "lyx", support::may_not_exist);
2325 if (!fullname.empty())
2326 filename = fullname.absFileName();
2328 if (!fullname.onlyPath().isDirectory()) {
2329 Alert::warning(_("Invalid filename"),
2330 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2331 from_utf8(fullname.absFileName())));
2335 // if the file doesn't exist and isn't already open (bug 6645),
2336 // let the user create one
2337 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2338 !LyXVC::file_not_found_hook(fullname)) {
2339 // the user specifically chose this name. Believe him.
2340 Buffer * const b = newFile(filename, string(), true);
2346 docstring const disp_fn = makeDisplayPath(filename);
2347 message(bformat(_("Opening document %1$s..."), disp_fn));
2350 Buffer * buf = loadDocument(fullname);
2352 str2 = bformat(_("Document %1$s opened."), disp_fn);
2353 if (buf->lyxvc().inUse())
2354 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2355 " " + _("Version control detected.");
2357 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2362 // FIXME: clean that
2363 static bool import(GuiView * lv, FileName const & filename,
2364 string const & format, ErrorList & errorList)
2366 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2368 string loader_format;
2369 vector<string> loaders = theConverters().loaders();
2370 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2371 vector<string>::const_iterator it = loaders.begin();
2372 vector<string>::const_iterator en = loaders.end();
2373 for (; it != en; ++it) {
2374 if (!theConverters().isReachable(format, *it))
2377 string const tofile =
2378 support::changeExtension(filename.absFileName(),
2379 theFormats().extension(*it));
2380 if (!theConverters().convert(0, filename, FileName(tofile),
2381 filename, format, *it, errorList))
2383 loader_format = *it;
2386 if (loader_format.empty()) {
2387 frontend::Alert::error(_("Couldn't import file"),
2388 bformat(_("No information for importing the format %1$s."),
2389 theFormats().prettyName(format)));
2393 loader_format = format;
2395 if (loader_format == "lyx") {
2396 Buffer * buf = lv->loadDocument(lyxfile);
2400 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2404 bool as_paragraphs = loader_format == "textparagraph";
2405 string filename2 = (loader_format == format) ? filename.absFileName()
2406 : support::changeExtension(filename.absFileName(),
2407 theFormats().extension(loader_format));
2408 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2410 guiApp->setCurrentView(lv);
2411 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2418 void GuiView::importDocument(string const & argument)
2421 string filename = split(argument, format, ' ');
2423 LYXERR(Debug::INFO, format << " file: " << filename);
2425 // need user interaction
2426 if (filename.empty()) {
2427 string initpath = lyxrc.document_path;
2428 if (documentBufferView()) {
2429 string const trypath = documentBufferView()->buffer().filePath();
2430 // If directory is writeable, use this as default.
2431 if (FileName(trypath).isDirWritable())
2435 docstring const text = bformat(_("Select %1$s file to import"),
2436 theFormats().prettyName(format));
2438 FileDialog dlg(toqstr(text));
2439 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2440 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2442 docstring filter = theFormats().prettyName(format);
2445 filter += from_utf8(theFormats().extensions(format));
2448 FileDialog::Result result =
2449 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2451 if (result.first == FileDialog::Later)
2454 filename = fromqstr(result.second);
2456 // check selected filename
2457 if (filename.empty())
2458 message(_("Canceled."));
2461 if (filename.empty())
2464 // get absolute path of file
2465 FileName const fullname(support::makeAbsPath(filename));
2467 // Can happen if the user entered a path into the dialog
2469 if (fullname.onlyFileName().empty()) {
2470 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2471 "Aborting import."),
2472 from_utf8(fullname.absFileName()));
2473 frontend::Alert::error(_("File name error"), msg);
2474 message(_("Canceled."));
2479 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2481 // Check if the document already is open
2482 Buffer * buf = theBufferList().getBuffer(lyxfile);
2485 if (!closeBuffer()) {
2486 message(_("Canceled."));
2491 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2493 // if the file exists already, and we didn't do
2494 // -i lyx thefile.lyx, warn
2495 if (lyxfile.exists() && fullname != lyxfile) {
2497 docstring text = bformat(_("The document %1$s already exists.\n\n"
2498 "Do you want to overwrite that document?"), displaypath);
2499 int const ret = Alert::prompt(_("Overwrite document?"),
2500 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2503 message(_("Canceled."));
2508 message(bformat(_("Importing %1$s..."), displaypath));
2509 ErrorList errorList;
2510 if (import(this, fullname, format, errorList))
2511 message(_("imported."));
2513 message(_("file not imported!"));
2515 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2519 void GuiView::newDocument(string const & filename, bool from_template)
2521 FileName initpath(lyxrc.document_path);
2522 if (documentBufferView()) {
2523 FileName const trypath(documentBufferView()->buffer().filePath());
2524 // If directory is writeable, use this as default.
2525 if (trypath.isDirWritable())
2529 string templatefile;
2530 if (from_template) {
2531 templatefile = selectTemplateFile().absFileName();
2532 if (templatefile.empty())
2537 if (filename.empty())
2538 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2540 b = newFile(filename, templatefile, true);
2545 // If no new document could be created, it is unsure
2546 // whether there is a valid BufferView.
2547 if (currentBufferView())
2548 // Ensure the cursor is correctly positioned on screen.
2549 currentBufferView()->showCursor();
2553 void GuiView::insertLyXFile(docstring const & fname)
2555 BufferView * bv = documentBufferView();
2560 FileName filename(to_utf8(fname));
2561 if (filename.empty()) {
2562 // Launch a file browser
2564 string initpath = lyxrc.document_path;
2565 string const trypath = bv->buffer().filePath();
2566 // If directory is writeable, use this as default.
2567 if (FileName(trypath).isDirWritable())
2571 FileDialog dlg(qt_("Select LyX document to insert"));
2572 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2573 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2575 FileDialog::Result result = dlg.open(toqstr(initpath),
2576 QStringList(qt_("LyX Documents (*.lyx)")));
2578 if (result.first == FileDialog::Later)
2582 filename.set(fromqstr(result.second));
2584 // check selected filename
2585 if (filename.empty()) {
2586 // emit message signal.
2587 message(_("Canceled."));
2592 bv->insertLyXFile(filename);
2593 bv->buffer().errors("Parse");
2597 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2599 FileName fname = b.fileName();
2600 FileName const oldname = fname;
2602 if (!newname.empty()) {
2604 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2606 // Switch to this Buffer.
2609 // No argument? Ask user through dialog.
2611 FileDialog dlg(qt_("Choose a filename to save document as"));
2612 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2613 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2615 if (!isLyXFileName(fname.absFileName()))
2616 fname.changeExtension(".lyx");
2618 FileDialog::Result result =
2619 dlg.save(toqstr(fname.onlyPath().absFileName()),
2620 QStringList(qt_("LyX Documents (*.lyx)")),
2621 toqstr(fname.onlyFileName()));
2623 if (result.first == FileDialog::Later)
2626 fname.set(fromqstr(result.second));
2631 if (!isLyXFileName(fname.absFileName()))
2632 fname.changeExtension(".lyx");
2635 // fname is now the new Buffer location.
2637 // if there is already a Buffer open with this name, we do not want
2638 // to have another one. (the second test makes sure we're not just
2639 // trying to overwrite ourselves, which is fine.)
2640 if (theBufferList().exists(fname) && fname != oldname
2641 && theBufferList().getBuffer(fname) != &b) {
2642 docstring const text =
2643 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2644 "Please close it before attempting to overwrite it.\n"
2645 "Do you want to choose a new filename?"),
2646 from_utf8(fname.absFileName()));
2647 int const ret = Alert::prompt(_("Chosen File Already Open"),
2648 text, 0, 1, _("&Rename"), _("&Cancel"));
2650 case 0: return renameBuffer(b, docstring(), kind);
2651 case 1: return false;
2656 bool const existsLocal = fname.exists();
2657 bool const existsInVC = LyXVC::fileInVC(fname);
2658 if (existsLocal || existsInVC) {
2659 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2660 if (kind != LV_WRITE_AS && existsInVC) {
2661 // renaming to a name that is already in VC
2663 docstring text = bformat(_("The document %1$s "
2664 "is already registered.\n\n"
2665 "Do you want to choose a new name?"),
2667 docstring const title = (kind == LV_VC_RENAME) ?
2668 _("Rename document?") : _("Copy document?");
2669 docstring const button = (kind == LV_VC_RENAME) ?
2670 _("&Rename") : _("&Copy");
2671 int const ret = Alert::prompt(title, text, 0, 1,
2672 button, _("&Cancel"));
2674 case 0: return renameBuffer(b, docstring(), kind);
2675 case 1: return false;
2680 docstring text = bformat(_("The document %1$s "
2681 "already exists.\n\n"
2682 "Do you want to overwrite that document?"),
2684 int const ret = Alert::prompt(_("Overwrite document?"),
2685 text, 0, 2, _("&Overwrite"),
2686 _("&Rename"), _("&Cancel"));
2689 case 1: return renameBuffer(b, docstring(), kind);
2690 case 2: return false;
2696 case LV_VC_RENAME: {
2697 string msg = b.lyxvc().rename(fname);
2700 message(from_utf8(msg));
2704 string msg = b.lyxvc().copy(fname);
2707 message(from_utf8(msg));
2713 // LyXVC created the file already in case of LV_VC_RENAME or
2714 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2715 // relative paths of included stuff right if we moved e.g. from
2716 // /a/b.lyx to /a/c/b.lyx.
2718 bool const saved = saveBuffer(b, fname);
2725 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2727 FileName fname = b.fileName();
2729 FileDialog dlg(qt_("Choose a filename to export the document as"));
2730 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2733 QString const anyformat = qt_("Guess from extension (*.*)");
2736 vector<Format const *> export_formats;
2737 for (Format const & f : theFormats())
2738 if (f.documentFormat())
2739 export_formats.push_back(&f);
2740 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2741 map<QString, string> fmap;
2744 for (Format const * f : export_formats) {
2745 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2746 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2748 from_ascii(f->extension())));
2749 types << loc_filter;
2750 fmap[loc_filter] = f->name();
2751 if (from_ascii(f->name()) == iformat) {
2752 filter = loc_filter;
2753 ext = f->extension();
2756 string ofname = fname.onlyFileName();
2758 ofname = support::changeExtension(ofname, ext);
2759 FileDialog::Result result =
2760 dlg.save(toqstr(fname.onlyPath().absFileName()),
2764 if (result.first != FileDialog::Chosen)
2768 fname.set(fromqstr(result.second));
2769 if (filter == anyformat)
2770 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2772 fmt_name = fmap[filter];
2773 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2774 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2776 if (fmt_name.empty() || fname.empty())
2779 // fname is now the new Buffer location.
2780 if (FileName(fname).exists()) {
2781 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2782 docstring text = bformat(_("The document %1$s already "
2783 "exists.\n\nDo you want to "
2784 "overwrite that document?"),
2786 int const ret = Alert::prompt(_("Overwrite document?"),
2787 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2790 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2791 case 2: return false;
2795 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2798 return dr.dispatched();
2802 bool GuiView::saveBuffer(Buffer & b)
2804 return saveBuffer(b, FileName());
2808 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2810 if (workArea(b) && workArea(b)->inDialogMode())
2813 if (fn.empty() && b.isUnnamed())
2814 return renameBuffer(b, docstring());
2816 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2818 theSession().lastFiles().add(b.fileName());
2819 theSession().writeFile();
2823 // Switch to this Buffer.
2826 // FIXME: we don't tell the user *WHY* the save failed !!
2827 docstring const file = makeDisplayPath(b.absFileName(), 30);
2828 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2829 "Do you want to rename the document and "
2830 "try again?"), file);
2831 int const ret = Alert::prompt(_("Rename and save?"),
2832 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2835 if (!renameBuffer(b, docstring()))
2844 return saveBuffer(b, fn);
2848 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2850 return closeWorkArea(wa, false);
2854 // We only want to close the buffer if it is not visible in other workareas
2855 // of the same view, nor in other views, and if this is not a child
2856 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2858 Buffer & buf = wa->bufferView().buffer();
2860 bool last_wa = d.countWorkAreasOf(buf) == 1
2861 && !inOtherView(buf) && !buf.parent();
2863 bool close_buffer = last_wa;
2866 if (lyxrc.close_buffer_with_last_view == "yes")
2868 else if (lyxrc.close_buffer_with_last_view == "no")
2869 close_buffer = false;
2872 if (buf.isUnnamed())
2873 file = from_utf8(buf.fileName().onlyFileName());
2875 file = buf.fileName().displayName(30);
2876 docstring const text = bformat(
2877 _("Last view on document %1$s is being closed.\n"
2878 "Would you like to close or hide the document?\n"
2880 "Hidden documents can be displayed back through\n"
2881 "the menu: View->Hidden->...\n"
2883 "To remove this question, set your preference in:\n"
2884 " Tools->Preferences->Look&Feel->UserInterface\n"
2886 int ret = Alert::prompt(_("Close or hide document?"),
2887 text, 0, 1, _("&Close"), _("&Hide"));
2888 close_buffer = (ret == 0);
2892 return closeWorkArea(wa, close_buffer);
2896 bool GuiView::closeBuffer()
2898 GuiWorkArea * wa = currentMainWorkArea();
2899 // coverity complained about this
2900 // it seems unnecessary, but perhaps is worth the check
2901 LASSERT(wa, return false);
2903 setCurrentWorkArea(wa);
2904 Buffer & buf = wa->bufferView().buffer();
2905 return closeWorkArea(wa, !buf.parent());
2909 void GuiView::writeSession() const {
2910 GuiWorkArea const * active_wa = currentMainWorkArea();
2911 for (int i = 0; i < d.splitter_->count(); ++i) {
2912 TabWorkArea * twa = d.tabWorkArea(i);
2913 for (int j = 0; j < twa->count(); ++j) {
2914 GuiWorkArea * wa = twa->workArea(j);
2915 Buffer & buf = wa->bufferView().buffer();
2916 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2922 bool GuiView::closeBufferAll()
2924 // Close the workareas in all other views
2925 QList<int> const ids = guiApp->viewIds();
2926 for (int i = 0; i != ids.size(); ++i) {
2927 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2931 // Close our own workareas
2932 if (!closeWorkAreaAll())
2935 // Now close the hidden buffers. We prevent hidden buffers from being
2936 // dirty, so we can just close them.
2937 theBufferList().closeAll();
2942 bool GuiView::closeWorkAreaAll()
2944 setCurrentWorkArea(currentMainWorkArea());
2946 // We might be in a situation that there is still a tabWorkArea, but
2947 // there are no tabs anymore. This can happen when we get here after a
2948 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2949 // many TabWorkArea's have no documents anymore.
2952 // We have to call count() each time, because it can happen that
2953 // more than one splitter will disappear in one iteration (bug 5998).
2954 while (d.splitter_->count() > empty_twa) {
2955 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2957 if (twa->count() == 0)
2960 setCurrentWorkArea(twa->currentWorkArea());
2961 if (!closeTabWorkArea(twa))
2969 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2974 Buffer & buf = wa->bufferView().buffer();
2976 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2977 Alert::warning(_("Close document"),
2978 _("Document could not be closed because it is being processed by LyX."));
2983 return closeBuffer(buf);
2985 if (!inMultiTabs(wa))
2986 if (!saveBufferIfNeeded(buf, true))
2994 bool GuiView::closeBuffer(Buffer & buf)
2996 // If we are in a close_event all children will be closed in some time,
2997 // so no need to do it here. This will ensure that the children end up
2998 // in the session file in the correct order. If we close the master
2999 // buffer, we can close or release the child buffers here too.
3000 bool success = true;
3002 ListOfBuffers clist = buf.getChildren();
3003 ListOfBuffers::const_iterator it = clist.begin();
3004 ListOfBuffers::const_iterator const bend = clist.end();
3005 for (; it != bend; ++it) {
3006 Buffer * child_buf = *it;
3007 if (theBufferList().isOthersChild(&buf, child_buf)) {
3008 child_buf->setParent(0);
3012 // FIXME: should we look in other tabworkareas?
3013 // ANSWER: I don't think so. I've tested, and if the child is
3014 // open in some other window, it closes without a problem.
3015 GuiWorkArea * child_wa = workArea(*child_buf);
3017 success = closeWorkArea(child_wa, true);
3021 // In this case the child buffer is open but hidden.
3022 // It therefore should not (MUST NOT) be dirty!
3023 LATTEST(child_buf->isClean());
3024 theBufferList().release(child_buf);
3029 // goto bookmark to update bookmark pit.
3030 // FIXME: we should update only the bookmarks related to this buffer!
3031 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3032 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3033 guiApp->gotoBookmark(i+1, false, false);
3035 if (saveBufferIfNeeded(buf, false)) {
3036 buf.removeAutosaveFile();
3037 theBufferList().release(&buf);
3041 // open all children again to avoid a crash because of dangling
3042 // pointers (bug 6603)
3048 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3050 while (twa == d.currentTabWorkArea()) {
3051 twa->setCurrentIndex(twa->count() - 1);
3053 GuiWorkArea * wa = twa->currentWorkArea();
3054 Buffer & b = wa->bufferView().buffer();
3056 // We only want to close the buffer if the same buffer is not visible
3057 // in another view, and if this is not a child and if we are closing
3058 // a view (not a tabgroup).
3059 bool const close_buffer =
3060 !inOtherView(b) && !b.parent() && closing_;
3062 if (!closeWorkArea(wa, close_buffer))
3069 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3071 if (buf.isClean() || buf.paragraphs().empty())
3074 // Switch to this Buffer.
3080 if (buf.isUnnamed()) {
3081 file = from_utf8(buf.fileName().onlyFileName());
3084 FileName filename = buf.fileName();
3086 file = filename.displayName(30);
3087 exists = filename.exists();
3090 // Bring this window to top before asking questions.
3095 if (hiding && buf.isUnnamed()) {
3096 docstring const text = bformat(_("The document %1$s has not been "
3097 "saved yet.\n\nDo you want to save "
3098 "the document?"), file);
3099 ret = Alert::prompt(_("Save new document?"),
3100 text, 0, 1, _("&Save"), _("&Cancel"));
3104 docstring const text = exists ?
3105 bformat(_("The document %1$s has unsaved changes."
3106 "\n\nDo you want to save the document or "
3107 "discard the changes?"), file) :
3108 bformat(_("The document %1$s has not been saved yet."
3109 "\n\nDo you want to save the document or "
3110 "discard it entirely?"), file);
3111 docstring const title = exists ?
3112 _("Save changed document?") : _("Save document?");
3113 ret = Alert::prompt(title, text, 0, 2,
3114 _("&Save"), _("&Discard"), _("&Cancel"));
3119 if (!saveBuffer(buf))
3123 // If we crash after this we could have no autosave file
3124 // but I guess this is really improbable (Jug).
3125 // Sometimes improbable things happen:
3126 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3127 // buf.removeAutosaveFile();
3129 // revert all changes
3140 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3142 Buffer & buf = wa->bufferView().buffer();
3144 for (int i = 0; i != d.splitter_->count(); ++i) {
3145 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3146 if (wa_ && wa_ != wa)
3149 return inOtherView(buf);
3153 bool GuiView::inOtherView(Buffer & buf)
3155 QList<int> const ids = guiApp->viewIds();
3157 for (int i = 0; i != ids.size(); ++i) {
3161 if (guiApp->view(ids[i]).workArea(buf))
3168 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3170 if (!documentBufferView())
3173 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3174 Buffer * const curbuf = &documentBufferView()->buffer();
3175 int nwa = twa->count();
3176 for (int i = 0; i < nwa; ++i) {
3177 if (&workArea(i)->bufferView().buffer() == curbuf) {
3179 if (np == NEXTBUFFER)
3180 next_index = (i == nwa - 1 ? 0 : i + 1);
3182 next_index = (i == 0 ? nwa - 1 : i - 1);
3184 twa->moveTab(i, next_index);
3186 setBuffer(&workArea(next_index)->bufferView().buffer());
3194 /// make sure the document is saved
3195 static bool ensureBufferClean(Buffer * buffer)
3197 LASSERT(buffer, return false);
3198 if (buffer->isClean() && !buffer->isUnnamed())
3201 docstring const file = buffer->fileName().displayName(30);
3204 if (!buffer->isUnnamed()) {
3205 text = bformat(_("The document %1$s has unsaved "
3206 "changes.\n\nDo you want to save "
3207 "the document?"), file);
3208 title = _("Save changed document?");
3211 text = bformat(_("The document %1$s has not been "
3212 "saved yet.\n\nDo you want to save "
3213 "the document?"), file);
3214 title = _("Save new document?");
3216 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3219 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3221 return buffer->isClean() && !buffer->isUnnamed();
3225 bool GuiView::reloadBuffer(Buffer & buf)
3227 currentBufferView()->cursor().reset();
3228 Buffer::ReadStatus status = buf.reload();
3229 return status == Buffer::ReadSuccess;
3233 void GuiView::checkExternallyModifiedBuffers()
3235 BufferList::iterator bit = theBufferList().begin();
3236 BufferList::iterator const bend = theBufferList().end();
3237 for (; bit != bend; ++bit) {
3238 Buffer * buf = *bit;
3239 if (buf->fileName().exists() && buf->isChecksumModified()) {
3240 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3241 " Reload now? Any local changes will be lost."),
3242 from_utf8(buf->absFileName()));
3243 int const ret = Alert::prompt(_("Reload externally changed document?"),
3244 text, 0, 1, _("&Reload"), _("&Cancel"));
3252 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3254 Buffer * buffer = documentBufferView()
3255 ? &(documentBufferView()->buffer()) : 0;
3257 switch (cmd.action()) {
3258 case LFUN_VC_REGISTER:
3259 if (!buffer || !ensureBufferClean(buffer))
3261 if (!buffer->lyxvc().inUse()) {
3262 if (buffer->lyxvc().registrer()) {
3263 reloadBuffer(*buffer);
3264 dr.clearMessageUpdate();
3269 case LFUN_VC_RENAME:
3270 case LFUN_VC_COPY: {
3271 if (!buffer || !ensureBufferClean(buffer))
3273 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3274 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3275 // Some changes are not yet committed.
3276 // We test here and not in getStatus(), since
3277 // this test is expensive.
3279 LyXVC::CommandResult ret =
3280 buffer->lyxvc().checkIn(log);
3282 if (ret == LyXVC::ErrorCommand ||
3283 ret == LyXVC::VCSuccess)
3284 reloadBuffer(*buffer);
3285 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3286 frontend::Alert::error(
3287 _("Revision control error."),
3288 _("Document could not be checked in."));
3292 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3293 LV_VC_RENAME : LV_VC_COPY;
3294 renameBuffer(*buffer, cmd.argument(), kind);
3299 case LFUN_VC_CHECK_IN:
3300 if (!buffer || !ensureBufferClean(buffer))
3302 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3304 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3306 // Only skip reloading if the checkin was cancelled or
3307 // an error occurred before the real checkin VCS command
3308 // was executed, since the VCS might have changed the
3309 // file even if it could not checkin successfully.
3310 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3311 reloadBuffer(*buffer);
3315 case LFUN_VC_CHECK_OUT:
3316 if (!buffer || !ensureBufferClean(buffer))
3318 if (buffer->lyxvc().inUse()) {
3319 dr.setMessage(buffer->lyxvc().checkOut());
3320 reloadBuffer(*buffer);
3324 case LFUN_VC_LOCKING_TOGGLE:
3325 LASSERT(buffer, return);
3326 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3328 if (buffer->lyxvc().inUse()) {
3329 string res = buffer->lyxvc().lockingToggle();
3331 frontend::Alert::error(_("Revision control error."),
3332 _("Error when setting the locking property."));
3335 reloadBuffer(*buffer);
3340 case LFUN_VC_REVERT:
3341 LASSERT(buffer, return);
3342 if (buffer->lyxvc().revert()) {
3343 reloadBuffer(*buffer);
3344 dr.clearMessageUpdate();
3348 case LFUN_VC_UNDO_LAST:
3349 LASSERT(buffer, return);
3350 buffer->lyxvc().undoLast();
3351 reloadBuffer(*buffer);
3352 dr.clearMessageUpdate();
3355 case LFUN_VC_REPO_UPDATE:
3356 LASSERT(buffer, return);
3357 if (ensureBufferClean(buffer)) {
3358 dr.setMessage(buffer->lyxvc().repoUpdate());
3359 checkExternallyModifiedBuffers();
3363 case LFUN_VC_COMMAND: {
3364 string flag = cmd.getArg(0);
3365 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3368 if (contains(flag, 'M')) {
3369 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3372 string path = cmd.getArg(1);
3373 if (contains(path, "$$p") && buffer)
3374 path = subst(path, "$$p", buffer->filePath());
3375 LYXERR(Debug::LYXVC, "Directory: " << path);
3377 if (!pp.isReadableDirectory()) {
3378 lyxerr << _("Directory is not accessible.") << endl;
3381 support::PathChanger p(pp);
3383 string command = cmd.getArg(2);
3384 if (command.empty())
3387 command = subst(command, "$$i", buffer->absFileName());
3388 command = subst(command, "$$p", buffer->filePath());
3390 command = subst(command, "$$m", to_utf8(message));
3391 LYXERR(Debug::LYXVC, "Command: " << command);
3393 one.startscript(Systemcall::Wait, command);
3397 if (contains(flag, 'I'))
3398 buffer->markDirty();
3399 if (contains(flag, 'R'))
3400 reloadBuffer(*buffer);
3405 case LFUN_VC_COMPARE: {
3406 if (cmd.argument().empty()) {
3407 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3411 string rev1 = cmd.getArg(0);
3416 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3419 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3420 f2 = buffer->absFileName();
3422 string rev2 = cmd.getArg(1);
3426 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3430 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3431 f1 << "\n" << f2 << "\n" );
3432 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3433 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3443 void GuiView::openChildDocument(string const & fname)
3445 LASSERT(documentBufferView(), return);
3446 Buffer & buffer = documentBufferView()->buffer();
3447 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3448 documentBufferView()->saveBookmark(false);
3450 if (theBufferList().exists(filename)) {
3451 child = theBufferList().getBuffer(filename);
3454 message(bformat(_("Opening child document %1$s..."),
3455 makeDisplayPath(filename.absFileName())));
3456 child = loadDocument(filename, false);
3458 // Set the parent name of the child document.
3459 // This makes insertion of citations and references in the child work,
3460 // when the target is in the parent or another child document.
3462 child->setParent(&buffer);
3466 bool GuiView::goToFileRow(string const & argument)
3470 size_t i = argument.find_last_of(' ');
3471 if (i != string::npos) {
3472 file_name = os::internal_path(trim(argument.substr(0, i)));
3473 istringstream is(argument.substr(i + 1));
3478 if (i == string::npos) {
3479 LYXERR0("Wrong argument: " << argument);
3483 string const abstmp = package().temp_dir().absFileName();
3484 string const realtmp = package().temp_dir().realPath();
3485 // We have to use os::path_prefix_is() here, instead of
3486 // simply prefixIs(), because the file name comes from
3487 // an external application and may need case adjustment.
3488 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3489 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3490 // Needed by inverse dvi search. If it is a file
3491 // in tmpdir, call the apropriated function.
3492 // If tmpdir is a symlink, we may have the real
3493 // path passed back, so we correct for that.
3494 if (!prefixIs(file_name, abstmp))
3495 file_name = subst(file_name, realtmp, abstmp);
3496 buf = theBufferList().getBufferFromTmp(file_name);
3498 // Must replace extension of the file to be .lyx
3499 // and get full path
3500 FileName const s = fileSearch(string(),
3501 support::changeExtension(file_name, ".lyx"), "lyx");
3502 // Either change buffer or load the file
3503 if (theBufferList().exists(s))
3504 buf = theBufferList().getBuffer(s);
3505 else if (s.exists()) {
3506 buf = loadDocument(s);
3511 _("File does not exist: %1$s"),
3512 makeDisplayPath(file_name)));
3518 _("No buffer for file: %1$s."),
3519 makeDisplayPath(file_name))
3524 bool success = documentBufferView()->setCursorFromRow(row);
3526 LYXERR(Debug::LATEX,
3527 "setCursorFromRow: invalid position for row " << row);
3528 frontend::Alert::error(_("Inverse Search Failed"),
3529 _("Invalid position requested by inverse search.\n"
3530 "You may need to update the viewed document."));
3536 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3538 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3539 menu->exec(QCursor::pos());
3544 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3546 Buffer::ExportStatus const status = func(format);
3548 // the cloning operation will have produced a clone of the entire set of
3549 // documents, starting from the master. so we must delete those.
3550 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3552 busyBuffers.remove(orig);
3557 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3559 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3560 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3564 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3566 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3567 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3571 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3573 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3574 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3578 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3579 string const & argument,
3580 Buffer const * used_buffer,
3581 docstring const & msg,
3582 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3583 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3584 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3590 string format = argument;
3592 format = used_buffer->params().getDefaultOutputFormat();
3593 processing_format = format;
3595 progress_->clearMessages();
3598 #if EXPORT_in_THREAD
3600 GuiViewPrivate::busyBuffers.insert(used_buffer);
3601 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3602 if (!cloned_buffer) {
3603 Alert::error(_("Export Error"),
3604 _("Error cloning the Buffer."));
3607 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3612 setPreviewFuture(f);
3613 last_export_format = used_buffer->params().bufferFormat();
3616 // We are asynchronous, so we don't know here anything about the success
3619 Buffer::ExportStatus status;
3621 status = (used_buffer->*syncFunc)(format, false);
3622 } else if (previewFunc) {
3623 status = (used_buffer->*previewFunc)(format);
3626 handleExportStatus(gv_, status, format);
3628 return (status == Buffer::ExportSuccess
3629 || status == Buffer::PreviewSuccess);
3632 Buffer::ExportStatus status;
3634 status = (used_buffer->*syncFunc)(format, true);
3635 } else if (previewFunc) {
3636 status = (used_buffer->*previewFunc)(format);
3639 handleExportStatus(gv_, status, format);
3641 return (status == Buffer::ExportSuccess
3642 || status == Buffer::PreviewSuccess);
3646 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3648 BufferView * bv = currentBufferView();
3649 LASSERT(bv, return);
3651 // Let the current BufferView dispatch its own actions.
3652 bv->dispatch(cmd, dr);
3653 if (dr.dispatched())
3656 // Try with the document BufferView dispatch if any.
3657 BufferView * doc_bv = documentBufferView();
3658 if (doc_bv && doc_bv != bv) {
3659 doc_bv->dispatch(cmd, dr);
3660 if (dr.dispatched())
3664 // Then let the current Cursor dispatch its own actions.
3665 bv->cursor().dispatch(cmd);
3667 // update completion. We do it here and not in
3668 // processKeySym to avoid another redraw just for a
3669 // changed inline completion
3670 if (cmd.origin() == FuncRequest::KEYBOARD) {
3671 if (cmd.action() == LFUN_SELF_INSERT
3672 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3673 updateCompletion(bv->cursor(), true, true);
3674 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3675 updateCompletion(bv->cursor(), false, true);
3677 updateCompletion(bv->cursor(), false, false);
3680 dr = bv->cursor().result();
3684 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3686 BufferView * bv = currentBufferView();
3687 // By default we won't need any update.
3688 dr.screenUpdate(Update::None);
3689 // assume cmd will be dispatched
3690 dr.dispatched(true);
3692 Buffer * doc_buffer = documentBufferView()
3693 ? &(documentBufferView()->buffer()) : 0;
3695 if (cmd.origin() == FuncRequest::TOC) {
3696 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3697 // FIXME: do we need to pass a DispatchResult object here?
3698 toc->doDispatch(bv->cursor(), cmd);
3702 string const argument = to_utf8(cmd.argument());
3704 switch(cmd.action()) {
3705 case LFUN_BUFFER_CHILD_OPEN:
3706 openChildDocument(to_utf8(cmd.argument()));
3709 case LFUN_BUFFER_IMPORT:
3710 importDocument(to_utf8(cmd.argument()));
3713 case LFUN_MASTER_BUFFER_EXPORT:
3715 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3717 case LFUN_BUFFER_EXPORT: {
3720 // GCC only sees strfwd.h when building merged
3721 if (::lyx::operator==(cmd.argument(), "custom")) {
3722 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3723 // so the following test should not be needed.
3724 // In principle, we could try to switch to such a view...
3725 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3726 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3730 string const dest = cmd.getArg(1);
3731 FileName target_dir;
3732 if (!dest.empty() && FileName::isAbsolute(dest))
3733 target_dir = FileName(support::onlyPath(dest));
3735 target_dir = doc_buffer->fileName().onlyPath();
3737 string const format = (argument.empty() || argument == "default") ?
3738 doc_buffer->params().getDefaultOutputFormat() : argument;
3740 if ((dest.empty() && doc_buffer->isUnnamed())
3741 || !target_dir.isDirWritable()) {
3742 exportBufferAs(*doc_buffer, from_utf8(format));
3745 /* TODO/Review: Is it a problem to also export the children?
3746 See the update_unincluded flag */
3747 d.asyncBufferProcessing(format,
3750 &GuiViewPrivate::exportAndDestroy,
3752 0, cmd.allowAsync());
3753 // TODO Inform user about success
3757 case LFUN_BUFFER_EXPORT_AS: {
3758 LASSERT(doc_buffer, break);
3759 docstring f = cmd.argument();
3761 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3762 exportBufferAs(*doc_buffer, f);
3766 case LFUN_BUFFER_UPDATE: {
3767 d.asyncBufferProcessing(argument,
3770 &GuiViewPrivate::compileAndDestroy,
3772 0, cmd.allowAsync());
3775 case LFUN_BUFFER_VIEW: {
3776 d.asyncBufferProcessing(argument,
3778 _("Previewing ..."),
3779 &GuiViewPrivate::previewAndDestroy,
3781 &Buffer::preview, cmd.allowAsync());
3784 case LFUN_MASTER_BUFFER_UPDATE: {
3785 d.asyncBufferProcessing(argument,
3786 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3788 &GuiViewPrivate::compileAndDestroy,
3790 0, cmd.allowAsync());
3793 case LFUN_MASTER_BUFFER_VIEW: {
3794 d.asyncBufferProcessing(argument,
3795 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3797 &GuiViewPrivate::previewAndDestroy,
3798 0, &Buffer::preview, cmd.allowAsync());
3801 case LFUN_BUFFER_SWITCH: {
3802 string const file_name = to_utf8(cmd.argument());
3803 if (!FileName::isAbsolute(file_name)) {
3805 dr.setMessage(_("Absolute filename expected."));
3809 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3812 dr.setMessage(_("Document not loaded"));
3816 // Do we open or switch to the buffer in this view ?
3817 if (workArea(*buffer)
3818 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3823 // Look for the buffer in other views
3824 QList<int> const ids = guiApp->viewIds();
3826 for (; i != ids.size(); ++i) {
3827 GuiView & gv = guiApp->view(ids[i]);
3828 if (gv.workArea(*buffer)) {
3830 gv.activateWindow();
3832 gv.setBuffer(buffer);
3837 // If necessary, open a new window as a last resort
3838 if (i == ids.size()) {
3839 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3845 case LFUN_BUFFER_NEXT:
3846 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3849 case LFUN_BUFFER_MOVE_NEXT:
3850 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3853 case LFUN_BUFFER_PREVIOUS:
3854 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3857 case LFUN_BUFFER_MOVE_PREVIOUS:
3858 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3861 case LFUN_BUFFER_CHKTEX:
3862 LASSERT(doc_buffer, break);
3863 doc_buffer->runChktex();
3866 case LFUN_COMMAND_EXECUTE: {
3867 command_execute_ = true;
3868 minibuffer_focus_ = true;
3871 case LFUN_DROP_LAYOUTS_CHOICE:
3872 d.layout_->showPopup();
3875 case LFUN_MENU_OPEN:
3876 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3877 menu->exec(QCursor::pos());
3880 case LFUN_FILE_INSERT:
3881 insertLyXFile(cmd.argument());
3884 case LFUN_FILE_INSERT_PLAINTEXT:
3885 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3886 string const fname = to_utf8(cmd.argument());
3887 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3888 dr.setMessage(_("Absolute filename expected."));
3892 FileName filename(fname);
3893 if (fname.empty()) {
3894 FileDialog dlg(qt_("Select file to insert"));
3896 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3897 QStringList(qt_("All Files (*)")));
3899 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3900 dr.setMessage(_("Canceled."));
3904 filename.set(fromqstr(result.second));
3908 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3909 bv->dispatch(new_cmd, dr);
3914 case LFUN_BUFFER_RELOAD: {
3915 LASSERT(doc_buffer, break);
3918 if (!doc_buffer->isClean()) {
3919 docstring const file =
3920 makeDisplayPath(doc_buffer->absFileName(), 20);
3921 if (doc_buffer->notifiesExternalModification()) {
3922 docstring text = _("The current version will be lost. "
3923 "Are you sure you want to load the version on disk "
3924 "of the document %1$s?");
3925 ret = Alert::prompt(_("Reload saved document?"),
3926 bformat(text, file), 1, 1,
3927 _("&Reload"), _("&Cancel"));
3929 docstring text = _("Any changes will be lost. "
3930 "Are you sure you want to revert to the saved version "
3931 "of the document %1$s?");
3932 ret = Alert::prompt(_("Revert to saved document?"),
3933 bformat(text, file), 1, 1,
3934 _("&Revert"), _("&Cancel"));
3939 doc_buffer->markClean();
3940 reloadBuffer(*doc_buffer);
3941 dr.forceBufferUpdate();
3946 case LFUN_BUFFER_WRITE:
3947 LASSERT(doc_buffer, break);
3948 saveBuffer(*doc_buffer);
3951 case LFUN_BUFFER_WRITE_AS:
3952 LASSERT(doc_buffer, break);
3953 renameBuffer(*doc_buffer, cmd.argument());
3956 case LFUN_BUFFER_WRITE_ALL: {
3957 Buffer * first = theBufferList().first();
3960 message(_("Saving all documents..."));
3961 // We cannot use a for loop as the buffer list cycles.
3964 if (!b->isClean()) {
3966 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3968 b = theBufferList().next(b);
3969 } while (b != first);
3970 dr.setMessage(_("All documents saved."));
3974 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3975 LASSERT(doc_buffer, break);
3976 doc_buffer->clearExternalModification();
3979 case LFUN_BUFFER_CLOSE:
3983 case LFUN_BUFFER_CLOSE_ALL:
3987 case LFUN_DEVEL_MODE_TOGGLE:
3988 devel_mode_ = !devel_mode_;
3990 dr.setMessage(_("Developer mode is now enabled."));
3992 dr.setMessage(_("Developer mode is now disabled."));
3995 case LFUN_TOOLBAR_TOGGLE: {
3996 string const name = cmd.getArg(0);
3997 if (GuiToolbar * t = toolbar(name))
4002 case LFUN_TOOLBAR_MOVABLE: {
4003 string const name = cmd.getArg(0);
4005 // toggle (all) toolbars movablility
4006 toolbarsMovable_ = !toolbarsMovable_;
4007 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4008 GuiToolbar * tb = toolbar(ti.name);
4009 if (tb && tb->isMovable() != toolbarsMovable_)
4010 // toggle toolbar movablity if it does not fit lock
4011 // (all) toolbars positions state silent = true, since
4012 // status bar notifications are slow
4015 if (toolbarsMovable_)
4016 dr.setMessage(_("Toolbars unlocked."));
4018 dr.setMessage(_("Toolbars locked."));
4019 } else if (GuiToolbar * t = toolbar(name)) {
4020 // toggle current toolbar movablity
4022 // update lock (all) toolbars positions
4023 updateLockToolbars();
4028 case LFUN_ICON_SIZE: {
4029 QSize size = d.iconSize(cmd.argument());
4031 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4032 size.width(), size.height()));
4036 case LFUN_DIALOG_UPDATE: {
4037 string const name = to_utf8(cmd.argument());
4038 if (name == "prefs" || name == "document")
4039 updateDialog(name, string());
4040 else if (name == "paragraph")
4041 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4042 else if (currentBufferView()) {
4043 Inset * inset = currentBufferView()->editedInset(name);
4044 // Can only update a dialog connected to an existing inset
4046 // FIXME: get rid of this indirection; GuiView ask the inset
4047 // if he is kind enough to update itself...
4048 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4049 //FIXME: pass DispatchResult here?
4050 inset->dispatch(currentBufferView()->cursor(), fr);
4056 case LFUN_DIALOG_TOGGLE: {
4057 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4058 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4059 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4063 case LFUN_DIALOG_DISCONNECT_INSET:
4064 disconnectDialog(to_utf8(cmd.argument()));
4067 case LFUN_DIALOG_HIDE: {
4068 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4072 case LFUN_DIALOG_SHOW: {
4073 string const name = cmd.getArg(0);
4074 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
4076 if (name == "character") {
4077 data = freefont2string();
4079 showDialog("character", data);
4080 } else if (name == "latexlog") {
4081 // getStatus checks that
4082 LATTEST(doc_buffer);
4083 Buffer::LogType type;
4084 string const logfile = doc_buffer->logName(&type);
4086 case Buffer::latexlog:
4089 case Buffer::buildlog:
4093 data += Lexer::quoteString(logfile);
4094 showDialog("log", data);
4095 } else if (name == "vclog") {
4096 // getStatus checks that
4097 LATTEST(doc_buffer);
4098 string const data = "vc " +
4099 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4100 showDialog("log", data);
4101 } else if (name == "symbols") {
4102 data = bv->cursor().getEncoding()->name();
4104 showDialog("symbols", data);
4106 } else if (name == "prefs" && isFullScreen()) {
4107 lfunUiToggle("fullscreen");
4108 showDialog("prefs", data);
4110 showDialog(name, data);
4115 dr.setMessage(cmd.argument());
4118 case LFUN_UI_TOGGLE: {
4119 string arg = cmd.getArg(0);
4120 if (!lfunUiToggle(arg)) {
4121 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4122 dr.setMessage(bformat(msg, from_utf8(arg)));
4124 // Make sure the keyboard focus stays in the work area.
4129 case LFUN_VIEW_SPLIT: {
4130 LASSERT(doc_buffer, break);
4131 string const orientation = cmd.getArg(0);
4132 d.splitter_->setOrientation(orientation == "vertical"
4133 ? Qt::Vertical : Qt::Horizontal);
4134 TabWorkArea * twa = addTabWorkArea();
4135 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4136 setCurrentWorkArea(wa);
4139 case LFUN_TAB_GROUP_CLOSE:
4140 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4141 closeTabWorkArea(twa);
4142 d.current_work_area_ = 0;
4143 twa = d.currentTabWorkArea();
4144 // Switch to the next GuiWorkArea in the found TabWorkArea.
4146 // Make sure the work area is up to date.
4147 setCurrentWorkArea(twa->currentWorkArea());
4149 setCurrentWorkArea(0);
4154 case LFUN_VIEW_CLOSE:
4155 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4156 closeWorkArea(twa->currentWorkArea());
4157 d.current_work_area_ = 0;
4158 twa = d.currentTabWorkArea();
4159 // Switch to the next GuiWorkArea in the found TabWorkArea.
4161 // Make sure the work area is up to date.
4162 setCurrentWorkArea(twa->currentWorkArea());
4164 setCurrentWorkArea(0);
4169 case LFUN_COMPLETION_INLINE:
4170 if (d.current_work_area_)
4171 d.current_work_area_->completer().showInline();
4174 case LFUN_COMPLETION_POPUP:
4175 if (d.current_work_area_)
4176 d.current_work_area_->completer().showPopup();
4181 if (d.current_work_area_)
4182 d.current_work_area_->completer().tab();
4185 case LFUN_COMPLETION_CANCEL:
4186 if (d.current_work_area_) {
4187 if (d.current_work_area_->completer().popupVisible())
4188 d.current_work_area_->completer().hidePopup();
4190 d.current_work_area_->completer().hideInline();
4194 case LFUN_COMPLETION_ACCEPT:
4195 if (d.current_work_area_)
4196 d.current_work_area_->completer().activate();
4199 case LFUN_BUFFER_ZOOM_IN:
4200 case LFUN_BUFFER_ZOOM_OUT:
4201 case LFUN_BUFFER_ZOOM: {
4202 if (cmd.argument().empty()) {
4203 if (cmd.action() == LFUN_BUFFER_ZOOM)
4205 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4210 if (cmd.action() == LFUN_BUFFER_ZOOM)
4211 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4212 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4213 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4215 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4218 // Actual zoom value: default zoom + fractional extra value
4219 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4220 if (zoom < static_cast<int>(zoom_min_))
4223 lyxrc.currentZoom = zoom;
4225 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4226 lyxrc.currentZoom, lyxrc.defaultZoom));
4228 // The global QPixmapCache is used in GuiPainter to cache text
4229 // painting so we must reset it.
4230 QPixmapCache::clear();
4231 guiApp->fontLoader().update();
4232 dr.screenUpdate(Update::Force | Update::FitCursor);
4236 case LFUN_VC_REGISTER:
4237 case LFUN_VC_RENAME:
4239 case LFUN_VC_CHECK_IN:
4240 case LFUN_VC_CHECK_OUT:
4241 case LFUN_VC_REPO_UPDATE:
4242 case LFUN_VC_LOCKING_TOGGLE:
4243 case LFUN_VC_REVERT:
4244 case LFUN_VC_UNDO_LAST:
4245 case LFUN_VC_COMMAND:
4246 case LFUN_VC_COMPARE:
4247 dispatchVC(cmd, dr);
4250 case LFUN_SERVER_GOTO_FILE_ROW:
4251 if(goToFileRow(to_utf8(cmd.argument())))
4252 dr.screenUpdate(Update::Force | Update::FitCursor);
4255 case LFUN_LYX_ACTIVATE:
4259 case LFUN_FORWARD_SEARCH: {
4260 // it seems safe to assume we have a document buffer, since
4261 // getStatus wants one.
4262 LATTEST(doc_buffer);
4263 Buffer const * doc_master = doc_buffer->masterBuffer();
4264 FileName const path(doc_master->temppath());
4265 string const texname = doc_master->isChild(doc_buffer)
4266 ? DocFileName(changeExtension(
4267 doc_buffer->absFileName(),
4268 "tex")).mangledFileName()
4269 : doc_buffer->latexName();
4270 string const fulltexname =
4271 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4272 string const mastername =
4273 removeExtension(doc_master->latexName());
4274 FileName const dviname(addName(path.absFileName(),
4275 addExtension(mastername, "dvi")));
4276 FileName const pdfname(addName(path.absFileName(),
4277 addExtension(mastername, "pdf")));
4278 bool const have_dvi = dviname.exists();
4279 bool const have_pdf = pdfname.exists();
4280 if (!have_dvi && !have_pdf) {
4281 dr.setMessage(_("Please, preview the document first."));
4284 string outname = dviname.onlyFileName();
4285 string command = lyxrc.forward_search_dvi;
4286 if (!have_dvi || (have_pdf &&
4287 pdfname.lastModified() > dviname.lastModified())) {
4288 outname = pdfname.onlyFileName();
4289 command = lyxrc.forward_search_pdf;
4292 DocIterator cur = bv->cursor();
4293 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4294 LYXERR(Debug::ACTION, "Forward search: row:" << row
4296 if (row == -1 || command.empty()) {
4297 dr.setMessage(_("Couldn't proceed."));
4300 string texrow = convert<string>(row);
4302 command = subst(command, "$$n", texrow);
4303 command = subst(command, "$$f", fulltexname);
4304 command = subst(command, "$$t", texname);
4305 command = subst(command, "$$o", outname);
4307 PathChanger p(path);
4309 one.startscript(Systemcall::DontWait, command);
4313 case LFUN_SPELLING_CONTINUOUSLY:
4314 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4315 dr.screenUpdate(Update::Force);
4319 // The LFUN must be for one of BufferView, Buffer or Cursor;
4321 dispatchToBufferView(cmd, dr);
4325 // Part of automatic menu appearance feature.
4326 if (isFullScreen()) {
4327 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4331 // Need to update bv because many LFUNs here might have destroyed it
4332 bv = currentBufferView();
4334 // Clear non-empty selections
4335 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4337 Cursor & cur = bv->cursor();
4338 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4339 cur.clearSelection();
4345 bool GuiView::lfunUiToggle(string const & ui_component)
4347 if (ui_component == "scrollbar") {
4348 // hide() is of no help
4349 if (d.current_work_area_->verticalScrollBarPolicy() ==
4350 Qt::ScrollBarAlwaysOff)
4352 d.current_work_area_->setVerticalScrollBarPolicy(
4353 Qt::ScrollBarAsNeeded);
4355 d.current_work_area_->setVerticalScrollBarPolicy(
4356 Qt::ScrollBarAlwaysOff);
4357 } else if (ui_component == "statusbar") {
4358 statusBar()->setVisible(!statusBar()->isVisible());
4359 } else if (ui_component == "menubar") {
4360 menuBar()->setVisible(!menuBar()->isVisible());
4362 if (ui_component == "frame") {
4364 getContentsMargins(&l, &t, &r, &b);
4365 //are the frames in default state?
4366 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4368 setContentsMargins(-2, -2, -2, -2);
4370 setContentsMargins(0, 0, 0, 0);
4373 if (ui_component == "fullscreen") {
4381 void GuiView::toggleFullScreen()
4383 if (isFullScreen()) {
4384 for (int i = 0; i != d.splitter_->count(); ++i)
4385 d.tabWorkArea(i)->setFullScreen(false);
4386 setContentsMargins(0, 0, 0, 0);
4387 setWindowState(windowState() ^ Qt::WindowFullScreen);
4390 statusBar()->show();
4393 hideDialogs("prefs", 0);
4394 for (int i = 0; i != d.splitter_->count(); ++i)
4395 d.tabWorkArea(i)->setFullScreen(true);
4396 setContentsMargins(-2, -2, -2, -2);
4398 setWindowState(windowState() ^ Qt::WindowFullScreen);
4399 if (lyxrc.full_screen_statusbar)
4400 statusBar()->hide();
4401 if (lyxrc.full_screen_menubar)
4403 if (lyxrc.full_screen_toolbars) {
4404 ToolbarMap::iterator end = d.toolbars_.end();
4405 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4410 // give dialogs like the TOC a chance to adapt
4415 Buffer const * GuiView::updateInset(Inset const * inset)
4420 Buffer const * inset_buffer = &(inset->buffer());
4422 for (int i = 0; i != d.splitter_->count(); ++i) {
4423 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4426 Buffer const * buffer = &(wa->bufferView().buffer());
4427 if (inset_buffer == buffer)
4428 wa->scheduleRedraw(true);
4430 return inset_buffer;
4434 void GuiView::restartCaret()
4436 /* When we move around, or type, it's nice to be able to see
4437 * the caret immediately after the keypress.
4439 if (d.current_work_area_)
4440 d.current_work_area_->startBlinkingCaret();
4442 // Take this occasion to update the other GUI elements.
4448 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4450 if (d.current_work_area_)
4451 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4456 // This list should be kept in sync with the list of insets in
4457 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4458 // dialog should have the same name as the inset.
4459 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4460 // docs in LyXAction.cpp.
4462 char const * const dialognames[] = {
4464 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4465 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4466 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4467 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4468 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4469 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4470 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4471 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4473 char const * const * const end_dialognames =
4474 dialognames + (sizeof(dialognames) / sizeof(char *));
4478 cmpCStr(char const * name) : name_(name) {}
4479 bool operator()(char const * other) {
4480 return strcmp(other, name_) == 0;
4487 bool isValidName(string const & name)
4489 return find_if(dialognames, end_dialognames,
4490 cmpCStr(name.c_str())) != end_dialognames;
4496 void GuiView::resetDialogs()
4498 // Make sure that no LFUN uses any GuiView.
4499 guiApp->setCurrentView(0);
4503 constructToolbars();
4504 guiApp->menus().fillMenuBar(menuBar(), this, false);
4505 d.layout_->updateContents(true);
4506 // Now update controls with current buffer.
4507 guiApp->setCurrentView(this);
4513 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4515 if (!isValidName(name))
4518 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4520 if (it != d.dialogs_.end()) {
4522 it->second->hideView();
4523 return it->second.get();
4526 Dialog * dialog = build(name);
4527 d.dialogs_[name].reset(dialog);
4528 if (lyxrc.allow_geometry_session)
4529 dialog->restoreSession();
4536 void GuiView::showDialog(string const & name, string const & data,
4539 triggerShowDialog(toqstr(name), toqstr(data), inset);
4543 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4549 const string name = fromqstr(qname);
4550 const string data = fromqstr(qdata);
4554 Dialog * dialog = findOrBuild(name, false);
4556 bool const visible = dialog->isVisibleView();
4557 dialog->showData(data);
4558 if (inset && currentBufferView())
4559 currentBufferView()->editInset(name, inset);
4560 // We only set the focus to the new dialog if it was not yet
4561 // visible in order not to change the existing previous behaviour
4563 // activateWindow is needed for floating dockviews
4564 dialog->asQWidget()->raise();
4565 dialog->asQWidget()->activateWindow();
4566 dialog->asQWidget()->setFocus();
4570 catch (ExceptionMessage const & ex) {
4578 bool GuiView::isDialogVisible(string const & name) const
4580 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4581 if (it == d.dialogs_.end())
4583 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4587 void GuiView::hideDialog(string const & name, Inset * inset)
4589 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4590 if (it == d.dialogs_.end())
4594 if (!currentBufferView())
4596 if (inset != currentBufferView()->editedInset(name))
4600 Dialog * const dialog = it->second.get();
4601 if (dialog->isVisibleView())
4603 if (currentBufferView())
4604 currentBufferView()->editInset(name, 0);
4608 void GuiView::disconnectDialog(string const & name)
4610 if (!isValidName(name))
4612 if (currentBufferView())
4613 currentBufferView()->editInset(name, 0);
4617 void GuiView::hideAll() const
4619 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4620 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4622 for(; it != end; ++it)
4623 it->second->hideView();
4627 void GuiView::updateDialogs()
4629 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4630 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4632 for(; it != end; ++it) {
4633 Dialog * dialog = it->second.get();
4635 if (dialog->needBufferOpen() && !documentBufferView())
4636 hideDialog(fromqstr(dialog->name()), 0);
4637 else if (dialog->isVisibleView())
4638 dialog->checkStatus();
4645 Dialog * createDialog(GuiView & lv, string const & name);
4647 // will be replaced by a proper factory...
4648 Dialog * createGuiAbout(GuiView & lv);
4649 Dialog * createGuiBibtex(GuiView & lv);
4650 Dialog * createGuiChanges(GuiView & lv);
4651 Dialog * createGuiCharacter(GuiView & lv);
4652 Dialog * createGuiCitation(GuiView & lv);
4653 Dialog * createGuiCompare(GuiView & lv);
4654 Dialog * createGuiCompareHistory(GuiView & lv);
4655 Dialog * createGuiDelimiter(GuiView & lv);
4656 Dialog * createGuiDocument(GuiView & lv);
4657 Dialog * createGuiErrorList(GuiView & lv);
4658 Dialog * createGuiExternal(GuiView & lv);
4659 Dialog * createGuiGraphics(GuiView & lv);
4660 Dialog * createGuiInclude(GuiView & lv);
4661 Dialog * createGuiIndex(GuiView & lv);
4662 Dialog * createGuiListings(GuiView & lv);
4663 Dialog * createGuiLog(GuiView & lv);
4664 Dialog * createGuiMathMatrix(GuiView & lv);
4665 Dialog * createGuiNote(GuiView & lv);
4666 Dialog * createGuiParagraph(GuiView & lv);
4667 Dialog * createGuiPhantom(GuiView & lv);
4668 Dialog * createGuiPreferences(GuiView & lv);
4669 Dialog * createGuiPrint(GuiView & lv);
4670 Dialog * createGuiPrintindex(GuiView & lv);
4671 Dialog * createGuiRef(GuiView & lv);
4672 Dialog * createGuiSearch(GuiView & lv);
4673 Dialog * createGuiSearchAdv(GuiView & lv);
4674 Dialog * createGuiSendTo(GuiView & lv);
4675 Dialog * createGuiShowFile(GuiView & lv);
4676 Dialog * createGuiSpellchecker(GuiView & lv);
4677 Dialog * createGuiSymbols(GuiView & lv);
4678 Dialog * createGuiTabularCreate(GuiView & lv);
4679 Dialog * createGuiTexInfo(GuiView & lv);
4680 Dialog * createGuiToc(GuiView & lv);
4681 Dialog * createGuiThesaurus(GuiView & lv);
4682 Dialog * createGuiViewSource(GuiView & lv);
4683 Dialog * createGuiWrap(GuiView & lv);
4684 Dialog * createGuiProgressView(GuiView & lv);
4688 Dialog * GuiView::build(string const & name)
4690 LASSERT(isValidName(name), return 0);
4692 Dialog * dialog = createDialog(*this, name);
4696 if (name == "aboutlyx")
4697 return createGuiAbout(*this);
4698 if (name == "bibtex")
4699 return createGuiBibtex(*this);
4700 if (name == "changes")
4701 return createGuiChanges(*this);
4702 if (name == "character")
4703 return createGuiCharacter(*this);
4704 if (name == "citation")
4705 return createGuiCitation(*this);
4706 if (name == "compare")
4707 return createGuiCompare(*this);
4708 if (name == "comparehistory")
4709 return createGuiCompareHistory(*this);
4710 if (name == "document")
4711 return createGuiDocument(*this);
4712 if (name == "errorlist")
4713 return createGuiErrorList(*this);
4714 if (name == "external")
4715 return createGuiExternal(*this);
4717 return createGuiShowFile(*this);
4718 if (name == "findreplace")
4719 return createGuiSearch(*this);
4720 if (name == "findreplaceadv")
4721 return createGuiSearchAdv(*this);
4722 if (name == "graphics")
4723 return createGuiGraphics(*this);
4724 if (name == "include")
4725 return createGuiInclude(*this);
4726 if (name == "index")
4727 return createGuiIndex(*this);
4728 if (name == "index_print")
4729 return createGuiPrintindex(*this);
4730 if (name == "listings")
4731 return createGuiListings(*this);
4733 return createGuiLog(*this);
4734 if (name == "mathdelimiter")
4735 return createGuiDelimiter(*this);
4736 if (name == "mathmatrix")
4737 return createGuiMathMatrix(*this);
4739 return createGuiNote(*this);
4740 if (name == "paragraph")
4741 return createGuiParagraph(*this);
4742 if (name == "phantom")
4743 return createGuiPhantom(*this);
4744 if (name == "prefs")
4745 return createGuiPreferences(*this);
4747 return createGuiRef(*this);
4748 if (name == "sendto")
4749 return createGuiSendTo(*this);
4750 if (name == "spellchecker")
4751 return createGuiSpellchecker(*this);
4752 if (name == "symbols")
4753 return createGuiSymbols(*this);
4754 if (name == "tabularcreate")
4755 return createGuiTabularCreate(*this);
4756 if (name == "texinfo")
4757 return createGuiTexInfo(*this);
4758 if (name == "thesaurus")
4759 return createGuiThesaurus(*this);
4761 return createGuiToc(*this);
4762 if (name == "view-source")
4763 return createGuiViewSource(*this);
4765 return createGuiWrap(*this);
4766 if (name == "progress")
4767 return createGuiProgressView(*this);
4773 SEMenu::SEMenu(QWidget * parent)
4775 QAction * action = addAction(qt_("Disable Shell Escape"));
4776 connect(action, SIGNAL(triggered()),
4777 parent, SLOT(disableShellEscape()));
4781 } // namespace frontend
4784 #include "moc_GuiView.cpp"