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->cloneFromMaster();
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
3620 // this will be run unconditionally in case EXPORT_in_THREAD
3622 Buffer::ExportStatus status;
3624 status = (used_buffer->*syncFunc)(format, true);
3625 } else if (previewFunc) {
3626 status = (used_buffer->*previewFunc)(format);
3629 handleExportStatus(gv_, status, format);
3631 return (status == Buffer::ExportSuccess
3632 || status == Buffer::PreviewSuccess);
3633 #if EXPORT_in_THREAD
3634 // the end of the else clause in that case.
3639 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3641 BufferView * bv = currentBufferView();
3642 LASSERT(bv, return);
3644 // Let the current BufferView dispatch its own actions.
3645 bv->dispatch(cmd, dr);
3646 if (dr.dispatched())
3649 // Try with the document BufferView dispatch if any.
3650 BufferView * doc_bv = documentBufferView();
3651 if (doc_bv && doc_bv != bv) {
3652 doc_bv->dispatch(cmd, dr);
3653 if (dr.dispatched())
3657 // Then let the current Cursor dispatch its own actions.
3658 bv->cursor().dispatch(cmd);
3660 // update completion. We do it here and not in
3661 // processKeySym to avoid another redraw just for a
3662 // changed inline completion
3663 if (cmd.origin() == FuncRequest::KEYBOARD) {
3664 if (cmd.action() == LFUN_SELF_INSERT
3665 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3666 updateCompletion(bv->cursor(), true, true);
3667 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3668 updateCompletion(bv->cursor(), false, true);
3670 updateCompletion(bv->cursor(), false, false);
3673 dr = bv->cursor().result();
3677 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3679 BufferView * bv = currentBufferView();
3680 // By default we won't need any update.
3681 dr.screenUpdate(Update::None);
3682 // assume cmd will be dispatched
3683 dr.dispatched(true);
3685 Buffer * doc_buffer = documentBufferView()
3686 ? &(documentBufferView()->buffer()) : 0;
3688 if (cmd.origin() == FuncRequest::TOC) {
3689 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3690 // FIXME: do we need to pass a DispatchResult object here?
3691 toc->doDispatch(bv->cursor(), cmd);
3695 string const argument = to_utf8(cmd.argument());
3697 switch(cmd.action()) {
3698 case LFUN_BUFFER_CHILD_OPEN:
3699 openChildDocument(to_utf8(cmd.argument()));
3702 case LFUN_BUFFER_IMPORT:
3703 importDocument(to_utf8(cmd.argument()));
3706 case LFUN_MASTER_BUFFER_EXPORT:
3708 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3710 case LFUN_BUFFER_EXPORT: {
3713 // GCC only sees strfwd.h when building merged
3714 if (::lyx::operator==(cmd.argument(), "custom")) {
3715 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3716 // so the following test should not be needed.
3717 // In principle, we could try to switch to such a view...
3718 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3719 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3723 string const dest = cmd.getArg(1);
3724 FileName target_dir;
3725 if (!dest.empty() && FileName::isAbsolute(dest))
3726 target_dir = FileName(support::onlyPath(dest));
3728 target_dir = doc_buffer->fileName().onlyPath();
3730 string const format = (argument.empty() || argument == "default") ?
3731 doc_buffer->params().getDefaultOutputFormat() : argument;
3733 if ((dest.empty() && doc_buffer->isUnnamed())
3734 || !target_dir.isDirWritable()) {
3735 exportBufferAs(*doc_buffer, from_utf8(format));
3738 /* TODO/Review: Is it a problem to also export the children?
3739 See the update_unincluded flag */
3740 d.asyncBufferProcessing(format,
3743 &GuiViewPrivate::exportAndDestroy,
3745 0, cmd.allowAsync());
3746 // TODO Inform user about success
3750 case LFUN_BUFFER_EXPORT_AS: {
3751 LASSERT(doc_buffer, break);
3752 docstring f = cmd.argument();
3754 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3755 exportBufferAs(*doc_buffer, f);
3759 case LFUN_BUFFER_UPDATE: {
3760 d.asyncBufferProcessing(argument,
3763 &GuiViewPrivate::compileAndDestroy,
3765 0, cmd.allowAsync());
3768 case LFUN_BUFFER_VIEW: {
3769 d.asyncBufferProcessing(argument,
3771 _("Previewing ..."),
3772 &GuiViewPrivate::previewAndDestroy,
3774 &Buffer::preview, cmd.allowAsync());
3777 case LFUN_MASTER_BUFFER_UPDATE: {
3778 d.asyncBufferProcessing(argument,
3779 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3781 &GuiViewPrivate::compileAndDestroy,
3783 0, cmd.allowAsync());
3786 case LFUN_MASTER_BUFFER_VIEW: {
3787 d.asyncBufferProcessing(argument,
3788 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3790 &GuiViewPrivate::previewAndDestroy,
3791 0, &Buffer::preview, cmd.allowAsync());
3794 case LFUN_BUFFER_SWITCH: {
3795 string const file_name = to_utf8(cmd.argument());
3796 if (!FileName::isAbsolute(file_name)) {
3798 dr.setMessage(_("Absolute filename expected."));
3802 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3805 dr.setMessage(_("Document not loaded"));
3809 // Do we open or switch to the buffer in this view ?
3810 if (workArea(*buffer)
3811 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3816 // Look for the buffer in other views
3817 QList<int> const ids = guiApp->viewIds();
3819 for (; i != ids.size(); ++i) {
3820 GuiView & gv = guiApp->view(ids[i]);
3821 if (gv.workArea(*buffer)) {
3823 gv.activateWindow();
3825 gv.setBuffer(buffer);
3830 // If necessary, open a new window as a last resort
3831 if (i == ids.size()) {
3832 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3838 case LFUN_BUFFER_NEXT:
3839 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3842 case LFUN_BUFFER_MOVE_NEXT:
3843 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3846 case LFUN_BUFFER_PREVIOUS:
3847 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3850 case LFUN_BUFFER_MOVE_PREVIOUS:
3851 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3854 case LFUN_BUFFER_CHKTEX:
3855 LASSERT(doc_buffer, break);
3856 doc_buffer->runChktex();
3859 case LFUN_COMMAND_EXECUTE: {
3860 command_execute_ = true;
3861 minibuffer_focus_ = true;
3864 case LFUN_DROP_LAYOUTS_CHOICE:
3865 d.layout_->showPopup();
3868 case LFUN_MENU_OPEN:
3869 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3870 menu->exec(QCursor::pos());
3873 case LFUN_FILE_INSERT:
3874 insertLyXFile(cmd.argument());
3877 case LFUN_FILE_INSERT_PLAINTEXT:
3878 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3879 string const fname = to_utf8(cmd.argument());
3880 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3881 dr.setMessage(_("Absolute filename expected."));
3885 FileName filename(fname);
3886 if (fname.empty()) {
3887 FileDialog dlg(qt_("Select file to insert"));
3889 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3890 QStringList(qt_("All Files (*)")));
3892 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3893 dr.setMessage(_("Canceled."));
3897 filename.set(fromqstr(result.second));
3901 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3902 bv->dispatch(new_cmd, dr);
3907 case LFUN_BUFFER_RELOAD: {
3908 LASSERT(doc_buffer, break);
3911 if (!doc_buffer->isClean()) {
3912 docstring const file =
3913 makeDisplayPath(doc_buffer->absFileName(), 20);
3914 if (doc_buffer->notifiesExternalModification()) {
3915 docstring text = _("The current version will be lost. "
3916 "Are you sure you want to load the version on disk "
3917 "of the document %1$s?");
3918 ret = Alert::prompt(_("Reload saved document?"),
3919 bformat(text, file), 1, 1,
3920 _("&Reload"), _("&Cancel"));
3922 docstring text = _("Any changes will be lost. "
3923 "Are you sure you want to revert to the saved version "
3924 "of the document %1$s?");
3925 ret = Alert::prompt(_("Revert to saved document?"),
3926 bformat(text, file), 1, 1,
3927 _("&Revert"), _("&Cancel"));
3932 doc_buffer->markClean();
3933 reloadBuffer(*doc_buffer);
3934 dr.forceBufferUpdate();
3939 case LFUN_BUFFER_WRITE:
3940 LASSERT(doc_buffer, break);
3941 saveBuffer(*doc_buffer);
3944 case LFUN_BUFFER_WRITE_AS:
3945 LASSERT(doc_buffer, break);
3946 renameBuffer(*doc_buffer, cmd.argument());
3949 case LFUN_BUFFER_WRITE_ALL: {
3950 Buffer * first = theBufferList().first();
3953 message(_("Saving all documents..."));
3954 // We cannot use a for loop as the buffer list cycles.
3957 if (!b->isClean()) {
3959 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3961 b = theBufferList().next(b);
3962 } while (b != first);
3963 dr.setMessage(_("All documents saved."));
3967 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3968 LASSERT(doc_buffer, break);
3969 doc_buffer->clearExternalModification();
3972 case LFUN_BUFFER_CLOSE:
3976 case LFUN_BUFFER_CLOSE_ALL:
3980 case LFUN_DEVEL_MODE_TOGGLE:
3981 devel_mode_ = !devel_mode_;
3983 dr.setMessage(_("Developer mode is now enabled."));
3985 dr.setMessage(_("Developer mode is now disabled."));
3988 case LFUN_TOOLBAR_TOGGLE: {
3989 string const name = cmd.getArg(0);
3990 if (GuiToolbar * t = toolbar(name))
3995 case LFUN_TOOLBAR_MOVABLE: {
3996 string const name = cmd.getArg(0);
3998 // toggle (all) toolbars movablility
3999 toolbarsMovable_ = !toolbarsMovable_;
4000 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4001 GuiToolbar * tb = toolbar(ti.name);
4002 if (tb && tb->isMovable() != toolbarsMovable_)
4003 // toggle toolbar movablity if it does not fit lock
4004 // (all) toolbars positions state silent = true, since
4005 // status bar notifications are slow
4008 if (toolbarsMovable_)
4009 dr.setMessage(_("Toolbars unlocked."));
4011 dr.setMessage(_("Toolbars locked."));
4012 } else if (GuiToolbar * t = toolbar(name)) {
4013 // toggle current toolbar movablity
4015 // update lock (all) toolbars positions
4016 updateLockToolbars();
4021 case LFUN_ICON_SIZE: {
4022 QSize size = d.iconSize(cmd.argument());
4024 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4025 size.width(), size.height()));
4029 case LFUN_DIALOG_UPDATE: {
4030 string const name = to_utf8(cmd.argument());
4031 if (name == "prefs" || name == "document")
4032 updateDialog(name, string());
4033 else if (name == "paragraph")
4034 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4035 else if (currentBufferView()) {
4036 Inset * inset = currentBufferView()->editedInset(name);
4037 // Can only update a dialog connected to an existing inset
4039 // FIXME: get rid of this indirection; GuiView ask the inset
4040 // if he is kind enough to update itself...
4041 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4042 //FIXME: pass DispatchResult here?
4043 inset->dispatch(currentBufferView()->cursor(), fr);
4049 case LFUN_DIALOG_TOGGLE: {
4050 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4051 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4052 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4056 case LFUN_DIALOG_DISCONNECT_INSET:
4057 disconnectDialog(to_utf8(cmd.argument()));
4060 case LFUN_DIALOG_HIDE: {
4061 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4065 case LFUN_DIALOG_SHOW: {
4066 string const name = cmd.getArg(0);
4067 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
4069 if (name == "character") {
4070 data = freefont2string();
4072 showDialog("character", data);
4073 } else if (name == "latexlog") {
4074 // getStatus checks that
4075 LATTEST(doc_buffer);
4076 Buffer::LogType type;
4077 string const logfile = doc_buffer->logName(&type);
4079 case Buffer::latexlog:
4082 case Buffer::buildlog:
4086 data += Lexer::quoteString(logfile);
4087 showDialog("log", data);
4088 } else if (name == "vclog") {
4089 // getStatus checks that
4090 LATTEST(doc_buffer);
4091 string const data = "vc " +
4092 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4093 showDialog("log", data);
4094 } else if (name == "symbols") {
4095 data = bv->cursor().getEncoding()->name();
4097 showDialog("symbols", data);
4099 } else if (name == "prefs" && isFullScreen()) {
4100 lfunUiToggle("fullscreen");
4101 showDialog("prefs", data);
4103 showDialog(name, data);
4108 dr.setMessage(cmd.argument());
4111 case LFUN_UI_TOGGLE: {
4112 string arg = cmd.getArg(0);
4113 if (!lfunUiToggle(arg)) {
4114 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4115 dr.setMessage(bformat(msg, from_utf8(arg)));
4117 // Make sure the keyboard focus stays in the work area.
4122 case LFUN_VIEW_SPLIT: {
4123 LASSERT(doc_buffer, break);
4124 string const orientation = cmd.getArg(0);
4125 d.splitter_->setOrientation(orientation == "vertical"
4126 ? Qt::Vertical : Qt::Horizontal);
4127 TabWorkArea * twa = addTabWorkArea();
4128 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4129 setCurrentWorkArea(wa);
4132 case LFUN_TAB_GROUP_CLOSE:
4133 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4134 closeTabWorkArea(twa);
4135 d.current_work_area_ = 0;
4136 twa = d.currentTabWorkArea();
4137 // Switch to the next GuiWorkArea in the found TabWorkArea.
4139 // Make sure the work area is up to date.
4140 setCurrentWorkArea(twa->currentWorkArea());
4142 setCurrentWorkArea(0);
4147 case LFUN_VIEW_CLOSE:
4148 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4149 closeWorkArea(twa->currentWorkArea());
4150 d.current_work_area_ = 0;
4151 twa = d.currentTabWorkArea();
4152 // Switch to the next GuiWorkArea in the found TabWorkArea.
4154 // Make sure the work area is up to date.
4155 setCurrentWorkArea(twa->currentWorkArea());
4157 setCurrentWorkArea(0);
4162 case LFUN_COMPLETION_INLINE:
4163 if (d.current_work_area_)
4164 d.current_work_area_->completer().showInline();
4167 case LFUN_COMPLETION_POPUP:
4168 if (d.current_work_area_)
4169 d.current_work_area_->completer().showPopup();
4174 if (d.current_work_area_)
4175 d.current_work_area_->completer().tab();
4178 case LFUN_COMPLETION_CANCEL:
4179 if (d.current_work_area_) {
4180 if (d.current_work_area_->completer().popupVisible())
4181 d.current_work_area_->completer().hidePopup();
4183 d.current_work_area_->completer().hideInline();
4187 case LFUN_COMPLETION_ACCEPT:
4188 if (d.current_work_area_)
4189 d.current_work_area_->completer().activate();
4192 case LFUN_BUFFER_ZOOM_IN:
4193 case LFUN_BUFFER_ZOOM_OUT:
4194 case LFUN_BUFFER_ZOOM: {
4195 if (cmd.argument().empty()) {
4196 if (cmd.action() == LFUN_BUFFER_ZOOM)
4198 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4203 if (cmd.action() == LFUN_BUFFER_ZOOM)
4204 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4205 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4206 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4208 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4211 // Actual zoom value: default zoom + fractional extra value
4212 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4213 if (zoom < static_cast<int>(zoom_min_))
4216 lyxrc.currentZoom = zoom;
4218 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4219 lyxrc.currentZoom, lyxrc.defaultZoom));
4221 // The global QPixmapCache is used in GuiPainter to cache text
4222 // painting so we must reset it.
4223 QPixmapCache::clear();
4224 guiApp->fontLoader().update();
4225 dr.screenUpdate(Update::Force | Update::FitCursor);
4229 case LFUN_VC_REGISTER:
4230 case LFUN_VC_RENAME:
4232 case LFUN_VC_CHECK_IN:
4233 case LFUN_VC_CHECK_OUT:
4234 case LFUN_VC_REPO_UPDATE:
4235 case LFUN_VC_LOCKING_TOGGLE:
4236 case LFUN_VC_REVERT:
4237 case LFUN_VC_UNDO_LAST:
4238 case LFUN_VC_COMMAND:
4239 case LFUN_VC_COMPARE:
4240 dispatchVC(cmd, dr);
4243 case LFUN_SERVER_GOTO_FILE_ROW:
4244 if(goToFileRow(to_utf8(cmd.argument())))
4245 dr.screenUpdate(Update::Force | Update::FitCursor);
4248 case LFUN_LYX_ACTIVATE:
4252 case LFUN_FORWARD_SEARCH: {
4253 // it seems safe to assume we have a document buffer, since
4254 // getStatus wants one.
4255 LATTEST(doc_buffer);
4256 Buffer const * doc_master = doc_buffer->masterBuffer();
4257 FileName const path(doc_master->temppath());
4258 string const texname = doc_master->isChild(doc_buffer)
4259 ? DocFileName(changeExtension(
4260 doc_buffer->absFileName(),
4261 "tex")).mangledFileName()
4262 : doc_buffer->latexName();
4263 string const fulltexname =
4264 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4265 string const mastername =
4266 removeExtension(doc_master->latexName());
4267 FileName const dviname(addName(path.absFileName(),
4268 addExtension(mastername, "dvi")));
4269 FileName const pdfname(addName(path.absFileName(),
4270 addExtension(mastername, "pdf")));
4271 bool const have_dvi = dviname.exists();
4272 bool const have_pdf = pdfname.exists();
4273 if (!have_dvi && !have_pdf) {
4274 dr.setMessage(_("Please, preview the document first."));
4277 string outname = dviname.onlyFileName();
4278 string command = lyxrc.forward_search_dvi;
4279 if (!have_dvi || (have_pdf &&
4280 pdfname.lastModified() > dviname.lastModified())) {
4281 outname = pdfname.onlyFileName();
4282 command = lyxrc.forward_search_pdf;
4285 DocIterator cur = bv->cursor();
4286 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4287 LYXERR(Debug::ACTION, "Forward search: row:" << row
4289 if (row == -1 || command.empty()) {
4290 dr.setMessage(_("Couldn't proceed."));
4293 string texrow = convert<string>(row);
4295 command = subst(command, "$$n", texrow);
4296 command = subst(command, "$$f", fulltexname);
4297 command = subst(command, "$$t", texname);
4298 command = subst(command, "$$o", outname);
4300 PathChanger p(path);
4302 one.startscript(Systemcall::DontWait, command);
4306 case LFUN_SPELLING_CONTINUOUSLY:
4307 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4308 dr.screenUpdate(Update::Force);
4312 // The LFUN must be for one of BufferView, Buffer or Cursor;
4314 dispatchToBufferView(cmd, dr);
4318 // Part of automatic menu appearance feature.
4319 if (isFullScreen()) {
4320 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4324 // Need to update bv because many LFUNs here might have destroyed it
4325 bv = currentBufferView();
4327 // Clear non-empty selections
4328 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4330 Cursor & cur = bv->cursor();
4331 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4332 cur.clearSelection();
4338 bool GuiView::lfunUiToggle(string const & ui_component)
4340 if (ui_component == "scrollbar") {
4341 // hide() is of no help
4342 if (d.current_work_area_->verticalScrollBarPolicy() ==
4343 Qt::ScrollBarAlwaysOff)
4345 d.current_work_area_->setVerticalScrollBarPolicy(
4346 Qt::ScrollBarAsNeeded);
4348 d.current_work_area_->setVerticalScrollBarPolicy(
4349 Qt::ScrollBarAlwaysOff);
4350 } else if (ui_component == "statusbar") {
4351 statusBar()->setVisible(!statusBar()->isVisible());
4352 } else if (ui_component == "menubar") {
4353 menuBar()->setVisible(!menuBar()->isVisible());
4355 if (ui_component == "frame") {
4357 getContentsMargins(&l, &t, &r, &b);
4358 //are the frames in default state?
4359 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4361 setContentsMargins(-2, -2, -2, -2);
4363 setContentsMargins(0, 0, 0, 0);
4366 if (ui_component == "fullscreen") {
4374 void GuiView::toggleFullScreen()
4376 if (isFullScreen()) {
4377 for (int i = 0; i != d.splitter_->count(); ++i)
4378 d.tabWorkArea(i)->setFullScreen(false);
4379 setContentsMargins(0, 0, 0, 0);
4380 setWindowState(windowState() ^ Qt::WindowFullScreen);
4383 statusBar()->show();
4386 hideDialogs("prefs", 0);
4387 for (int i = 0; i != d.splitter_->count(); ++i)
4388 d.tabWorkArea(i)->setFullScreen(true);
4389 setContentsMargins(-2, -2, -2, -2);
4391 setWindowState(windowState() ^ Qt::WindowFullScreen);
4392 if (lyxrc.full_screen_statusbar)
4393 statusBar()->hide();
4394 if (lyxrc.full_screen_menubar)
4396 if (lyxrc.full_screen_toolbars) {
4397 ToolbarMap::iterator end = d.toolbars_.end();
4398 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4403 // give dialogs like the TOC a chance to adapt
4408 Buffer const * GuiView::updateInset(Inset const * inset)
4413 Buffer const * inset_buffer = &(inset->buffer());
4415 for (int i = 0; i != d.splitter_->count(); ++i) {
4416 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4419 Buffer const * buffer = &(wa->bufferView().buffer());
4420 if (inset_buffer == buffer)
4421 wa->scheduleRedraw(true);
4423 return inset_buffer;
4427 void GuiView::restartCaret()
4429 /* When we move around, or type, it's nice to be able to see
4430 * the caret immediately after the keypress.
4432 if (d.current_work_area_)
4433 d.current_work_area_->startBlinkingCaret();
4435 // Take this occasion to update the other GUI elements.
4441 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4443 if (d.current_work_area_)
4444 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4449 // This list should be kept in sync with the list of insets in
4450 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4451 // dialog should have the same name as the inset.
4452 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4453 // docs in LyXAction.cpp.
4455 char const * const dialognames[] = {
4457 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4458 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4459 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4460 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4461 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4462 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4463 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4464 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4466 char const * const * const end_dialognames =
4467 dialognames + (sizeof(dialognames) / sizeof(char *));
4471 cmpCStr(char const * name) : name_(name) {}
4472 bool operator()(char const * other) {
4473 return strcmp(other, name_) == 0;
4480 bool isValidName(string const & name)
4482 return find_if(dialognames, end_dialognames,
4483 cmpCStr(name.c_str())) != end_dialognames;
4489 void GuiView::resetDialogs()
4491 // Make sure that no LFUN uses any GuiView.
4492 guiApp->setCurrentView(0);
4496 constructToolbars();
4497 guiApp->menus().fillMenuBar(menuBar(), this, false);
4498 d.layout_->updateContents(true);
4499 // Now update controls with current buffer.
4500 guiApp->setCurrentView(this);
4506 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4508 if (!isValidName(name))
4511 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4513 if (it != d.dialogs_.end()) {
4515 it->second->hideView();
4516 return it->second.get();
4519 Dialog * dialog = build(name);
4520 d.dialogs_[name].reset(dialog);
4521 if (lyxrc.allow_geometry_session)
4522 dialog->restoreSession();
4529 void GuiView::showDialog(string const & name, string const & data,
4532 triggerShowDialog(toqstr(name), toqstr(data), inset);
4536 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4542 const string name = fromqstr(qname);
4543 const string data = fromqstr(qdata);
4547 Dialog * dialog = findOrBuild(name, false);
4549 bool const visible = dialog->isVisibleView();
4550 dialog->showData(data);
4551 if (inset && currentBufferView())
4552 currentBufferView()->editInset(name, inset);
4553 // We only set the focus to the new dialog if it was not yet
4554 // visible in order not to change the existing previous behaviour
4556 // activateWindow is needed for floating dockviews
4557 dialog->asQWidget()->raise();
4558 dialog->asQWidget()->activateWindow();
4559 dialog->asQWidget()->setFocus();
4563 catch (ExceptionMessage const & ex) {
4571 bool GuiView::isDialogVisible(string const & name) const
4573 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4574 if (it == d.dialogs_.end())
4576 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4580 void GuiView::hideDialog(string const & name, Inset * inset)
4582 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4583 if (it == d.dialogs_.end())
4587 if (!currentBufferView())
4589 if (inset != currentBufferView()->editedInset(name))
4593 Dialog * const dialog = it->second.get();
4594 if (dialog->isVisibleView())
4596 if (currentBufferView())
4597 currentBufferView()->editInset(name, 0);
4601 void GuiView::disconnectDialog(string const & name)
4603 if (!isValidName(name))
4605 if (currentBufferView())
4606 currentBufferView()->editInset(name, 0);
4610 void GuiView::hideAll() const
4612 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4613 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4615 for(; it != end; ++it)
4616 it->second->hideView();
4620 void GuiView::updateDialogs()
4622 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4623 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4625 for(; it != end; ++it) {
4626 Dialog * dialog = it->second.get();
4628 if (dialog->needBufferOpen() && !documentBufferView())
4629 hideDialog(fromqstr(dialog->name()), 0);
4630 else if (dialog->isVisibleView())
4631 dialog->checkStatus();
4638 Dialog * createDialog(GuiView & lv, string const & name);
4640 // will be replaced by a proper factory...
4641 Dialog * createGuiAbout(GuiView & lv);
4642 Dialog * createGuiBibtex(GuiView & lv);
4643 Dialog * createGuiChanges(GuiView & lv);
4644 Dialog * createGuiCharacter(GuiView & lv);
4645 Dialog * createGuiCitation(GuiView & lv);
4646 Dialog * createGuiCompare(GuiView & lv);
4647 Dialog * createGuiCompareHistory(GuiView & lv);
4648 Dialog * createGuiDelimiter(GuiView & lv);
4649 Dialog * createGuiDocument(GuiView & lv);
4650 Dialog * createGuiErrorList(GuiView & lv);
4651 Dialog * createGuiExternal(GuiView & lv);
4652 Dialog * createGuiGraphics(GuiView & lv);
4653 Dialog * createGuiInclude(GuiView & lv);
4654 Dialog * createGuiIndex(GuiView & lv);
4655 Dialog * createGuiListings(GuiView & lv);
4656 Dialog * createGuiLog(GuiView & lv);
4657 Dialog * createGuiMathMatrix(GuiView & lv);
4658 Dialog * createGuiNote(GuiView & lv);
4659 Dialog * createGuiParagraph(GuiView & lv);
4660 Dialog * createGuiPhantom(GuiView & lv);
4661 Dialog * createGuiPreferences(GuiView & lv);
4662 Dialog * createGuiPrint(GuiView & lv);
4663 Dialog * createGuiPrintindex(GuiView & lv);
4664 Dialog * createGuiRef(GuiView & lv);
4665 Dialog * createGuiSearch(GuiView & lv);
4666 Dialog * createGuiSearchAdv(GuiView & lv);
4667 Dialog * createGuiSendTo(GuiView & lv);
4668 Dialog * createGuiShowFile(GuiView & lv);
4669 Dialog * createGuiSpellchecker(GuiView & lv);
4670 Dialog * createGuiSymbols(GuiView & lv);
4671 Dialog * createGuiTabularCreate(GuiView & lv);
4672 Dialog * createGuiTexInfo(GuiView & lv);
4673 Dialog * createGuiToc(GuiView & lv);
4674 Dialog * createGuiThesaurus(GuiView & lv);
4675 Dialog * createGuiViewSource(GuiView & lv);
4676 Dialog * createGuiWrap(GuiView & lv);
4677 Dialog * createGuiProgressView(GuiView & lv);
4681 Dialog * GuiView::build(string const & name)
4683 LASSERT(isValidName(name), return 0);
4685 Dialog * dialog = createDialog(*this, name);
4689 if (name == "aboutlyx")
4690 return createGuiAbout(*this);
4691 if (name == "bibtex")
4692 return createGuiBibtex(*this);
4693 if (name == "changes")
4694 return createGuiChanges(*this);
4695 if (name == "character")
4696 return createGuiCharacter(*this);
4697 if (name == "citation")
4698 return createGuiCitation(*this);
4699 if (name == "compare")
4700 return createGuiCompare(*this);
4701 if (name == "comparehistory")
4702 return createGuiCompareHistory(*this);
4703 if (name == "document")
4704 return createGuiDocument(*this);
4705 if (name == "errorlist")
4706 return createGuiErrorList(*this);
4707 if (name == "external")
4708 return createGuiExternal(*this);
4710 return createGuiShowFile(*this);
4711 if (name == "findreplace")
4712 return createGuiSearch(*this);
4713 if (name == "findreplaceadv")
4714 return createGuiSearchAdv(*this);
4715 if (name == "graphics")
4716 return createGuiGraphics(*this);
4717 if (name == "include")
4718 return createGuiInclude(*this);
4719 if (name == "index")
4720 return createGuiIndex(*this);
4721 if (name == "index_print")
4722 return createGuiPrintindex(*this);
4723 if (name == "listings")
4724 return createGuiListings(*this);
4726 return createGuiLog(*this);
4727 if (name == "mathdelimiter")
4728 return createGuiDelimiter(*this);
4729 if (name == "mathmatrix")
4730 return createGuiMathMatrix(*this);
4732 return createGuiNote(*this);
4733 if (name == "paragraph")
4734 return createGuiParagraph(*this);
4735 if (name == "phantom")
4736 return createGuiPhantom(*this);
4737 if (name == "prefs")
4738 return createGuiPreferences(*this);
4740 return createGuiRef(*this);
4741 if (name == "sendto")
4742 return createGuiSendTo(*this);
4743 if (name == "spellchecker")
4744 return createGuiSpellchecker(*this);
4745 if (name == "symbols")
4746 return createGuiSymbols(*this);
4747 if (name == "tabularcreate")
4748 return createGuiTabularCreate(*this);
4749 if (name == "texinfo")
4750 return createGuiTexInfo(*this);
4751 if (name == "thesaurus")
4752 return createGuiThesaurus(*this);
4754 return createGuiToc(*this);
4755 if (name == "view-source")
4756 return createGuiViewSource(*this);
4758 return createGuiWrap(*this);
4759 if (name == "progress")
4760 return createGuiProgressView(*this);
4766 SEMenu::SEMenu(QWidget * parent)
4768 QAction * action = addAction(qt_("Disable Shell Escape"));
4769 connect(action, SIGNAL(triggered()),
4770 parent, SLOT(disableShellEscape()));
4774 } // namespace frontend
4777 #include "moc_GuiView.cpp"