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), devel_mode_(false)
514 connect(this, SIGNAL(bufferViewChanged()),
515 this, SLOT(onBufferViewChanged()));
517 // GuiToolbars *must* be initialised before the menu bar.
518 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
521 // set ourself as the current view. This is needed for the menu bar
522 // filling, at least for the static special menu item on Mac. Otherwise
523 // they are greyed out.
524 guiApp->setCurrentView(this);
526 // Fill up the menu bar.
527 guiApp->menus().fillMenuBar(menuBar(), this, true);
529 setCentralWidget(d.stack_widget_);
531 // Start autosave timer
532 if (lyxrc.autosave) {
533 // The connection is closed when this is destroyed.
534 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
535 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
536 d.autosave_timeout_.start();
538 connect(&d.statusbar_timer_, SIGNAL(timeout()),
539 this, SLOT(clearMessage()));
541 // We don't want to keep the window in memory if it is closed.
542 setAttribute(Qt::WA_DeleteOnClose, true);
544 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
545 // QIcon::fromTheme was introduced in Qt 4.6
546 #if (QT_VERSION >= 0x040600)
547 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
548 // since the icon is provided in the application bundle. We use a themed
549 // version when available and use the bundled one as fallback.
550 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
552 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
558 // use tabbed dock area for multiple docks
559 // (such as "source" and "messages")
560 setDockOptions(QMainWindow::ForceTabbedDocks);
563 setAcceptDrops(true);
565 // add busy indicator to statusbar
566 QLabel * busylabel = new QLabel(statusBar());
567 statusBar()->addPermanentWidget(busylabel);
568 search_mode mode = theGuiApp()->imageSearchMode();
569 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
570 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
571 busylabel->setMovie(busyanim);
575 connect(&d.processing_thread_watcher_, SIGNAL(started()),
576 busylabel, SLOT(show()));
577 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
578 busylabel, SLOT(hide()));
580 QFontMetrics const fm(statusBar()->fontMetrics());
581 int const iconheight = max(int(d.normalIconSize), fm.height());
582 QSize const iconsize(iconheight, iconheight);
584 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
585 shell_escape_ = new QLabel(statusBar());
586 shell_escape_->setPixmap(shellescape);
587 shell_escape_->setScaledContents(true);
588 shell_escape_->setAlignment(Qt::AlignCenter);
589 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
590 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
591 "external commands for this document. "
592 "Right click to change."));
593 SEMenu * menu = new SEMenu(this);
594 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
595 menu, SLOT(showMenu(QPoint)));
596 shell_escape_->hide();
597 statusBar()->addPermanentWidget(shell_escape_);
599 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
600 read_only_ = new QLabel(statusBar());
601 read_only_->setPixmap(readonly);
602 read_only_->setScaledContents(true);
603 read_only_->setAlignment(Qt::AlignCenter);
605 statusBar()->addPermanentWidget(read_only_);
607 version_control_ = new QLabel(statusBar());
608 version_control_->setAlignment(Qt::AlignCenter);
609 version_control_->setFrameStyle(QFrame::StyledPanel);
610 version_control_->hide();
611 statusBar()->addPermanentWidget(version_control_);
613 statusBar()->setSizeGripEnabled(true);
616 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
617 SLOT(autoSaveThreadFinished()));
619 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
620 SLOT(processingThreadStarted()));
621 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
622 SLOT(processingThreadFinished()));
624 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
625 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
627 // set custom application bars context menu, e.g. tool bar and menu bar
628 setContextMenuPolicy(Qt::CustomContextMenu);
629 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
630 SLOT(toolBarPopup(const QPoint &)));
632 // Forbid too small unresizable window because it can happen
633 // with some window manager under X11.
634 setMinimumSize(300, 200);
636 if (lyxrc.allow_geometry_session) {
637 // Now take care of session management.
642 // no session handling, default to a sane size.
643 setGeometry(50, 50, 690, 510);
646 // clear session data if any.
648 settings.remove("views");
658 void GuiView::disableShellEscape()
660 BufferView * bv = documentBufferView();
663 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
664 bv->buffer().params().shell_escape = false;
665 bv->processUpdateFlags(Update::Force);
669 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
671 QVector<GuiWorkArea*> areas;
672 for (int i = 0; i < tabWorkAreaCount(); i++) {
673 TabWorkArea* ta = tabWorkArea(i);
674 for (int u = 0; u < ta->count(); u++) {
675 areas << ta->workArea(u);
681 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
682 string const & format)
684 docstring const fmt = theFormats().prettyName(format);
687 case Buffer::ExportSuccess:
688 msg = bformat(_("Successful export to format: %1$s"), fmt);
690 case Buffer::ExportCancel:
691 msg = _("Document export cancelled.");
693 case Buffer::ExportError:
694 case Buffer::ExportNoPathToFormat:
695 case Buffer::ExportTexPathHasSpaces:
696 case Buffer::ExportConverterError:
697 msg = bformat(_("Error while exporting format: %1$s"), fmt);
699 case Buffer::PreviewSuccess:
700 msg = bformat(_("Successful preview of format: %1$s"), fmt);
702 case Buffer::PreviewError:
703 msg = bformat(_("Error while previewing format: %1$s"), fmt);
710 void GuiView::processingThreadStarted()
715 void GuiView::processingThreadFinished()
717 QFutureWatcher<Buffer::ExportStatus> const * watcher =
718 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
720 Buffer::ExportStatus const status = watcher->result();
721 handleExportStatus(this, status, d.processing_format);
724 BufferView const * const bv = currentBufferView();
725 if (bv && !bv->buffer().errorList("Export").empty()) {
729 if (status != Buffer::ExportSuccess && status != Buffer::PreviewSuccess &&
730 status != Buffer::ExportCancel) {
731 errors(d.last_export_format);
736 void GuiView::autoSaveThreadFinished()
738 QFutureWatcher<docstring> const * watcher =
739 static_cast<QFutureWatcher<docstring> const *>(sender());
740 message(watcher->result());
745 void GuiView::saveLayout() const
748 settings.setValue("zoom_ratio", zoom_ratio_);
749 settings.setValue("devel_mode", devel_mode_);
750 settings.beginGroup("views");
751 settings.beginGroup(QString::number(id_));
752 #if defined(Q_WS_X11) || defined(QPA_XCB)
753 settings.setValue("pos", pos());
754 settings.setValue("size", size());
756 settings.setValue("geometry", saveGeometry());
758 settings.setValue("layout", saveState(0));
759 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
763 void GuiView::saveUISettings() const
767 // Save the toolbar private states
768 ToolbarMap::iterator end = d.toolbars_.end();
769 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
770 it->second->saveSession(settings);
771 // Now take care of all other dialogs
772 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
773 for (; it!= d.dialogs_.end(); ++it)
774 it->second->saveSession(settings);
778 bool GuiView::restoreLayout()
781 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
782 // Actual zoom value: default zoom + fractional offset
783 int zoom = lyxrc.defaultZoom * zoom_ratio_;
784 if (zoom < static_cast<int>(zoom_min_))
786 lyxrc.currentZoom = zoom;
787 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
788 settings.beginGroup("views");
789 settings.beginGroup(QString::number(id_));
790 QString const icon_key = "icon_size";
791 if (!settings.contains(icon_key))
794 //code below is skipped when when ~/.config/LyX is (re)created
795 setIconSize(d.iconSize(settings.value(icon_key).toString()));
797 #if defined(Q_WS_X11) || defined(QPA_XCB)
798 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
799 QSize size = settings.value("size", QSize(690, 510)).toSize();
803 // Work-around for bug #6034: the window ends up in an undetermined
804 // state when trying to restore a maximized window when it is
805 // already maximized.
806 if (!(windowState() & Qt::WindowMaximized))
807 if (!restoreGeometry(settings.value("geometry").toByteArray()))
808 setGeometry(50, 50, 690, 510);
810 // Make sure layout is correctly oriented.
811 setLayoutDirection(qApp->layoutDirection());
813 // Allow the toc and view-source dock widget to be restored if needed.
815 if ((dialog = findOrBuild("toc", true)))
816 // see bug 5082. At least setup title and enabled state.
817 // Visibility will be adjusted by restoreState below.
818 dialog->prepareView();
819 if ((dialog = findOrBuild("view-source", true)))
820 dialog->prepareView();
821 if ((dialog = findOrBuild("progress", true)))
822 dialog->prepareView();
824 if (!restoreState(settings.value("layout").toByteArray(), 0))
827 // init the toolbars that have not been restored
828 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
829 Toolbars::Infos::iterator end = guiApp->toolbars().end();
830 for (; cit != end; ++cit) {
831 GuiToolbar * tb = toolbar(cit->name);
832 if (tb && !tb->isRestored())
833 initToolbar(cit->name);
836 // update lock (all) toolbars positions
837 updateLockToolbars();
844 GuiToolbar * GuiView::toolbar(string const & name)
846 ToolbarMap::iterator it = d.toolbars_.find(name);
847 if (it != d.toolbars_.end())
850 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
855 void GuiView::updateLockToolbars()
857 toolbarsMovable_ = false;
858 for (ToolbarInfo const & info : guiApp->toolbars()) {
859 GuiToolbar * tb = toolbar(info.name);
860 if (tb && tb->isMovable())
861 toolbarsMovable_ = true;
866 void GuiView::constructToolbars()
868 ToolbarMap::iterator it = d.toolbars_.begin();
869 for (; it != d.toolbars_.end(); ++it)
873 // I don't like doing this here, but the standard toolbar
874 // destroys this object when it's destroyed itself (vfr)
875 d.layout_ = new LayoutBox(*this);
876 d.stack_widget_->addWidget(d.layout_);
877 d.layout_->move(0,0);
879 // extracts the toolbars from the backend
880 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
881 Toolbars::Infos::iterator end = guiApp->toolbars().end();
882 for (; cit != end; ++cit)
883 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
887 void GuiView::initToolbars()
889 // extracts the toolbars from the backend
890 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
891 Toolbars::Infos::iterator end = guiApp->toolbars().end();
892 for (; cit != end; ++cit)
893 initToolbar(cit->name);
897 void GuiView::initToolbar(string const & name)
899 GuiToolbar * tb = toolbar(name);
902 int const visibility = guiApp->toolbars().defaultVisibility(name);
903 bool newline = !(visibility & Toolbars::SAMEROW);
904 tb->setVisible(false);
905 tb->setVisibility(visibility);
907 if (visibility & Toolbars::TOP) {
909 addToolBarBreak(Qt::TopToolBarArea);
910 addToolBar(Qt::TopToolBarArea, tb);
913 if (visibility & Toolbars::BOTTOM) {
915 addToolBarBreak(Qt::BottomToolBarArea);
916 addToolBar(Qt::BottomToolBarArea, tb);
919 if (visibility & Toolbars::LEFT) {
921 addToolBarBreak(Qt::LeftToolBarArea);
922 addToolBar(Qt::LeftToolBarArea, tb);
925 if (visibility & Toolbars::RIGHT) {
927 addToolBarBreak(Qt::RightToolBarArea);
928 addToolBar(Qt::RightToolBarArea, tb);
931 if (visibility & Toolbars::ON)
932 tb->setVisible(true);
934 tb->setMovable(true);
938 TocModels & GuiView::tocModels()
940 return d.toc_models_;
944 void GuiView::setFocus()
946 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
947 QMainWindow::setFocus();
951 bool GuiView::hasFocus() const
953 if (currentWorkArea())
954 return currentWorkArea()->hasFocus();
955 if (currentMainWorkArea())
956 return currentMainWorkArea()->hasFocus();
957 return d.bg_widget_->hasFocus();
961 void GuiView::focusInEvent(QFocusEvent * e)
963 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
964 QMainWindow::focusInEvent(e);
965 // Make sure guiApp points to the correct view.
966 guiApp->setCurrentView(this);
967 if (currentWorkArea())
968 currentWorkArea()->setFocus();
969 else if (currentMainWorkArea())
970 currentMainWorkArea()->setFocus();
972 d.bg_widget_->setFocus();
976 void GuiView::showEvent(QShowEvent * e)
978 LYXERR(Debug::GUI, "Passed Geometry "
979 << size().height() << "x" << size().width()
980 << "+" << pos().x() << "+" << pos().y());
982 if (d.splitter_->count() == 0)
983 // No work area, switch to the background widget.
987 QMainWindow::showEvent(e);
991 bool GuiView::closeScheduled()
998 bool GuiView::prepareAllBuffersForLogout()
1000 Buffer * first = theBufferList().first();
1004 // First, iterate over all buffers and ask the users if unsaved
1005 // changes should be saved.
1006 // We cannot use a for loop as the buffer list cycles.
1009 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1011 b = theBufferList().next(b);
1012 } while (b != first);
1014 // Next, save session state
1015 // When a view/window was closed before without quitting LyX, there
1016 // are already entries in the lastOpened list.
1017 theSession().lastOpened().clear();
1024 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1025 ** is responsibility of the container (e.g., dialog)
1027 void GuiView::closeEvent(QCloseEvent * close_event)
1029 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1031 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1032 Alert::warning(_("Exit LyX"),
1033 _("LyX could not be closed because documents are being processed by LyX."));
1034 close_event->setAccepted(false);
1038 // If the user pressed the x (so we didn't call closeView
1039 // programmatically), we want to clear all existing entries.
1041 theSession().lastOpened().clear();
1046 // it can happen that this event arrives without selecting the view,
1047 // e.g. when clicking the close button on a background window.
1049 if (!closeWorkAreaAll()) {
1051 close_event->ignore();
1055 // Make sure that nothing will use this to be closed View.
1056 guiApp->unregisterView(this);
1058 if (isFullScreen()) {
1059 // Switch off fullscreen before closing.
1064 // Make sure the timer time out will not trigger a statusbar update.
1065 d.statusbar_timer_.stop();
1067 // Saving fullscreen requires additional tweaks in the toolbar code.
1068 // It wouldn't also work under linux natively.
1069 if (lyxrc.allow_geometry_session) {
1074 close_event->accept();
1078 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1080 if (event->mimeData()->hasUrls())
1082 /// \todo Ask lyx-devel is this is enough:
1083 /// if (event->mimeData()->hasFormat("text/plain"))
1084 /// event->acceptProposedAction();
1088 void GuiView::dropEvent(QDropEvent * event)
1090 QList<QUrl> files = event->mimeData()->urls();
1091 if (files.isEmpty())
1094 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1095 for (int i = 0; i != files.size(); ++i) {
1096 string const file = os::internal_path(fromqstr(
1097 files.at(i).toLocalFile()));
1101 string const ext = support::getExtension(file);
1102 vector<const Format *> found_formats;
1104 // Find all formats that have the correct extension.
1105 vector<const Format *> const & import_formats
1106 = theConverters().importableFormats();
1107 vector<const Format *>::const_iterator it = import_formats.begin();
1108 for (; it != import_formats.end(); ++it)
1109 if ((*it)->hasExtension(ext))
1110 found_formats.push_back(*it);
1113 if (found_formats.size() >= 1) {
1114 if (found_formats.size() > 1) {
1115 //FIXME: show a dialog to choose the correct importable format
1116 LYXERR(Debug::FILES,
1117 "Multiple importable formats found, selecting first");
1119 string const arg = found_formats[0]->name() + " " + file;
1120 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1123 //FIXME: do we have to explicitly check whether it's a lyx file?
1124 LYXERR(Debug::FILES,
1125 "No formats found, trying to open it as a lyx file");
1126 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1128 // add the functions to the queue
1129 guiApp->addToFuncRequestQueue(cmd);
1132 // now process the collected functions. We perform the events
1133 // asynchronously. This prevents potential problems in case the
1134 // BufferView is closed within an event.
1135 guiApp->processFuncRequestQueueAsync();
1139 void GuiView::message(docstring const & str)
1141 if (ForkedProcess::iAmAChild())
1144 // call is moved to GUI-thread by GuiProgress
1145 d.progress_->appendMessage(toqstr(str));
1149 void GuiView::clearMessageText()
1151 message(docstring());
1155 void GuiView::updateStatusBarMessage(QString const & str)
1157 statusBar()->showMessage(str);
1158 d.statusbar_timer_.stop();
1159 d.statusbar_timer_.start(3000);
1163 void GuiView::clearMessage()
1165 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1166 // the hasFocus function mostly returns false, even if the focus is on
1167 // a workarea in this view.
1171 d.statusbar_timer_.stop();
1175 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1177 if (wa != d.current_work_area_
1178 || wa->bufferView().buffer().isInternal())
1180 Buffer const & buf = wa->bufferView().buffer();
1181 // Set the windows title
1182 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1183 if (buf.notifiesExternalModification()) {
1184 title = bformat(_("%1$s (modified externally)"), title);
1185 // If the external modification status has changed, then maybe the status of
1186 // buffer-save has changed too.
1190 title += from_ascii(" - LyX");
1192 setWindowTitle(toqstr(title));
1193 // Sets the path for the window: this is used by OSX to
1194 // allow a context click on the title bar showing a menu
1195 // with the path up to the file
1196 setWindowFilePath(toqstr(buf.absFileName()));
1197 // Tell Qt whether the current document is changed
1198 setWindowModified(!buf.isClean());
1200 if (buf.params().shell_escape)
1201 shell_escape_->show();
1203 shell_escape_->hide();
1205 if (buf.hasReadonlyFlag())
1210 if (buf.lyxvc().inUse()) {
1211 version_control_->show();
1212 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1214 version_control_->hide();
1218 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1220 if (d.current_work_area_)
1221 // disconnect the current work area from all slots
1222 QObject::disconnect(d.current_work_area_, 0, this, 0);
1224 disconnectBufferView();
1225 connectBufferView(wa->bufferView());
1226 connectBuffer(wa->bufferView().buffer());
1227 d.current_work_area_ = wa;
1228 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1229 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1230 QObject::connect(wa, SIGNAL(busy(bool)),
1231 this, SLOT(setBusy(bool)));
1232 // connection of a signal to a signal
1233 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1234 this, SIGNAL(bufferViewChanged()));
1235 Q_EMIT updateWindowTitle(wa);
1236 Q_EMIT bufferViewChanged();
1240 void GuiView::onBufferViewChanged()
1243 // Buffer-dependent dialogs must be updated. This is done here because
1244 // some dialogs require buffer()->text.
1249 void GuiView::on_lastWorkAreaRemoved()
1252 // We already are in a close event. Nothing more to do.
1255 if (d.splitter_->count() > 1)
1256 // We have a splitter so don't close anything.
1259 // Reset and updates the dialogs.
1260 Q_EMIT bufferViewChanged();
1265 if (lyxrc.open_buffers_in_tabs)
1266 // Nothing more to do, the window should stay open.
1269 if (guiApp->viewIds().size() > 1) {
1275 // On Mac we also close the last window because the application stay
1276 // resident in memory. On other platforms we don't close the last
1277 // window because this would quit the application.
1283 void GuiView::updateStatusBar()
1285 // let the user see the explicit message
1286 if (d.statusbar_timer_.isActive())
1293 void GuiView::showMessage()
1297 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1298 if (msg.isEmpty()) {
1299 BufferView const * bv = currentBufferView();
1301 msg = toqstr(bv->cursor().currentState(devel_mode_));
1303 msg = qt_("Welcome to LyX!");
1305 statusBar()->showMessage(msg);
1309 bool GuiView::event(QEvent * e)
1313 // Useful debug code:
1314 //case QEvent::ActivationChange:
1315 //case QEvent::WindowDeactivate:
1316 //case QEvent::Paint:
1317 //case QEvent::Enter:
1318 //case QEvent::Leave:
1319 //case QEvent::HoverEnter:
1320 //case QEvent::HoverLeave:
1321 //case QEvent::HoverMove:
1322 //case QEvent::StatusTip:
1323 //case QEvent::DragEnter:
1324 //case QEvent::DragLeave:
1325 //case QEvent::Drop:
1328 case QEvent::WindowActivate: {
1329 GuiView * old_view = guiApp->currentView();
1330 if (this == old_view) {
1332 return QMainWindow::event(e);
1334 if (old_view && old_view->currentBufferView()) {
1335 // save current selection to the selection buffer to allow
1336 // middle-button paste in this window.
1337 cap::saveSelection(old_view->currentBufferView()->cursor());
1339 guiApp->setCurrentView(this);
1340 if (d.current_work_area_)
1341 on_currentWorkAreaChanged(d.current_work_area_);
1345 return QMainWindow::event(e);
1348 case QEvent::ShortcutOverride: {
1350 if (isFullScreen() && menuBar()->isHidden()) {
1351 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1352 // FIXME: we should also try to detect special LyX shortcut such as
1353 // Alt-P and Alt-M. Right now there is a hack in
1354 // GuiWorkArea::processKeySym() that hides again the menubar for
1356 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1358 return QMainWindow::event(e);
1361 return QMainWindow::event(e);
1365 return QMainWindow::event(e);
1369 void GuiView::resetWindowTitle()
1371 setWindowTitle(qt_("LyX"));
1374 bool GuiView::focusNextPrevChild(bool /*next*/)
1381 bool GuiView::busy() const
1387 void GuiView::setBusy(bool busy)
1389 bool const busy_before = busy_ > 0;
1390 busy ? ++busy_ : --busy_;
1391 if ((busy_ > 0) == busy_before)
1392 // busy state didn't change
1396 QApplication::setOverrideCursor(Qt::WaitCursor);
1399 QApplication::restoreOverrideCursor();
1404 void GuiView::resetCommandExecute()
1406 command_execute_ = false;
1411 double GuiView::pixelRatio() const
1413 #if QT_VERSION >= 0x050000
1414 return qt_scale_factor * devicePixelRatio();
1421 GuiWorkArea * GuiView::workArea(int index)
1423 if (TabWorkArea * twa = d.currentTabWorkArea())
1424 if (index < twa->count())
1425 return twa->workArea(index);
1430 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1432 if (currentWorkArea()
1433 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1434 return currentWorkArea();
1435 if (TabWorkArea * twa = d.currentTabWorkArea())
1436 return twa->workArea(buffer);
1441 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1443 // Automatically create a TabWorkArea if there are none yet.
1444 TabWorkArea * tab_widget = d.splitter_->count()
1445 ? d.currentTabWorkArea() : addTabWorkArea();
1446 return tab_widget->addWorkArea(buffer, *this);
1450 TabWorkArea * GuiView::addTabWorkArea()
1452 TabWorkArea * twa = new TabWorkArea;
1453 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1454 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1455 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1456 this, SLOT(on_lastWorkAreaRemoved()));
1458 d.splitter_->addWidget(twa);
1459 d.stack_widget_->setCurrentWidget(d.splitter_);
1464 GuiWorkArea const * GuiView::currentWorkArea() const
1466 return d.current_work_area_;
1470 GuiWorkArea * GuiView::currentWorkArea()
1472 return d.current_work_area_;
1476 GuiWorkArea const * GuiView::currentMainWorkArea() const
1478 if (!d.currentTabWorkArea())
1480 return d.currentTabWorkArea()->currentWorkArea();
1484 GuiWorkArea * GuiView::currentMainWorkArea()
1486 if (!d.currentTabWorkArea())
1488 return d.currentTabWorkArea()->currentWorkArea();
1492 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1494 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1496 d.current_work_area_ = 0;
1498 Q_EMIT bufferViewChanged();
1502 // FIXME: I've no clue why this is here and why it accesses
1503 // theGuiApp()->currentView, which might be 0 (bug 6464).
1504 // See also 27525 (vfr).
1505 if (theGuiApp()->currentView() == this
1506 && theGuiApp()->currentView()->currentWorkArea() == wa)
1509 if (currentBufferView())
1510 cap::saveSelection(currentBufferView()->cursor());
1512 theGuiApp()->setCurrentView(this);
1513 d.current_work_area_ = wa;
1515 // We need to reset this now, because it will need to be
1516 // right if the tabWorkArea gets reset in the for loop. We
1517 // will change it back if we aren't in that case.
1518 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1519 d.current_main_work_area_ = wa;
1521 for (int i = 0; i != d.splitter_->count(); ++i) {
1522 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1523 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1524 << ", Current main wa: " << currentMainWorkArea());
1529 d.current_main_work_area_ = old_cmwa;
1531 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1532 on_currentWorkAreaChanged(wa);
1533 BufferView & bv = wa->bufferView();
1534 bv.cursor().fixIfBroken();
1536 wa->setUpdatesEnabled(true);
1537 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1541 void GuiView::removeWorkArea(GuiWorkArea * wa)
1543 LASSERT(wa, return);
1544 if (wa == d.current_work_area_) {
1546 disconnectBufferView();
1547 d.current_work_area_ = 0;
1548 d.current_main_work_area_ = 0;
1551 bool found_twa = false;
1552 for (int i = 0; i != d.splitter_->count(); ++i) {
1553 TabWorkArea * twa = d.tabWorkArea(i);
1554 if (twa->removeWorkArea(wa)) {
1555 // Found in this tab group, and deleted the GuiWorkArea.
1557 if (twa->count() != 0) {
1558 if (d.current_work_area_ == 0)
1559 // This means that we are closing the current GuiWorkArea, so
1560 // switch to the next GuiWorkArea in the found TabWorkArea.
1561 setCurrentWorkArea(twa->currentWorkArea());
1563 // No more WorkAreas in this tab group, so delete it.
1570 // It is not a tabbed work area (i.e., the search work area), so it
1571 // should be deleted by other means.
1572 LASSERT(found_twa, return);
1574 if (d.current_work_area_ == 0) {
1575 if (d.splitter_->count() != 0) {
1576 TabWorkArea * twa = d.currentTabWorkArea();
1577 setCurrentWorkArea(twa->currentWorkArea());
1579 // No more work areas, switch to the background widget.
1580 setCurrentWorkArea(0);
1586 LayoutBox * GuiView::getLayoutDialog() const
1592 void GuiView::updateLayoutList()
1595 d.layout_->updateContents(false);
1599 void GuiView::updateToolbars()
1601 ToolbarMap::iterator end = d.toolbars_.end();
1602 if (d.current_work_area_) {
1604 if (d.current_work_area_->bufferView().cursor().inMathed()
1605 && !d.current_work_area_->bufferView().cursor().inRegexped())
1606 context |= Toolbars::MATH;
1607 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1608 context |= Toolbars::TABLE;
1609 if (currentBufferView()->buffer().areChangesPresent()
1610 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1611 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1612 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1613 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1614 context |= Toolbars::REVIEW;
1615 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1616 context |= Toolbars::MATHMACROTEMPLATE;
1617 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1618 context |= Toolbars::IPA;
1619 if (command_execute_)
1620 context |= Toolbars::MINIBUFFER;
1621 if (minibuffer_focus_) {
1622 context |= Toolbars::MINIBUFFER_FOCUS;
1623 minibuffer_focus_ = false;
1626 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1627 it->second->update(context);
1629 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1630 it->second->update();
1634 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1636 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1637 LASSERT(newBuffer, return);
1639 GuiWorkArea * wa = workArea(*newBuffer);
1642 newBuffer->masterBuffer()->updateBuffer();
1644 wa = addWorkArea(*newBuffer);
1645 // scroll to the position when the BufferView was last closed
1646 if (lyxrc.use_lastfilepos) {
1647 LastFilePosSection::FilePos filepos =
1648 theSession().lastFilePos().load(newBuffer->fileName());
1649 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1652 //Disconnect the old buffer...there's no new one.
1655 connectBuffer(*newBuffer);
1656 connectBufferView(wa->bufferView());
1658 setCurrentWorkArea(wa);
1662 void GuiView::connectBuffer(Buffer & buf)
1664 buf.setGuiDelegate(this);
1668 void GuiView::disconnectBuffer()
1670 if (d.current_work_area_)
1671 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1675 void GuiView::connectBufferView(BufferView & bv)
1677 bv.setGuiDelegate(this);
1681 void GuiView::disconnectBufferView()
1683 if (d.current_work_area_)
1684 d.current_work_area_->bufferView().setGuiDelegate(0);
1688 void GuiView::errors(string const & error_type, bool from_master)
1690 BufferView const * const bv = currentBufferView();
1694 #if EXPORT_in_THREAD
1695 // We are called with from_master == false by default, so we
1696 // have to figure out whether that is the case or not.
1697 ErrorList & el = bv->buffer().errorList(error_type);
1699 el = bv->buffer().masterBuffer()->errorList(error_type);
1703 ErrorList const & el = from_master ?
1704 bv->buffer().masterBuffer()->errorList(error_type) :
1705 bv->buffer().errorList(error_type);
1711 string data = error_type;
1713 data = "from_master|" + error_type;
1714 showDialog("errorlist", data);
1718 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1720 d.toc_models_.updateItem(toqstr(type), dit);
1724 void GuiView::structureChanged()
1726 // This is called from the Buffer, which has no way to ensure that cursors
1727 // in BufferView remain valid.
1728 if (documentBufferView())
1729 documentBufferView()->cursor().sanitize();
1730 // FIXME: This is slightly expensive, though less than the tocBackend update
1731 // (#9880). This also resets the view in the Toc Widget (#6675).
1732 d.toc_models_.reset(documentBufferView());
1733 // Navigator needs more than a simple update in this case. It needs to be
1735 updateDialog("toc", "");
1739 void GuiView::updateDialog(string const & name, string const & data)
1741 if (!isDialogVisible(name))
1744 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1745 if (it == d.dialogs_.end())
1748 Dialog * const dialog = it->second.get();
1749 if (dialog->isVisibleView())
1750 dialog->initialiseParams(data);
1754 BufferView * GuiView::documentBufferView()
1756 return currentMainWorkArea()
1757 ? ¤tMainWorkArea()->bufferView()
1762 BufferView const * GuiView::documentBufferView() const
1764 return currentMainWorkArea()
1765 ? ¤tMainWorkArea()->bufferView()
1770 BufferView * GuiView::currentBufferView()
1772 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1776 BufferView const * GuiView::currentBufferView() const
1778 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1782 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1783 Buffer const * orig, Buffer * clone)
1785 bool const success = clone->autoSave();
1787 busyBuffers.remove(orig);
1789 ? _("Automatic save done.")
1790 : _("Automatic save failed!");
1794 void GuiView::autoSave()
1796 LYXERR(Debug::INFO, "Running autoSave()");
1798 Buffer * buffer = documentBufferView()
1799 ? &documentBufferView()->buffer() : 0;
1801 resetAutosaveTimers();
1805 GuiViewPrivate::busyBuffers.insert(buffer);
1806 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1807 buffer, buffer->cloneBufferOnly());
1808 d.autosave_watcher_.setFuture(f);
1809 resetAutosaveTimers();
1813 void GuiView::resetAutosaveTimers()
1816 d.autosave_timeout_.restart();
1820 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1823 Buffer * buf = currentBufferView()
1824 ? ¤tBufferView()->buffer() : 0;
1825 Buffer * doc_buffer = documentBufferView()
1826 ? &(documentBufferView()->buffer()) : 0;
1829 /* In LyX/Mac, when a dialog is open, the menus of the
1830 application can still be accessed without giving focus to
1831 the main window. In this case, we want to disable the menu
1832 entries that are buffer-related.
1833 This code must not be used on Linux and Windows, since it
1834 would disable buffer-related entries when hovering over the
1835 menu (see bug #9574).
1837 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1843 // Check whether we need a buffer
1844 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1845 // no, exit directly
1846 flag.message(from_utf8(N_("Command not allowed with"
1847 "out any document open")));
1848 flag.setEnabled(false);
1852 if (cmd.origin() == FuncRequest::TOC) {
1853 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1854 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1855 flag.setEnabled(false);
1859 switch(cmd.action()) {
1860 case LFUN_BUFFER_IMPORT:
1863 case LFUN_MASTER_BUFFER_EXPORT:
1865 && (doc_buffer->parent() != 0
1866 || doc_buffer->hasChildren())
1867 && !d.processing_thread_watcher_.isRunning()
1868 // this launches a dialog, which would be in the wrong Buffer
1869 && !(::lyx::operator==(cmd.argument(), "custom"));
1872 case LFUN_MASTER_BUFFER_UPDATE:
1873 case LFUN_MASTER_BUFFER_VIEW:
1875 && (doc_buffer->parent() != 0
1876 || doc_buffer->hasChildren())
1877 && !d.processing_thread_watcher_.isRunning();
1880 case LFUN_BUFFER_UPDATE:
1881 case LFUN_BUFFER_VIEW: {
1882 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1886 string format = to_utf8(cmd.argument());
1887 if (cmd.argument().empty())
1888 format = doc_buffer->params().getDefaultOutputFormat();
1889 enable = doc_buffer->params().isExportable(format, true);
1893 case LFUN_BUFFER_RELOAD:
1894 enable = doc_buffer && !doc_buffer->isUnnamed()
1895 && doc_buffer->fileName().exists()
1896 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1899 case LFUN_BUFFER_CHILD_OPEN:
1900 enable = doc_buffer != 0;
1903 case LFUN_BUFFER_WRITE:
1904 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1907 //FIXME: This LFUN should be moved to GuiApplication.
1908 case LFUN_BUFFER_WRITE_ALL: {
1909 // We enable the command only if there are some modified buffers
1910 Buffer * first = theBufferList().first();
1915 // We cannot use a for loop as the buffer list is a cycle.
1917 if (!b->isClean()) {
1921 b = theBufferList().next(b);
1922 } while (b != first);
1926 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1927 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1930 case LFUN_BUFFER_EXPORT: {
1931 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1935 return doc_buffer->getStatus(cmd, flag);
1939 case LFUN_BUFFER_EXPORT_AS:
1940 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1945 case LFUN_BUFFER_WRITE_AS:
1946 enable = doc_buffer != 0;
1949 case LFUN_BUFFER_CLOSE:
1950 case LFUN_VIEW_CLOSE:
1951 enable = doc_buffer != 0;
1954 case LFUN_BUFFER_CLOSE_ALL:
1955 enable = theBufferList().last() != theBufferList().first();
1958 case LFUN_BUFFER_CHKTEX: {
1959 // hide if we have no checktex command
1960 if (lyxrc.chktex_command.empty()) {
1961 flag.setUnknown(true);
1965 if (!doc_buffer || !doc_buffer->params().isLatex()
1966 || d.processing_thread_watcher_.isRunning()) {
1967 // grey out, don't hide
1975 case LFUN_VIEW_SPLIT:
1976 if (cmd.getArg(0) == "vertical")
1977 enable = doc_buffer && (d.splitter_->count() == 1 ||
1978 d.splitter_->orientation() == Qt::Vertical);
1980 enable = doc_buffer && (d.splitter_->count() == 1 ||
1981 d.splitter_->orientation() == Qt::Horizontal);
1984 case LFUN_TAB_GROUP_CLOSE:
1985 enable = d.tabWorkAreaCount() > 1;
1988 case LFUN_DEVEL_MODE_TOGGLE:
1989 flag.setOnOff(devel_mode_);
1992 case LFUN_TOOLBAR_TOGGLE: {
1993 string const name = cmd.getArg(0);
1994 if (GuiToolbar * t = toolbar(name))
1995 flag.setOnOff(t->isVisible());
1998 docstring const msg =
1999 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2005 case LFUN_TOOLBAR_MOVABLE: {
2006 string const name = cmd.getArg(0);
2007 // use negation since locked == !movable
2009 // toolbar name * locks all toolbars
2010 flag.setOnOff(!toolbarsMovable_);
2011 else if (GuiToolbar * t = toolbar(name))
2012 flag.setOnOff(!(t->isMovable()));
2015 docstring const msg =
2016 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2022 case LFUN_ICON_SIZE:
2023 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2026 case LFUN_DROP_LAYOUTS_CHOICE:
2030 case LFUN_UI_TOGGLE:
2031 flag.setOnOff(isFullScreen());
2034 case LFUN_DIALOG_DISCONNECT_INSET:
2037 case LFUN_DIALOG_HIDE:
2038 // FIXME: should we check if the dialog is shown?
2041 case LFUN_DIALOG_TOGGLE:
2042 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2045 case LFUN_DIALOG_SHOW: {
2046 string const name = cmd.getArg(0);
2048 enable = name == "aboutlyx"
2049 || name == "file" //FIXME: should be removed.
2051 || name == "texinfo"
2052 || name == "progress"
2053 || name == "compare";
2054 else if (name == "character" || name == "symbols"
2055 || name == "mathdelimiter" || name == "mathmatrix") {
2056 if (!buf || buf->isReadonly())
2059 Cursor const & cur = currentBufferView()->cursor();
2060 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2063 else if (name == "latexlog")
2064 enable = FileName(doc_buffer->logName()).isReadableFile();
2065 else if (name == "spellchecker")
2066 enable = theSpellChecker()
2067 && !doc_buffer->isReadonly()
2068 && !doc_buffer->text().empty();
2069 else if (name == "vclog")
2070 enable = doc_buffer->lyxvc().inUse();
2074 case LFUN_DIALOG_UPDATE: {
2075 string const name = cmd.getArg(0);
2077 enable = name == "prefs";
2081 case LFUN_COMMAND_EXECUTE:
2083 case LFUN_MENU_OPEN:
2084 // Nothing to check.
2087 case LFUN_COMPLETION_INLINE:
2088 if (!d.current_work_area_
2089 || !d.current_work_area_->completer().inlinePossible(
2090 currentBufferView()->cursor()))
2094 case LFUN_COMPLETION_POPUP:
2095 if (!d.current_work_area_
2096 || !d.current_work_area_->completer().popupPossible(
2097 currentBufferView()->cursor()))
2102 if (!d.current_work_area_
2103 || !d.current_work_area_->completer().inlinePossible(
2104 currentBufferView()->cursor()))
2108 case LFUN_COMPLETION_ACCEPT:
2109 if (!d.current_work_area_
2110 || (!d.current_work_area_->completer().popupVisible()
2111 && !d.current_work_area_->completer().inlineVisible()
2112 && !d.current_work_area_->completer().completionAvailable()))
2116 case LFUN_COMPLETION_CANCEL:
2117 if (!d.current_work_area_
2118 || (!d.current_work_area_->completer().popupVisible()
2119 && !d.current_work_area_->completer().inlineVisible()))
2123 case LFUN_BUFFER_ZOOM_OUT:
2124 case LFUN_BUFFER_ZOOM_IN: {
2125 // only diff between these two is that the default for ZOOM_OUT
2127 bool const neg_zoom =
2128 convert<int>(cmd.argument()) < 0 ||
2129 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2130 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2131 docstring const msg =
2132 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2136 enable = doc_buffer;
2140 case LFUN_BUFFER_ZOOM: {
2141 bool const less_than_min_zoom =
2142 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2143 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2144 docstring const msg =
2145 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2150 enable = doc_buffer;
2154 case LFUN_BUFFER_MOVE_NEXT:
2155 case LFUN_BUFFER_MOVE_PREVIOUS:
2156 // we do not cycle when moving
2157 case LFUN_BUFFER_NEXT:
2158 case LFUN_BUFFER_PREVIOUS:
2159 // because we cycle, it doesn't matter whether on first or last
2160 enable = (d.currentTabWorkArea()->count() > 1);
2162 case LFUN_BUFFER_SWITCH:
2163 // toggle on the current buffer, but do not toggle off
2164 // the other ones (is that a good idea?)
2166 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2167 flag.setOnOff(true);
2170 case LFUN_VC_REGISTER:
2171 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2173 case LFUN_VC_RENAME:
2174 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2177 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2179 case LFUN_VC_CHECK_IN:
2180 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2182 case LFUN_VC_CHECK_OUT:
2183 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2185 case LFUN_VC_LOCKING_TOGGLE:
2186 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2187 && doc_buffer->lyxvc().lockingToggleEnabled();
2188 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2190 case LFUN_VC_REVERT:
2191 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2192 && !doc_buffer->hasReadonlyFlag();
2194 case LFUN_VC_UNDO_LAST:
2195 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2197 case LFUN_VC_REPO_UPDATE:
2198 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2200 case LFUN_VC_COMMAND: {
2201 if (cmd.argument().empty())
2203 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2207 case LFUN_VC_COMPARE:
2208 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2211 case LFUN_SERVER_GOTO_FILE_ROW:
2212 case LFUN_LYX_ACTIVATE:
2214 case LFUN_FORWARD_SEARCH:
2215 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2218 case LFUN_FILE_INSERT_PLAINTEXT:
2219 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2220 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2223 case LFUN_SPELLING_CONTINUOUSLY:
2224 flag.setOnOff(lyxrc.spellcheck_continuously);
2232 flag.setEnabled(false);
2238 static FileName selectTemplateFile()
2240 FileDialog dlg(qt_("Select template file"));
2241 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2242 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2244 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2245 QStringList(qt_("LyX Documents (*.lyx)")));
2247 if (result.first == FileDialog::Later)
2249 if (result.second.isEmpty())
2251 return FileName(fromqstr(result.second));
2255 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2259 Buffer * newBuffer = 0;
2261 newBuffer = checkAndLoadLyXFile(filename);
2262 } catch (ExceptionMessage const & e) {
2269 message(_("Document not loaded."));
2273 setBuffer(newBuffer);
2274 newBuffer->errors("Parse");
2277 theSession().lastFiles().add(filename);
2278 theSession().writeFile();
2285 void GuiView::openDocument(string const & fname)
2287 string initpath = lyxrc.document_path;
2289 if (documentBufferView()) {
2290 string const trypath = documentBufferView()->buffer().filePath();
2291 // If directory is writeable, use this as default.
2292 if (FileName(trypath).isDirWritable())
2298 if (fname.empty()) {
2299 FileDialog dlg(qt_("Select document to open"));
2300 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2301 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2303 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2304 FileDialog::Result result =
2305 dlg.open(toqstr(initpath), filter);
2307 if (result.first == FileDialog::Later)
2310 filename = fromqstr(result.second);
2312 // check selected filename
2313 if (filename.empty()) {
2314 message(_("Canceled."));
2320 // get absolute path of file and add ".lyx" to the filename if
2322 FileName const fullname =
2323 fileSearch(string(), filename, "lyx", support::may_not_exist);
2324 if (!fullname.empty())
2325 filename = fullname.absFileName();
2327 if (!fullname.onlyPath().isDirectory()) {
2328 Alert::warning(_("Invalid filename"),
2329 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2330 from_utf8(fullname.absFileName())));
2334 // if the file doesn't exist and isn't already open (bug 6645),
2335 // let the user create one
2336 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2337 !LyXVC::file_not_found_hook(fullname)) {
2338 // the user specifically chose this name. Believe him.
2339 Buffer * const b = newFile(filename, string(), true);
2345 docstring const disp_fn = makeDisplayPath(filename);
2346 message(bformat(_("Opening document %1$s..."), disp_fn));
2349 Buffer * buf = loadDocument(fullname);
2351 str2 = bformat(_("Document %1$s opened."), disp_fn);
2352 if (buf->lyxvc().inUse())
2353 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2354 " " + _("Version control detected.");
2356 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2361 // FIXME: clean that
2362 static bool import(GuiView * lv, FileName const & filename,
2363 string const & format, ErrorList & errorList)
2365 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2367 string loader_format;
2368 vector<string> loaders = theConverters().loaders();
2369 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2370 vector<string>::const_iterator it = loaders.begin();
2371 vector<string>::const_iterator en = loaders.end();
2372 for (; it != en; ++it) {
2373 if (!theConverters().isReachable(format, *it))
2376 string const tofile =
2377 support::changeExtension(filename.absFileName(),
2378 theFormats().extension(*it));
2379 if (!theConverters().convert(0, filename, FileName(tofile),
2380 filename, format, *it, errorList))
2382 loader_format = *it;
2385 if (loader_format.empty()) {
2386 frontend::Alert::error(_("Couldn't import file"),
2387 bformat(_("No information for importing the format %1$s."),
2388 theFormats().prettyName(format)));
2392 loader_format = format;
2394 if (loader_format == "lyx") {
2395 Buffer * buf = lv->loadDocument(lyxfile);
2399 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2403 bool as_paragraphs = loader_format == "textparagraph";
2404 string filename2 = (loader_format == format) ? filename.absFileName()
2405 : support::changeExtension(filename.absFileName(),
2406 theFormats().extension(loader_format));
2407 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2409 guiApp->setCurrentView(lv);
2410 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2417 void GuiView::importDocument(string const & argument)
2420 string filename = split(argument, format, ' ');
2422 LYXERR(Debug::INFO, format << " file: " << filename);
2424 // need user interaction
2425 if (filename.empty()) {
2426 string initpath = lyxrc.document_path;
2427 if (documentBufferView()) {
2428 string const trypath = documentBufferView()->buffer().filePath();
2429 // If directory is writeable, use this as default.
2430 if (FileName(trypath).isDirWritable())
2434 docstring const text = bformat(_("Select %1$s file to import"),
2435 theFormats().prettyName(format));
2437 FileDialog dlg(toqstr(text));
2438 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2439 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2441 docstring filter = theFormats().prettyName(format);
2444 filter += from_utf8(theFormats().extensions(format));
2447 FileDialog::Result result =
2448 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2450 if (result.first == FileDialog::Later)
2453 filename = fromqstr(result.second);
2455 // check selected filename
2456 if (filename.empty())
2457 message(_("Canceled."));
2460 if (filename.empty())
2463 // get absolute path of file
2464 FileName const fullname(support::makeAbsPath(filename));
2466 // Can happen if the user entered a path into the dialog
2468 if (fullname.onlyFileName().empty()) {
2469 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2470 "Aborting import."),
2471 from_utf8(fullname.absFileName()));
2472 frontend::Alert::error(_("File name error"), msg);
2473 message(_("Canceled."));
2478 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2480 // Check if the document already is open
2481 Buffer * buf = theBufferList().getBuffer(lyxfile);
2484 if (!closeBuffer()) {
2485 message(_("Canceled."));
2490 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2492 // if the file exists already, and we didn't do
2493 // -i lyx thefile.lyx, warn
2494 if (lyxfile.exists() && fullname != lyxfile) {
2496 docstring text = bformat(_("The document %1$s already exists.\n\n"
2497 "Do you want to overwrite that document?"), displaypath);
2498 int const ret = Alert::prompt(_("Overwrite document?"),
2499 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2502 message(_("Canceled."));
2507 message(bformat(_("Importing %1$s..."), displaypath));
2508 ErrorList errorList;
2509 if (import(this, fullname, format, errorList))
2510 message(_("imported."));
2512 message(_("file not imported!"));
2514 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2518 void GuiView::newDocument(string const & filename, bool from_template)
2520 FileName initpath(lyxrc.document_path);
2521 if (documentBufferView()) {
2522 FileName const trypath(documentBufferView()->buffer().filePath());
2523 // If directory is writeable, use this as default.
2524 if (trypath.isDirWritable())
2528 string templatefile;
2529 if (from_template) {
2530 templatefile = selectTemplateFile().absFileName();
2531 if (templatefile.empty())
2536 if (filename.empty())
2537 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2539 b = newFile(filename, templatefile, true);
2544 // If no new document could be created, it is unsure
2545 // whether there is a valid BufferView.
2546 if (currentBufferView())
2547 // Ensure the cursor is correctly positioned on screen.
2548 currentBufferView()->showCursor();
2552 void GuiView::insertLyXFile(docstring const & fname)
2554 BufferView * bv = documentBufferView();
2559 FileName filename(to_utf8(fname));
2560 if (filename.empty()) {
2561 // Launch a file browser
2563 string initpath = lyxrc.document_path;
2564 string const trypath = bv->buffer().filePath();
2565 // If directory is writeable, use this as default.
2566 if (FileName(trypath).isDirWritable())
2570 FileDialog dlg(qt_("Select LyX document to insert"));
2571 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2572 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2574 FileDialog::Result result = dlg.open(toqstr(initpath),
2575 QStringList(qt_("LyX Documents (*.lyx)")));
2577 if (result.first == FileDialog::Later)
2581 filename.set(fromqstr(result.second));
2583 // check selected filename
2584 if (filename.empty()) {
2585 // emit message signal.
2586 message(_("Canceled."));
2591 bv->insertLyXFile(filename);
2592 bv->buffer().errors("Parse");
2596 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2598 FileName fname = b.fileName();
2599 FileName const oldname = fname;
2601 if (!newname.empty()) {
2603 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2605 // Switch to this Buffer.
2608 // No argument? Ask user through dialog.
2610 FileDialog dlg(qt_("Choose a filename to save document as"));
2611 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2612 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2614 if (!isLyXFileName(fname.absFileName()))
2615 fname.changeExtension(".lyx");
2617 FileDialog::Result result =
2618 dlg.save(toqstr(fname.onlyPath().absFileName()),
2619 QStringList(qt_("LyX Documents (*.lyx)")),
2620 toqstr(fname.onlyFileName()));
2622 if (result.first == FileDialog::Later)
2625 fname.set(fromqstr(result.second));
2630 if (!isLyXFileName(fname.absFileName()))
2631 fname.changeExtension(".lyx");
2634 // fname is now the new Buffer location.
2636 // if there is already a Buffer open with this name, we do not want
2637 // to have another one. (the second test makes sure we're not just
2638 // trying to overwrite ourselves, which is fine.)
2639 if (theBufferList().exists(fname) && fname != oldname
2640 && theBufferList().getBuffer(fname) != &b) {
2641 docstring const text =
2642 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2643 "Please close it before attempting to overwrite it.\n"
2644 "Do you want to choose a new filename?"),
2645 from_utf8(fname.absFileName()));
2646 int const ret = Alert::prompt(_("Chosen File Already Open"),
2647 text, 0, 1, _("&Rename"), _("&Cancel"));
2649 case 0: return renameBuffer(b, docstring(), kind);
2650 case 1: return false;
2655 bool const existsLocal = fname.exists();
2656 bool const existsInVC = LyXVC::fileInVC(fname);
2657 if (existsLocal || existsInVC) {
2658 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2659 if (kind != LV_WRITE_AS && existsInVC) {
2660 // renaming to a name that is already in VC
2662 docstring text = bformat(_("The document %1$s "
2663 "is already registered.\n\n"
2664 "Do you want to choose a new name?"),
2666 docstring const title = (kind == LV_VC_RENAME) ?
2667 _("Rename document?") : _("Copy document?");
2668 docstring const button = (kind == LV_VC_RENAME) ?
2669 _("&Rename") : _("&Copy");
2670 int const ret = Alert::prompt(title, text, 0, 1,
2671 button, _("&Cancel"));
2673 case 0: return renameBuffer(b, docstring(), kind);
2674 case 1: return false;
2679 docstring text = bformat(_("The document %1$s "
2680 "already exists.\n\n"
2681 "Do you want to overwrite that document?"),
2683 int const ret = Alert::prompt(_("Overwrite document?"),
2684 text, 0, 2, _("&Overwrite"),
2685 _("&Rename"), _("&Cancel"));
2688 case 1: return renameBuffer(b, docstring(), kind);
2689 case 2: return false;
2695 case LV_VC_RENAME: {
2696 string msg = b.lyxvc().rename(fname);
2699 message(from_utf8(msg));
2703 string msg = b.lyxvc().copy(fname);
2706 message(from_utf8(msg));
2712 // LyXVC created the file already in case of LV_VC_RENAME or
2713 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2714 // relative paths of included stuff right if we moved e.g. from
2715 // /a/b.lyx to /a/c/b.lyx.
2717 bool const saved = saveBuffer(b, fname);
2724 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2726 FileName fname = b.fileName();
2728 FileDialog dlg(qt_("Choose a filename to export the document as"));
2729 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2732 QString const anyformat = qt_("Guess from extension (*.*)");
2735 vector<Format const *> export_formats;
2736 for (Format const & f : theFormats())
2737 if (f.documentFormat())
2738 export_formats.push_back(&f);
2739 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2740 map<QString, string> fmap;
2743 for (Format const * f : export_formats) {
2744 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2745 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2747 from_ascii(f->extension())));
2748 types << loc_filter;
2749 fmap[loc_filter] = f->name();
2750 if (from_ascii(f->name()) == iformat) {
2751 filter = loc_filter;
2752 ext = f->extension();
2755 string ofname = fname.onlyFileName();
2757 ofname = support::changeExtension(ofname, ext);
2758 FileDialog::Result result =
2759 dlg.save(toqstr(fname.onlyPath().absFileName()),
2763 if (result.first != FileDialog::Chosen)
2767 fname.set(fromqstr(result.second));
2768 if (filter == anyformat)
2769 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2771 fmt_name = fmap[filter];
2772 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2773 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2775 if (fmt_name.empty() || fname.empty())
2778 // fname is now the new Buffer location.
2779 if (FileName(fname).exists()) {
2780 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2781 docstring text = bformat(_("The document %1$s already "
2782 "exists.\n\nDo you want to "
2783 "overwrite that document?"),
2785 int const ret = Alert::prompt(_("Overwrite document?"),
2786 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2789 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2790 case 2: return false;
2794 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2797 return dr.dispatched();
2801 bool GuiView::saveBuffer(Buffer & b)
2803 return saveBuffer(b, FileName());
2807 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2809 if (workArea(b) && workArea(b)->inDialogMode())
2812 if (fn.empty() && b.isUnnamed())
2813 return renameBuffer(b, docstring());
2815 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2817 theSession().lastFiles().add(b.fileName());
2818 theSession().writeFile();
2822 // Switch to this Buffer.
2825 // FIXME: we don't tell the user *WHY* the save failed !!
2826 docstring const file = makeDisplayPath(b.absFileName(), 30);
2827 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2828 "Do you want to rename the document and "
2829 "try again?"), file);
2830 int const ret = Alert::prompt(_("Rename and save?"),
2831 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2834 if (!renameBuffer(b, docstring()))
2843 return saveBuffer(b, fn);
2847 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2849 return closeWorkArea(wa, false);
2853 // We only want to close the buffer if it is not visible in other workareas
2854 // of the same view, nor in other views, and if this is not a child
2855 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2857 Buffer & buf = wa->bufferView().buffer();
2859 bool last_wa = d.countWorkAreasOf(buf) == 1
2860 && !inOtherView(buf) && !buf.parent();
2862 bool close_buffer = last_wa;
2865 if (lyxrc.close_buffer_with_last_view == "yes")
2867 else if (lyxrc.close_buffer_with_last_view == "no")
2868 close_buffer = false;
2871 if (buf.isUnnamed())
2872 file = from_utf8(buf.fileName().onlyFileName());
2874 file = buf.fileName().displayName(30);
2875 docstring const text = bformat(
2876 _("Last view on document %1$s is being closed.\n"
2877 "Would you like to close or hide the document?\n"
2879 "Hidden documents can be displayed back through\n"
2880 "the menu: View->Hidden->...\n"
2882 "To remove this question, set your preference in:\n"
2883 " Tools->Preferences->Look&Feel->UserInterface\n"
2885 int ret = Alert::prompt(_("Close or hide document?"),
2886 text, 0, 1, _("&Close"), _("&Hide"));
2887 close_buffer = (ret == 0);
2891 return closeWorkArea(wa, close_buffer);
2895 bool GuiView::closeBuffer()
2897 GuiWorkArea * wa = currentMainWorkArea();
2898 // coverity complained about this
2899 // it seems unnecessary, but perhaps is worth the check
2900 LASSERT(wa, return false);
2902 setCurrentWorkArea(wa);
2903 Buffer & buf = wa->bufferView().buffer();
2904 return closeWorkArea(wa, !buf.parent());
2908 void GuiView::writeSession() const {
2909 GuiWorkArea const * active_wa = currentMainWorkArea();
2910 for (int i = 0; i < d.splitter_->count(); ++i) {
2911 TabWorkArea * twa = d.tabWorkArea(i);
2912 for (int j = 0; j < twa->count(); ++j) {
2913 GuiWorkArea * wa = twa->workArea(j);
2914 Buffer & buf = wa->bufferView().buffer();
2915 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2921 bool GuiView::closeBufferAll()
2923 // Close the workareas in all other views
2924 QList<int> const ids = guiApp->viewIds();
2925 for (int i = 0; i != ids.size(); ++i) {
2926 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2930 // Close our own workareas
2931 if (!closeWorkAreaAll())
2934 // Now close the hidden buffers. We prevent hidden buffers from being
2935 // dirty, so we can just close them.
2936 theBufferList().closeAll();
2941 bool GuiView::closeWorkAreaAll()
2943 setCurrentWorkArea(currentMainWorkArea());
2945 // We might be in a situation that there is still a tabWorkArea, but
2946 // there are no tabs anymore. This can happen when we get here after a
2947 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2948 // many TabWorkArea's have no documents anymore.
2951 // We have to call count() each time, because it can happen that
2952 // more than one splitter will disappear in one iteration (bug 5998).
2953 while (d.splitter_->count() > empty_twa) {
2954 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2956 if (twa->count() == 0)
2959 setCurrentWorkArea(twa->currentWorkArea());
2960 if (!closeTabWorkArea(twa))
2968 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2973 Buffer & buf = wa->bufferView().buffer();
2975 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2976 Alert::warning(_("Close document"),
2977 _("Document could not be closed because it is being processed by LyX."));
2982 return closeBuffer(buf);
2984 if (!inMultiTabs(wa))
2985 if (!saveBufferIfNeeded(buf, true))
2993 bool GuiView::closeBuffer(Buffer & buf)
2995 // If we are in a close_event all children will be closed in some time,
2996 // so no need to do it here. This will ensure that the children end up
2997 // in the session file in the correct order. If we close the master
2998 // buffer, we can close or release the child buffers here too.
2999 bool success = true;
3001 ListOfBuffers clist = buf.getChildren();
3002 ListOfBuffers::const_iterator it = clist.begin();
3003 ListOfBuffers::const_iterator const bend = clist.end();
3004 for (; it != bend; ++it) {
3005 Buffer * child_buf = *it;
3006 if (theBufferList().isOthersChild(&buf, child_buf)) {
3007 child_buf->setParent(0);
3011 // FIXME: should we look in other tabworkareas?
3012 // ANSWER: I don't think so. I've tested, and if the child is
3013 // open in some other window, it closes without a problem.
3014 GuiWorkArea * child_wa = workArea(*child_buf);
3016 success = closeWorkArea(child_wa, true);
3020 // In this case the child buffer is open but hidden.
3021 // It therefore should not (MUST NOT) be dirty!
3022 LATTEST(child_buf->isClean());
3023 theBufferList().release(child_buf);
3028 // goto bookmark to update bookmark pit.
3029 // FIXME: we should update only the bookmarks related to this buffer!
3030 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3031 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3032 guiApp->gotoBookmark(i+1, false, false);
3034 if (saveBufferIfNeeded(buf, false)) {
3035 buf.removeAutosaveFile();
3036 theBufferList().release(&buf);
3040 // open all children again to avoid a crash because of dangling
3041 // pointers (bug 6603)
3047 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3049 while (twa == d.currentTabWorkArea()) {
3050 twa->setCurrentIndex(twa->count() - 1);
3052 GuiWorkArea * wa = twa->currentWorkArea();
3053 Buffer & b = wa->bufferView().buffer();
3055 // We only want to close the buffer if the same buffer is not visible
3056 // in another view, and if this is not a child and if we are closing
3057 // a view (not a tabgroup).
3058 bool const close_buffer =
3059 !inOtherView(b) && !b.parent() && closing_;
3061 if (!closeWorkArea(wa, close_buffer))
3068 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3070 if (buf.isClean() || buf.paragraphs().empty())
3073 // Switch to this Buffer.
3079 if (buf.isUnnamed()) {
3080 file = from_utf8(buf.fileName().onlyFileName());
3083 FileName filename = buf.fileName();
3085 file = filename.displayName(30);
3086 exists = filename.exists();
3089 // Bring this window to top before asking questions.
3094 if (hiding && buf.isUnnamed()) {
3095 docstring const text = bformat(_("The document %1$s has not been "
3096 "saved yet.\n\nDo you want to save "
3097 "the document?"), file);
3098 ret = Alert::prompt(_("Save new document?"),
3099 text, 0, 1, _("&Save"), _("&Cancel"));
3103 docstring const text = exists ?
3104 bformat(_("The document %1$s has unsaved changes."
3105 "\n\nDo you want to save the document or "
3106 "discard the changes?"), file) :
3107 bformat(_("The document %1$s has not been saved yet."
3108 "\n\nDo you want to save the document or "
3109 "discard it entirely?"), file);
3110 docstring const title = exists ?
3111 _("Save changed document?") : _("Save document?");
3112 ret = Alert::prompt(title, text, 0, 2,
3113 _("&Save"), _("&Discard"), _("&Cancel"));
3118 if (!saveBuffer(buf))
3122 // If we crash after this we could have no autosave file
3123 // but I guess this is really improbable (Jug).
3124 // Sometimes improbable things happen:
3125 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3126 // buf.removeAutosaveFile();
3128 // revert all changes
3139 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3141 Buffer & buf = wa->bufferView().buffer();
3143 for (int i = 0; i != d.splitter_->count(); ++i) {
3144 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3145 if (wa_ && wa_ != wa)
3148 return inOtherView(buf);
3152 bool GuiView::inOtherView(Buffer & buf)
3154 QList<int> const ids = guiApp->viewIds();
3156 for (int i = 0; i != ids.size(); ++i) {
3160 if (guiApp->view(ids[i]).workArea(buf))
3167 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3169 if (!documentBufferView())
3172 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3173 Buffer * const curbuf = &documentBufferView()->buffer();
3174 int nwa = twa->count();
3175 for (int i = 0; i < nwa; ++i) {
3176 if (&workArea(i)->bufferView().buffer() == curbuf) {
3178 if (np == NEXTBUFFER)
3179 next_index = (i == nwa - 1 ? 0 : i + 1);
3181 next_index = (i == 0 ? nwa - 1 : i - 1);
3183 twa->moveTab(i, next_index);
3185 setBuffer(&workArea(next_index)->bufferView().buffer());
3193 /// make sure the document is saved
3194 static bool ensureBufferClean(Buffer * buffer)
3196 LASSERT(buffer, return false);
3197 if (buffer->isClean() && !buffer->isUnnamed())
3200 docstring const file = buffer->fileName().displayName(30);
3203 if (!buffer->isUnnamed()) {
3204 text = bformat(_("The document %1$s has unsaved "
3205 "changes.\n\nDo you want to save "
3206 "the document?"), file);
3207 title = _("Save changed document?");
3210 text = bformat(_("The document %1$s has not been "
3211 "saved yet.\n\nDo you want to save "
3212 "the document?"), file);
3213 title = _("Save new document?");
3215 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3218 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3220 return buffer->isClean() && !buffer->isUnnamed();
3224 bool GuiView::reloadBuffer(Buffer & buf)
3226 currentBufferView()->cursor().reset();
3227 Buffer::ReadStatus status = buf.reload();
3228 return status == Buffer::ReadSuccess;
3232 void GuiView::checkExternallyModifiedBuffers()
3234 BufferList::iterator bit = theBufferList().begin();
3235 BufferList::iterator const bend = theBufferList().end();
3236 for (; bit != bend; ++bit) {
3237 Buffer * buf = *bit;
3238 if (buf->fileName().exists() && buf->isChecksumModified()) {
3239 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3240 " Reload now? Any local changes will be lost."),
3241 from_utf8(buf->absFileName()));
3242 int const ret = Alert::prompt(_("Reload externally changed document?"),
3243 text, 0, 1, _("&Reload"), _("&Cancel"));
3251 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3253 Buffer * buffer = documentBufferView()
3254 ? &(documentBufferView()->buffer()) : 0;
3256 switch (cmd.action()) {
3257 case LFUN_VC_REGISTER:
3258 if (!buffer || !ensureBufferClean(buffer))
3260 if (!buffer->lyxvc().inUse()) {
3261 if (buffer->lyxvc().registrer()) {
3262 reloadBuffer(*buffer);
3263 dr.clearMessageUpdate();
3268 case LFUN_VC_RENAME:
3269 case LFUN_VC_COPY: {
3270 if (!buffer || !ensureBufferClean(buffer))
3272 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3273 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3274 // Some changes are not yet committed.
3275 // We test here and not in getStatus(), since
3276 // this test is expensive.
3278 LyXVC::CommandResult ret =
3279 buffer->lyxvc().checkIn(log);
3281 if (ret == LyXVC::ErrorCommand ||
3282 ret == LyXVC::VCSuccess)
3283 reloadBuffer(*buffer);
3284 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3285 frontend::Alert::error(
3286 _("Revision control error."),
3287 _("Document could not be checked in."));
3291 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3292 LV_VC_RENAME : LV_VC_COPY;
3293 renameBuffer(*buffer, cmd.argument(), kind);
3298 case LFUN_VC_CHECK_IN:
3299 if (!buffer || !ensureBufferClean(buffer))
3301 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3303 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3305 // Only skip reloading if the checkin was cancelled or
3306 // an error occurred before the real checkin VCS command
3307 // was executed, since the VCS might have changed the
3308 // file even if it could not checkin successfully.
3309 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3310 reloadBuffer(*buffer);
3314 case LFUN_VC_CHECK_OUT:
3315 if (!buffer || !ensureBufferClean(buffer))
3317 if (buffer->lyxvc().inUse()) {
3318 dr.setMessage(buffer->lyxvc().checkOut());
3319 reloadBuffer(*buffer);
3323 case LFUN_VC_LOCKING_TOGGLE:
3324 LASSERT(buffer, return);
3325 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3327 if (buffer->lyxvc().inUse()) {
3328 string res = buffer->lyxvc().lockingToggle();
3330 frontend::Alert::error(_("Revision control error."),
3331 _("Error when setting the locking property."));
3334 reloadBuffer(*buffer);
3339 case LFUN_VC_REVERT:
3340 LASSERT(buffer, return);
3341 if (buffer->lyxvc().revert()) {
3342 reloadBuffer(*buffer);
3343 dr.clearMessageUpdate();
3347 case LFUN_VC_UNDO_LAST:
3348 LASSERT(buffer, return);
3349 buffer->lyxvc().undoLast();
3350 reloadBuffer(*buffer);
3351 dr.clearMessageUpdate();
3354 case LFUN_VC_REPO_UPDATE:
3355 LASSERT(buffer, return);
3356 if (ensureBufferClean(buffer)) {
3357 dr.setMessage(buffer->lyxvc().repoUpdate());
3358 checkExternallyModifiedBuffers();
3362 case LFUN_VC_COMMAND: {
3363 string flag = cmd.getArg(0);
3364 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3367 if (contains(flag, 'M')) {
3368 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3371 string path = cmd.getArg(1);
3372 if (contains(path, "$$p") && buffer)
3373 path = subst(path, "$$p", buffer->filePath());
3374 LYXERR(Debug::LYXVC, "Directory: " << path);
3376 if (!pp.isReadableDirectory()) {
3377 lyxerr << _("Directory is not accessible.") << endl;
3380 support::PathChanger p(pp);
3382 string command = cmd.getArg(2);
3383 if (command.empty())
3386 command = subst(command, "$$i", buffer->absFileName());
3387 command = subst(command, "$$p", buffer->filePath());
3389 command = subst(command, "$$m", to_utf8(message));
3390 LYXERR(Debug::LYXVC, "Command: " << command);
3392 one.startscript(Systemcall::Wait, command);
3396 if (contains(flag, 'I'))
3397 buffer->markDirty();
3398 if (contains(flag, 'R'))
3399 reloadBuffer(*buffer);
3404 case LFUN_VC_COMPARE: {
3405 if (cmd.argument().empty()) {
3406 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3410 string rev1 = cmd.getArg(0);
3415 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3418 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3419 f2 = buffer->absFileName();
3421 string rev2 = cmd.getArg(1);
3425 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3429 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3430 f1 << "\n" << f2 << "\n" );
3431 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3432 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3442 void GuiView::openChildDocument(string const & fname)
3444 LASSERT(documentBufferView(), return);
3445 Buffer & buffer = documentBufferView()->buffer();
3446 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3447 documentBufferView()->saveBookmark(false);
3449 if (theBufferList().exists(filename)) {
3450 child = theBufferList().getBuffer(filename);
3453 message(bformat(_("Opening child document %1$s..."),
3454 makeDisplayPath(filename.absFileName())));
3455 child = loadDocument(filename, false);
3457 // Set the parent name of the child document.
3458 // This makes insertion of citations and references in the child work,
3459 // when the target is in the parent or another child document.
3461 child->setParent(&buffer);
3465 bool GuiView::goToFileRow(string const & argument)
3469 size_t i = argument.find_last_of(' ');
3470 if (i != string::npos) {
3471 file_name = os::internal_path(trim(argument.substr(0, i)));
3472 istringstream is(argument.substr(i + 1));
3477 if (i == string::npos) {
3478 LYXERR0("Wrong argument: " << argument);
3482 string const abstmp = package().temp_dir().absFileName();
3483 string const realtmp = package().temp_dir().realPath();
3484 // We have to use os::path_prefix_is() here, instead of
3485 // simply prefixIs(), because the file name comes from
3486 // an external application and may need case adjustment.
3487 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3488 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3489 // Needed by inverse dvi search. If it is a file
3490 // in tmpdir, call the apropriated function.
3491 // If tmpdir is a symlink, we may have the real
3492 // path passed back, so we correct for that.
3493 if (!prefixIs(file_name, abstmp))
3494 file_name = subst(file_name, realtmp, abstmp);
3495 buf = theBufferList().getBufferFromTmp(file_name);
3497 // Must replace extension of the file to be .lyx
3498 // and get full path
3499 FileName const s = fileSearch(string(),
3500 support::changeExtension(file_name, ".lyx"), "lyx");
3501 // Either change buffer or load the file
3502 if (theBufferList().exists(s))
3503 buf = theBufferList().getBuffer(s);
3504 else if (s.exists()) {
3505 buf = loadDocument(s);
3510 _("File does not exist: %1$s"),
3511 makeDisplayPath(file_name)));
3517 _("No buffer for file: %1$s."),
3518 makeDisplayPath(file_name))
3523 bool success = documentBufferView()->setCursorFromRow(row);
3525 LYXERR(Debug::LATEX,
3526 "setCursorFromRow: invalid position for row " << row);
3527 frontend::Alert::error(_("Inverse Search Failed"),
3528 _("Invalid position requested by inverse search.\n"
3529 "You may need to update the viewed document."));
3535 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3537 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3538 menu->exec(QCursor::pos());
3543 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3545 Buffer::ExportStatus const status = func(format);
3547 // the cloning operation will have produced a clone of the entire set of
3548 // documents, starting from the master. so we must delete those.
3549 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3551 busyBuffers.remove(orig);
3556 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3558 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3559 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3563 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3565 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3566 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3570 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3572 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3573 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3577 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3578 string const & argument,
3579 Buffer const * used_buffer,
3580 docstring const & msg,
3581 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3582 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3583 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3589 string format = argument;
3591 format = used_buffer->params().getDefaultOutputFormat();
3592 processing_format = format;
3594 progress_->clearMessages();
3597 #if EXPORT_in_THREAD
3599 GuiViewPrivate::busyBuffers.insert(used_buffer);
3600 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3601 if (!cloned_buffer) {
3602 Alert::error(_("Export Error"),
3603 _("Error cloning the Buffer."));
3606 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3611 setPreviewFuture(f);
3612 last_export_format = used_buffer->params().bufferFormat();
3615 // We are asynchronous, so we don't know here anything about the success
3619 // this will be run unconditionally in case EXPORT_in_THREAD
3621 Buffer::ExportStatus status;
3623 status = (used_buffer->*syncFunc)(format, true);
3624 } else if (previewFunc) {
3625 status = (used_buffer->*previewFunc)(format);
3628 handleExportStatus(gv_, status, format);
3630 return (status == Buffer::ExportSuccess
3631 || status == Buffer::PreviewSuccess);
3632 #if EXPORT_in_THREAD
3633 // the end of the else clause in that case.
3638 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3640 BufferView * bv = currentBufferView();
3641 LASSERT(bv, return);
3643 // Let the current BufferView dispatch its own actions.
3644 bv->dispatch(cmd, dr);
3645 if (dr.dispatched())
3648 // Try with the document BufferView dispatch if any.
3649 BufferView * doc_bv = documentBufferView();
3650 if (doc_bv && doc_bv != bv) {
3651 doc_bv->dispatch(cmd, dr);
3652 if (dr.dispatched())
3656 // Then let the current Cursor dispatch its own actions.
3657 bv->cursor().dispatch(cmd);
3659 // update completion. We do it here and not in
3660 // processKeySym to avoid another redraw just for a
3661 // changed inline completion
3662 if (cmd.origin() == FuncRequest::KEYBOARD) {
3663 if (cmd.action() == LFUN_SELF_INSERT
3664 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3665 updateCompletion(bv->cursor(), true, true);
3666 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3667 updateCompletion(bv->cursor(), false, true);
3669 updateCompletion(bv->cursor(), false, false);
3672 dr = bv->cursor().result();
3676 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3678 BufferView * bv = currentBufferView();
3679 // By default we won't need any update.
3680 dr.screenUpdate(Update::None);
3681 // assume cmd will be dispatched
3682 dr.dispatched(true);
3684 Buffer * doc_buffer = documentBufferView()
3685 ? &(documentBufferView()->buffer()) : 0;
3687 if (cmd.origin() == FuncRequest::TOC) {
3688 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3689 // FIXME: do we need to pass a DispatchResult object here?
3690 toc->doDispatch(bv->cursor(), cmd);
3694 string const argument = to_utf8(cmd.argument());
3696 switch(cmd.action()) {
3697 case LFUN_BUFFER_CHILD_OPEN:
3698 openChildDocument(to_utf8(cmd.argument()));
3701 case LFUN_BUFFER_IMPORT:
3702 importDocument(to_utf8(cmd.argument()));
3705 case LFUN_MASTER_BUFFER_EXPORT:
3707 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3709 case LFUN_BUFFER_EXPORT: {
3712 // GCC only sees strfwd.h when building merged
3713 if (::lyx::operator==(cmd.argument(), "custom")) {
3714 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3715 // so the following test should not be needed.
3716 // In principle, we could try to switch to such a view...
3717 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3718 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3722 string const dest = cmd.getArg(1);
3723 FileName target_dir;
3724 if (!dest.empty() && FileName::isAbsolute(dest))
3725 target_dir = FileName(support::onlyPath(dest));
3727 target_dir = doc_buffer->fileName().onlyPath();
3729 string const format = (argument.empty() || argument == "default") ?
3730 doc_buffer->params().getDefaultOutputFormat() : argument;
3732 if ((dest.empty() && doc_buffer->isUnnamed())
3733 || !target_dir.isDirWritable()) {
3734 exportBufferAs(*doc_buffer, from_utf8(format));
3737 /* TODO/Review: Is it a problem to also export the children?
3738 See the update_unincluded flag */
3739 d.asyncBufferProcessing(format,
3742 &GuiViewPrivate::exportAndDestroy,
3744 0, cmd.allowAsync());
3745 // TODO Inform user about success
3749 case LFUN_BUFFER_EXPORT_AS: {
3750 LASSERT(doc_buffer, break);
3751 docstring f = cmd.argument();
3753 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3754 exportBufferAs(*doc_buffer, f);
3758 case LFUN_BUFFER_UPDATE: {
3759 d.asyncBufferProcessing(argument,
3762 &GuiViewPrivate::compileAndDestroy,
3764 0, cmd.allowAsync());
3767 case LFUN_BUFFER_VIEW: {
3768 d.asyncBufferProcessing(argument,
3770 _("Previewing ..."),
3771 &GuiViewPrivate::previewAndDestroy,
3773 &Buffer::preview, cmd.allowAsync());
3776 case LFUN_MASTER_BUFFER_UPDATE: {
3777 d.asyncBufferProcessing(argument,
3778 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3780 &GuiViewPrivate::compileAndDestroy,
3782 0, cmd.allowAsync());
3785 case LFUN_MASTER_BUFFER_VIEW: {
3786 d.asyncBufferProcessing(argument,
3787 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3789 &GuiViewPrivate::previewAndDestroy,
3790 0, &Buffer::preview, cmd.allowAsync());
3793 case LFUN_BUFFER_SWITCH: {
3794 string const file_name = to_utf8(cmd.argument());
3795 if (!FileName::isAbsolute(file_name)) {
3797 dr.setMessage(_("Absolute filename expected."));
3801 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3804 dr.setMessage(_("Document not loaded"));
3808 // Do we open or switch to the buffer in this view ?
3809 if (workArea(*buffer)
3810 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3815 // Look for the buffer in other views
3816 QList<int> const ids = guiApp->viewIds();
3818 for (; i != ids.size(); ++i) {
3819 GuiView & gv = guiApp->view(ids[i]);
3820 if (gv.workArea(*buffer)) {
3822 gv.activateWindow();
3824 gv.setBuffer(buffer);
3829 // If necessary, open a new window as a last resort
3830 if (i == ids.size()) {
3831 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3837 case LFUN_BUFFER_NEXT:
3838 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3841 case LFUN_BUFFER_MOVE_NEXT:
3842 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3845 case LFUN_BUFFER_PREVIOUS:
3846 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3849 case LFUN_BUFFER_MOVE_PREVIOUS:
3850 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3853 case LFUN_BUFFER_CHKTEX:
3854 LASSERT(doc_buffer, break);
3855 doc_buffer->runChktex();
3858 case LFUN_COMMAND_EXECUTE: {
3859 command_execute_ = true;
3860 minibuffer_focus_ = true;
3863 case LFUN_DROP_LAYOUTS_CHOICE:
3864 d.layout_->showPopup();
3867 case LFUN_MENU_OPEN:
3868 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3869 menu->exec(QCursor::pos());
3872 case LFUN_FILE_INSERT:
3873 insertLyXFile(cmd.argument());
3876 case LFUN_FILE_INSERT_PLAINTEXT:
3877 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3878 string const fname = to_utf8(cmd.argument());
3879 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3880 dr.setMessage(_("Absolute filename expected."));
3884 FileName filename(fname);
3885 if (fname.empty()) {
3886 FileDialog dlg(qt_("Select file to insert"));
3888 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3889 QStringList(qt_("All Files (*)")));
3891 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3892 dr.setMessage(_("Canceled."));
3896 filename.set(fromqstr(result.second));
3900 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3901 bv->dispatch(new_cmd, dr);
3906 case LFUN_BUFFER_RELOAD: {
3907 LASSERT(doc_buffer, break);
3910 if (!doc_buffer->isClean()) {
3911 docstring const file =
3912 makeDisplayPath(doc_buffer->absFileName(), 20);
3913 if (doc_buffer->notifiesExternalModification()) {
3914 docstring text = _("The current version will be lost. "
3915 "Are you sure you want to load the version on disk "
3916 "of the document %1$s?");
3917 ret = Alert::prompt(_("Reload saved document?"),
3918 bformat(text, file), 1, 1,
3919 _("&Reload"), _("&Cancel"));
3921 docstring text = _("Any changes will be lost. "
3922 "Are you sure you want to revert to the saved version "
3923 "of the document %1$s?");
3924 ret = Alert::prompt(_("Revert to saved document?"),
3925 bformat(text, file), 1, 1,
3926 _("&Revert"), _("&Cancel"));
3931 doc_buffer->markClean();
3932 reloadBuffer(*doc_buffer);
3933 dr.forceBufferUpdate();
3938 case LFUN_BUFFER_WRITE:
3939 LASSERT(doc_buffer, break);
3940 saveBuffer(*doc_buffer);
3943 case LFUN_BUFFER_WRITE_AS:
3944 LASSERT(doc_buffer, break);
3945 renameBuffer(*doc_buffer, cmd.argument());
3948 case LFUN_BUFFER_WRITE_ALL: {
3949 Buffer * first = theBufferList().first();
3952 message(_("Saving all documents..."));
3953 // We cannot use a for loop as the buffer list cycles.
3956 if (!b->isClean()) {
3958 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3960 b = theBufferList().next(b);
3961 } while (b != first);
3962 dr.setMessage(_("All documents saved."));
3966 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3967 LASSERT(doc_buffer, break);
3968 doc_buffer->clearExternalModification();
3971 case LFUN_BUFFER_CLOSE:
3975 case LFUN_BUFFER_CLOSE_ALL:
3979 case LFUN_DEVEL_MODE_TOGGLE:
3980 devel_mode_ = !devel_mode_;
3982 dr.setMessage(_("Developer mode is now enabled."));
3984 dr.setMessage(_("Developer mode is now disabled."));
3987 case LFUN_TOOLBAR_TOGGLE: {
3988 string const name = cmd.getArg(0);
3989 if (GuiToolbar * t = toolbar(name))
3994 case LFUN_TOOLBAR_MOVABLE: {
3995 string const name = cmd.getArg(0);
3997 // toggle (all) toolbars movablility
3998 toolbarsMovable_ = !toolbarsMovable_;
3999 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4000 GuiToolbar * tb = toolbar(ti.name);
4001 if (tb && tb->isMovable() != toolbarsMovable_)
4002 // toggle toolbar movablity if it does not fit lock
4003 // (all) toolbars positions state silent = true, since
4004 // status bar notifications are slow
4007 if (toolbarsMovable_)
4008 dr.setMessage(_("Toolbars unlocked."));
4010 dr.setMessage(_("Toolbars locked."));
4011 } else if (GuiToolbar * t = toolbar(name)) {
4012 // toggle current toolbar movablity
4014 // update lock (all) toolbars positions
4015 updateLockToolbars();
4020 case LFUN_ICON_SIZE: {
4021 QSize size = d.iconSize(cmd.argument());
4023 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4024 size.width(), size.height()));
4028 case LFUN_DIALOG_UPDATE: {
4029 string const name = to_utf8(cmd.argument());
4030 if (name == "prefs" || name == "document")
4031 updateDialog(name, string());
4032 else if (name == "paragraph")
4033 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4034 else if (currentBufferView()) {
4035 Inset * inset = currentBufferView()->editedInset(name);
4036 // Can only update a dialog connected to an existing inset
4038 // FIXME: get rid of this indirection; GuiView ask the inset
4039 // if he is kind enough to update itself...
4040 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4041 //FIXME: pass DispatchResult here?
4042 inset->dispatch(currentBufferView()->cursor(), fr);
4048 case LFUN_DIALOG_TOGGLE: {
4049 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4050 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4051 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4055 case LFUN_DIALOG_DISCONNECT_INSET:
4056 disconnectDialog(to_utf8(cmd.argument()));
4059 case LFUN_DIALOG_HIDE: {
4060 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4064 case LFUN_DIALOG_SHOW: {
4065 string const name = cmd.getArg(0);
4066 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
4068 if (name == "character") {
4069 data = freefont2string();
4071 showDialog("character", data);
4072 } else if (name == "latexlog") {
4073 // getStatus checks that
4074 LATTEST(doc_buffer);
4075 Buffer::LogType type;
4076 string const logfile = doc_buffer->logName(&type);
4078 case Buffer::latexlog:
4081 case Buffer::buildlog:
4085 data += Lexer::quoteString(logfile);
4086 showDialog("log", data);
4087 } else if (name == "vclog") {
4088 // getStatus checks that
4089 LATTEST(doc_buffer);
4090 string const data = "vc " +
4091 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4092 showDialog("log", data);
4093 } else if (name == "symbols") {
4094 data = bv->cursor().getEncoding()->name();
4096 showDialog("symbols", data);
4098 } else if (name == "prefs" && isFullScreen()) {
4099 lfunUiToggle("fullscreen");
4100 showDialog("prefs", data);
4102 showDialog(name, data);
4107 dr.setMessage(cmd.argument());
4110 case LFUN_UI_TOGGLE: {
4111 string arg = cmd.getArg(0);
4112 if (!lfunUiToggle(arg)) {
4113 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4114 dr.setMessage(bformat(msg, from_utf8(arg)));
4116 // Make sure the keyboard focus stays in the work area.
4121 case LFUN_VIEW_SPLIT: {
4122 LASSERT(doc_buffer, break);
4123 string const orientation = cmd.getArg(0);
4124 d.splitter_->setOrientation(orientation == "vertical"
4125 ? Qt::Vertical : Qt::Horizontal);
4126 TabWorkArea * twa = addTabWorkArea();
4127 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4128 setCurrentWorkArea(wa);
4131 case LFUN_TAB_GROUP_CLOSE:
4132 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4133 closeTabWorkArea(twa);
4134 d.current_work_area_ = 0;
4135 twa = d.currentTabWorkArea();
4136 // Switch to the next GuiWorkArea in the found TabWorkArea.
4138 // Make sure the work area is up to date.
4139 setCurrentWorkArea(twa->currentWorkArea());
4141 setCurrentWorkArea(0);
4146 case LFUN_VIEW_CLOSE:
4147 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4148 closeWorkArea(twa->currentWorkArea());
4149 d.current_work_area_ = 0;
4150 twa = d.currentTabWorkArea();
4151 // Switch to the next GuiWorkArea in the found TabWorkArea.
4153 // Make sure the work area is up to date.
4154 setCurrentWorkArea(twa->currentWorkArea());
4156 setCurrentWorkArea(0);
4161 case LFUN_COMPLETION_INLINE:
4162 if (d.current_work_area_)
4163 d.current_work_area_->completer().showInline();
4166 case LFUN_COMPLETION_POPUP:
4167 if (d.current_work_area_)
4168 d.current_work_area_->completer().showPopup();
4173 if (d.current_work_area_)
4174 d.current_work_area_->completer().tab();
4177 case LFUN_COMPLETION_CANCEL:
4178 if (d.current_work_area_) {
4179 if (d.current_work_area_->completer().popupVisible())
4180 d.current_work_area_->completer().hidePopup();
4182 d.current_work_area_->completer().hideInline();
4186 case LFUN_COMPLETION_ACCEPT:
4187 if (d.current_work_area_)
4188 d.current_work_area_->completer().activate();
4191 case LFUN_BUFFER_ZOOM_IN:
4192 case LFUN_BUFFER_ZOOM_OUT:
4193 case LFUN_BUFFER_ZOOM: {
4194 if (cmd.argument().empty()) {
4195 if (cmd.action() == LFUN_BUFFER_ZOOM)
4197 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4202 if (cmd.action() == LFUN_BUFFER_ZOOM)
4203 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4204 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4205 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4207 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4210 // Actual zoom value: default zoom + fractional extra value
4211 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4212 if (zoom < static_cast<int>(zoom_min_))
4215 lyxrc.currentZoom = zoom;
4217 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4218 lyxrc.currentZoom, lyxrc.defaultZoom));
4220 // The global QPixmapCache is used in GuiPainter to cache text
4221 // painting so we must reset it.
4222 QPixmapCache::clear();
4223 guiApp->fontLoader().update();
4224 dr.screenUpdate(Update::Force | Update::FitCursor);
4228 case LFUN_VC_REGISTER:
4229 case LFUN_VC_RENAME:
4231 case LFUN_VC_CHECK_IN:
4232 case LFUN_VC_CHECK_OUT:
4233 case LFUN_VC_REPO_UPDATE:
4234 case LFUN_VC_LOCKING_TOGGLE:
4235 case LFUN_VC_REVERT:
4236 case LFUN_VC_UNDO_LAST:
4237 case LFUN_VC_COMMAND:
4238 case LFUN_VC_COMPARE:
4239 dispatchVC(cmd, dr);
4242 case LFUN_SERVER_GOTO_FILE_ROW:
4243 if(goToFileRow(to_utf8(cmd.argument())))
4244 dr.screenUpdate(Update::Force | Update::FitCursor);
4247 case LFUN_LYX_ACTIVATE:
4251 case LFUN_FORWARD_SEARCH: {
4252 // it seems safe to assume we have a document buffer, since
4253 // getStatus wants one.
4254 LATTEST(doc_buffer);
4255 Buffer const * doc_master = doc_buffer->masterBuffer();
4256 FileName const path(doc_master->temppath());
4257 string const texname = doc_master->isChild(doc_buffer)
4258 ? DocFileName(changeExtension(
4259 doc_buffer->absFileName(),
4260 "tex")).mangledFileName()
4261 : doc_buffer->latexName();
4262 string const fulltexname =
4263 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4264 string const mastername =
4265 removeExtension(doc_master->latexName());
4266 FileName const dviname(addName(path.absFileName(),
4267 addExtension(mastername, "dvi")));
4268 FileName const pdfname(addName(path.absFileName(),
4269 addExtension(mastername, "pdf")));
4270 bool const have_dvi = dviname.exists();
4271 bool const have_pdf = pdfname.exists();
4272 if (!have_dvi && !have_pdf) {
4273 dr.setMessage(_("Please, preview the document first."));
4276 string outname = dviname.onlyFileName();
4277 string command = lyxrc.forward_search_dvi;
4278 if (!have_dvi || (have_pdf &&
4279 pdfname.lastModified() > dviname.lastModified())) {
4280 outname = pdfname.onlyFileName();
4281 command = lyxrc.forward_search_pdf;
4284 DocIterator cur = bv->cursor();
4285 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4286 LYXERR(Debug::ACTION, "Forward search: row:" << row
4288 if (row == -1 || command.empty()) {
4289 dr.setMessage(_("Couldn't proceed."));
4292 string texrow = convert<string>(row);
4294 command = subst(command, "$$n", texrow);
4295 command = subst(command, "$$f", fulltexname);
4296 command = subst(command, "$$t", texname);
4297 command = subst(command, "$$o", outname);
4299 PathChanger p(path);
4301 one.startscript(Systemcall::DontWait, command);
4305 case LFUN_SPELLING_CONTINUOUSLY:
4306 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4307 dr.screenUpdate(Update::Force);
4311 // The LFUN must be for one of BufferView, Buffer or Cursor;
4313 dispatchToBufferView(cmd, dr);
4317 // Part of automatic menu appearance feature.
4318 if (isFullScreen()) {
4319 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4323 // Need to update bv because many LFUNs here might have destroyed it
4324 bv = currentBufferView();
4326 // Clear non-empty selections
4327 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4329 Cursor & cur = bv->cursor();
4330 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4331 cur.clearSelection();
4337 bool GuiView::lfunUiToggle(string const & ui_component)
4339 if (ui_component == "scrollbar") {
4340 // hide() is of no help
4341 if (d.current_work_area_->verticalScrollBarPolicy() ==
4342 Qt::ScrollBarAlwaysOff)
4344 d.current_work_area_->setVerticalScrollBarPolicy(
4345 Qt::ScrollBarAsNeeded);
4347 d.current_work_area_->setVerticalScrollBarPolicy(
4348 Qt::ScrollBarAlwaysOff);
4349 } else if (ui_component == "statusbar") {
4350 statusBar()->setVisible(!statusBar()->isVisible());
4351 } else if (ui_component == "menubar") {
4352 menuBar()->setVisible(!menuBar()->isVisible());
4354 if (ui_component == "frame") {
4356 getContentsMargins(&l, &t, &r, &b);
4357 //are the frames in default state?
4358 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4360 setContentsMargins(-2, -2, -2, -2);
4362 setContentsMargins(0, 0, 0, 0);
4365 if (ui_component == "fullscreen") {
4373 void GuiView::toggleFullScreen()
4375 if (isFullScreen()) {
4376 for (int i = 0; i != d.splitter_->count(); ++i)
4377 d.tabWorkArea(i)->setFullScreen(false);
4378 setContentsMargins(0, 0, 0, 0);
4379 setWindowState(windowState() ^ Qt::WindowFullScreen);
4382 statusBar()->show();
4385 hideDialogs("prefs", 0);
4386 for (int i = 0; i != d.splitter_->count(); ++i)
4387 d.tabWorkArea(i)->setFullScreen(true);
4388 setContentsMargins(-2, -2, -2, -2);
4390 setWindowState(windowState() ^ Qt::WindowFullScreen);
4391 if (lyxrc.full_screen_statusbar)
4392 statusBar()->hide();
4393 if (lyxrc.full_screen_menubar)
4395 if (lyxrc.full_screen_toolbars) {
4396 ToolbarMap::iterator end = d.toolbars_.end();
4397 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4402 // give dialogs like the TOC a chance to adapt
4407 Buffer const * GuiView::updateInset(Inset const * inset)
4412 Buffer const * inset_buffer = &(inset->buffer());
4414 for (int i = 0; i != d.splitter_->count(); ++i) {
4415 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4418 Buffer const * buffer = &(wa->bufferView().buffer());
4419 if (inset_buffer == buffer)
4420 wa->scheduleRedraw(true);
4422 return inset_buffer;
4426 void GuiView::restartCaret()
4428 /* When we move around, or type, it's nice to be able to see
4429 * the caret immediately after the keypress.
4431 if (d.current_work_area_)
4432 d.current_work_area_->startBlinkingCaret();
4434 // Take this occasion to update the other GUI elements.
4440 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4442 if (d.current_work_area_)
4443 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4448 // This list should be kept in sync with the list of insets in
4449 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4450 // dialog should have the same name as the inset.
4451 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4452 // docs in LyXAction.cpp.
4454 char const * const dialognames[] = {
4456 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4457 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4458 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4459 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4460 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4461 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4462 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4463 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4465 char const * const * const end_dialognames =
4466 dialognames + (sizeof(dialognames) / sizeof(char *));
4470 cmpCStr(char const * name) : name_(name) {}
4471 bool operator()(char const * other) {
4472 return strcmp(other, name_) == 0;
4479 bool isValidName(string const & name)
4481 return find_if(dialognames, end_dialognames,
4482 cmpCStr(name.c_str())) != end_dialognames;
4488 void GuiView::resetDialogs()
4490 // Make sure that no LFUN uses any GuiView.
4491 guiApp->setCurrentView(0);
4495 constructToolbars();
4496 guiApp->menus().fillMenuBar(menuBar(), this, false);
4497 d.layout_->updateContents(true);
4498 // Now update controls with current buffer.
4499 guiApp->setCurrentView(this);
4505 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4507 if (!isValidName(name))
4510 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4512 if (it != d.dialogs_.end()) {
4514 it->second->hideView();
4515 return it->second.get();
4518 Dialog * dialog = build(name);
4519 d.dialogs_[name].reset(dialog);
4520 if (lyxrc.allow_geometry_session)
4521 dialog->restoreSession();
4528 void GuiView::showDialog(string const & name, string const & data,
4531 triggerShowDialog(toqstr(name), toqstr(data), inset);
4535 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4541 const string name = fromqstr(qname);
4542 const string data = fromqstr(qdata);
4546 Dialog * dialog = findOrBuild(name, false);
4548 bool const visible = dialog->isVisibleView();
4549 dialog->showData(data);
4550 if (inset && currentBufferView())
4551 currentBufferView()->editInset(name, inset);
4552 // We only set the focus to the new dialog if it was not yet
4553 // visible in order not to change the existing previous behaviour
4555 // activateWindow is needed for floating dockviews
4556 dialog->asQWidget()->raise();
4557 dialog->asQWidget()->activateWindow();
4558 dialog->asQWidget()->setFocus();
4562 catch (ExceptionMessage const & ex) {
4570 bool GuiView::isDialogVisible(string const & name) const
4572 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4573 if (it == d.dialogs_.end())
4575 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4579 void GuiView::hideDialog(string const & name, Inset * inset)
4581 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4582 if (it == d.dialogs_.end())
4586 if (!currentBufferView())
4588 if (inset != currentBufferView()->editedInset(name))
4592 Dialog * const dialog = it->second.get();
4593 if (dialog->isVisibleView())
4595 if (currentBufferView())
4596 currentBufferView()->editInset(name, 0);
4600 void GuiView::disconnectDialog(string const & name)
4602 if (!isValidName(name))
4604 if (currentBufferView())
4605 currentBufferView()->editInset(name, 0);
4609 void GuiView::hideAll() const
4611 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4612 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4614 for(; it != end; ++it)
4615 it->second->hideView();
4619 void GuiView::updateDialogs()
4621 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4622 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4624 for(; it != end; ++it) {
4625 Dialog * dialog = it->second.get();
4627 if (dialog->needBufferOpen() && !documentBufferView())
4628 hideDialog(fromqstr(dialog->name()), 0);
4629 else if (dialog->isVisibleView())
4630 dialog->checkStatus();
4637 Dialog * createDialog(GuiView & lv, string const & name);
4639 // will be replaced by a proper factory...
4640 Dialog * createGuiAbout(GuiView & lv);
4641 Dialog * createGuiBibtex(GuiView & lv);
4642 Dialog * createGuiChanges(GuiView & lv);
4643 Dialog * createGuiCharacter(GuiView & lv);
4644 Dialog * createGuiCitation(GuiView & lv);
4645 Dialog * createGuiCompare(GuiView & lv);
4646 Dialog * createGuiCompareHistory(GuiView & lv);
4647 Dialog * createGuiDelimiter(GuiView & lv);
4648 Dialog * createGuiDocument(GuiView & lv);
4649 Dialog * createGuiErrorList(GuiView & lv);
4650 Dialog * createGuiExternal(GuiView & lv);
4651 Dialog * createGuiGraphics(GuiView & lv);
4652 Dialog * createGuiInclude(GuiView & lv);
4653 Dialog * createGuiIndex(GuiView & lv);
4654 Dialog * createGuiListings(GuiView & lv);
4655 Dialog * createGuiLog(GuiView & lv);
4656 Dialog * createGuiMathMatrix(GuiView & lv);
4657 Dialog * createGuiNote(GuiView & lv);
4658 Dialog * createGuiParagraph(GuiView & lv);
4659 Dialog * createGuiPhantom(GuiView & lv);
4660 Dialog * createGuiPreferences(GuiView & lv);
4661 Dialog * createGuiPrint(GuiView & lv);
4662 Dialog * createGuiPrintindex(GuiView & lv);
4663 Dialog * createGuiRef(GuiView & lv);
4664 Dialog * createGuiSearch(GuiView & lv);
4665 Dialog * createGuiSearchAdv(GuiView & lv);
4666 Dialog * createGuiSendTo(GuiView & lv);
4667 Dialog * createGuiShowFile(GuiView & lv);
4668 Dialog * createGuiSpellchecker(GuiView & lv);
4669 Dialog * createGuiSymbols(GuiView & lv);
4670 Dialog * createGuiTabularCreate(GuiView & lv);
4671 Dialog * createGuiTexInfo(GuiView & lv);
4672 Dialog * createGuiToc(GuiView & lv);
4673 Dialog * createGuiThesaurus(GuiView & lv);
4674 Dialog * createGuiViewSource(GuiView & lv);
4675 Dialog * createGuiWrap(GuiView & lv);
4676 Dialog * createGuiProgressView(GuiView & lv);
4680 Dialog * GuiView::build(string const & name)
4682 LASSERT(isValidName(name), return 0);
4684 Dialog * dialog = createDialog(*this, name);
4688 if (name == "aboutlyx")
4689 return createGuiAbout(*this);
4690 if (name == "bibtex")
4691 return createGuiBibtex(*this);
4692 if (name == "changes")
4693 return createGuiChanges(*this);
4694 if (name == "character")
4695 return createGuiCharacter(*this);
4696 if (name == "citation")
4697 return createGuiCitation(*this);
4698 if (name == "compare")
4699 return createGuiCompare(*this);
4700 if (name == "comparehistory")
4701 return createGuiCompareHistory(*this);
4702 if (name == "document")
4703 return createGuiDocument(*this);
4704 if (name == "errorlist")
4705 return createGuiErrorList(*this);
4706 if (name == "external")
4707 return createGuiExternal(*this);
4709 return createGuiShowFile(*this);
4710 if (name == "findreplace")
4711 return createGuiSearch(*this);
4712 if (name == "findreplaceadv")
4713 return createGuiSearchAdv(*this);
4714 if (name == "graphics")
4715 return createGuiGraphics(*this);
4716 if (name == "include")
4717 return createGuiInclude(*this);
4718 if (name == "index")
4719 return createGuiIndex(*this);
4720 if (name == "index_print")
4721 return createGuiPrintindex(*this);
4722 if (name == "listings")
4723 return createGuiListings(*this);
4725 return createGuiLog(*this);
4726 if (name == "mathdelimiter")
4727 return createGuiDelimiter(*this);
4728 if (name == "mathmatrix")
4729 return createGuiMathMatrix(*this);
4731 return createGuiNote(*this);
4732 if (name == "paragraph")
4733 return createGuiParagraph(*this);
4734 if (name == "phantom")
4735 return createGuiPhantom(*this);
4736 if (name == "prefs")
4737 return createGuiPreferences(*this);
4739 return createGuiRef(*this);
4740 if (name == "sendto")
4741 return createGuiSendTo(*this);
4742 if (name == "spellchecker")
4743 return createGuiSpellchecker(*this);
4744 if (name == "symbols")
4745 return createGuiSymbols(*this);
4746 if (name == "tabularcreate")
4747 return createGuiTabularCreate(*this);
4748 if (name == "texinfo")
4749 return createGuiTexInfo(*this);
4750 if (name == "thesaurus")
4751 return createGuiThesaurus(*this);
4753 return createGuiToc(*this);
4754 if (name == "view-source")
4755 return createGuiViewSource(*this);
4757 return createGuiWrap(*this);
4758 if (name == "progress")
4759 return createGuiProgressView(*this);
4765 SEMenu::SEMenu(QWidget * parent)
4767 QAction * action = addAction(qt_("Disable Shell Escape"));
4768 connect(action, SIGNAL(triggered()),
4769 parent, SLOT(disableShellEscape()));
4773 } // namespace frontend
4776 #include "moc_GuiView.cpp"