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,
488 Buffer * buffer, string const & format);
489 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
490 Buffer * buffer, string const & format);
491 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
492 Buffer * buffer, string const & format);
493 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
496 static Buffer::ExportStatus runAndDestroy(const T& func,
497 Buffer const * orig, Buffer * buffer, string const & format);
499 // TODO syncFunc/previewFunc: use bind
500 bool asyncBufferProcessing(string const & argument,
501 Buffer const * used_buffer,
502 docstring const & msg,
503 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
504 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
505 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
508 QVector<GuiWorkArea*> guiWorkAreas();
511 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
514 GuiView::GuiView(int id)
515 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
516 command_execute_(false), minibuffer_focus_(false), devel_mode_(false)
518 connect(this, SIGNAL(bufferViewChanged()),
519 this, SLOT(onBufferViewChanged()));
521 // GuiToolbars *must* be initialised before the menu bar.
522 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
525 // set ourself as the current view. This is needed for the menu bar
526 // filling, at least for the static special menu item on Mac. Otherwise
527 // they are greyed out.
528 guiApp->setCurrentView(this);
530 // Fill up the menu bar.
531 guiApp->menus().fillMenuBar(menuBar(), this, true);
533 setCentralWidget(d.stack_widget_);
535 // Start autosave timer
536 if (lyxrc.autosave) {
537 // The connection is closed when this is destroyed.
538 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
539 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
540 d.autosave_timeout_.start();
542 connect(&d.statusbar_timer_, SIGNAL(timeout()),
543 this, SLOT(clearMessage()));
545 // We don't want to keep the window in memory if it is closed.
546 setAttribute(Qt::WA_DeleteOnClose, true);
548 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
549 // QIcon::fromTheme was introduced in Qt 4.6
550 #if (QT_VERSION >= 0x040600)
551 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
552 // since the icon is provided in the application bundle. We use a themed
553 // version when available and use the bundled one as fallback.
554 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
556 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
562 // use tabbed dock area for multiple docks
563 // (such as "source" and "messages")
564 setDockOptions(QMainWindow::ForceTabbedDocks);
567 setAcceptDrops(true);
569 // add busy indicator to statusbar
570 QLabel * busylabel = new QLabel(statusBar());
571 statusBar()->addPermanentWidget(busylabel);
572 search_mode mode = theGuiApp()->imageSearchMode();
573 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
574 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
575 busylabel->setMovie(busyanim);
579 connect(&d.processing_thread_watcher_, SIGNAL(started()),
580 busylabel, SLOT(show()));
581 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
582 busylabel, SLOT(hide()));
584 QFontMetrics const fm(statusBar()->fontMetrics());
585 int const iconheight = max(int(d.normalIconSize), fm.height());
586 QSize const iconsize(iconheight, iconheight);
588 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
589 shell_escape_ = new QLabel(statusBar());
590 shell_escape_->setPixmap(shellescape);
591 shell_escape_->setScaledContents(true);
592 shell_escape_->setAlignment(Qt::AlignCenter);
593 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
594 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
595 "external commands for this document. "
596 "Right click to change."));
597 SEMenu * menu = new SEMenu(this);
598 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
599 menu, SLOT(showMenu(QPoint)));
600 shell_escape_->hide();
601 statusBar()->addPermanentWidget(shell_escape_);
603 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
604 read_only_ = new QLabel(statusBar());
605 read_only_->setPixmap(readonly);
606 read_only_->setScaledContents(true);
607 read_only_->setAlignment(Qt::AlignCenter);
609 statusBar()->addPermanentWidget(read_only_);
611 version_control_ = new QLabel(statusBar());
612 version_control_->setAlignment(Qt::AlignCenter);
613 version_control_->setFrameStyle(QFrame::StyledPanel);
614 version_control_->hide();
615 statusBar()->addPermanentWidget(version_control_);
617 statusBar()->setSizeGripEnabled(true);
620 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
621 SLOT(autoSaveThreadFinished()));
623 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
624 SLOT(processingThreadStarted()));
625 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
626 SLOT(processingThreadFinished()));
628 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
629 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
631 // set custom application bars context menu, e.g. tool bar and menu bar
632 setContextMenuPolicy(Qt::CustomContextMenu);
633 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
634 SLOT(toolBarPopup(const QPoint &)));
636 // Forbid too small unresizable window because it can happen
637 // with some window manager under X11.
638 setMinimumSize(300, 200);
640 if (lyxrc.allow_geometry_session) {
641 // Now take care of session management.
646 // no session handling, default to a sane size.
647 setGeometry(50, 50, 690, 510);
650 // clear session data if any.
652 settings.remove("views");
662 void GuiView::disableShellEscape()
664 BufferView * bv = documentBufferView();
667 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
668 bv->buffer().params().shell_escape = false;
669 bv->processUpdateFlags(Update::Force);
673 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
675 QVector<GuiWorkArea*> areas;
676 for (int i = 0; i < tabWorkAreaCount(); i++) {
677 TabWorkArea* ta = tabWorkArea(i);
678 for (int u = 0; u < ta->count(); u++) {
679 areas << ta->workArea(u);
685 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
686 string const & format)
688 docstring const fmt = theFormats().prettyName(format);
691 case Buffer::ExportSuccess:
692 msg = bformat(_("Successful export to format: %1$s"), fmt);
694 case Buffer::ExportCancel:
695 msg = _("Document export cancelled.");
697 case Buffer::ExportError:
698 case Buffer::ExportNoPathToFormat:
699 case Buffer::ExportTexPathHasSpaces:
700 case Buffer::ExportConverterError:
701 msg = bformat(_("Error while exporting format: %1$s"), fmt);
703 case Buffer::PreviewSuccess:
704 msg = bformat(_("Successful preview of format: %1$s"), fmt);
706 case Buffer::PreviewError:
707 msg = bformat(_("Error while previewing format: %1$s"), fmt);
709 case Buffer::ExportKilled:
710 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
717 void GuiView::processingThreadStarted()
722 void GuiView::processingThreadFinished()
724 QFutureWatcher<Buffer::ExportStatus> const * watcher =
725 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
727 Buffer::ExportStatus const status = watcher->result();
728 handleExportStatus(this, status, d.processing_format);
731 BufferView const * const bv = currentBufferView();
732 if (bv && !bv->buffer().errorList("Export").empty()) {
737 bool const error = (status != Buffer::ExportSuccess &&
738 status != Buffer::PreviewSuccess &&
739 status != Buffer::ExportCancel);
741 ErrorList & el = bv->buffer().errorList(d.last_export_format);
742 // at this point, we do not know if buffer-view or
743 // master-buffer-view was called. If there was an export error,
744 // and the current buffer's error log is empty, we guess that
745 // it must be master-buffer-view that was called so we set
747 errors(d.last_export_format, el.empty());
752 void GuiView::autoSaveThreadFinished()
754 QFutureWatcher<docstring> const * watcher =
755 static_cast<QFutureWatcher<docstring> const *>(sender());
756 message(watcher->result());
761 void GuiView::saveLayout() const
764 settings.setValue("zoom_ratio", zoom_ratio_);
765 settings.setValue("devel_mode", devel_mode_);
766 settings.beginGroup("views");
767 settings.beginGroup(QString::number(id_));
768 #if defined(Q_WS_X11) || defined(QPA_XCB)
769 settings.setValue("pos", pos());
770 settings.setValue("size", size());
772 settings.setValue("geometry", saveGeometry());
774 settings.setValue("layout", saveState(0));
775 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
779 void GuiView::saveUISettings() const
783 // Save the toolbar private states
784 ToolbarMap::iterator end = d.toolbars_.end();
785 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
786 it->second->saveSession(settings);
787 // Now take care of all other dialogs
788 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
789 for (; it!= d.dialogs_.end(); ++it)
790 it->second->saveSession(settings);
794 bool GuiView::restoreLayout()
797 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
798 // Actual zoom value: default zoom + fractional offset
799 int zoom = lyxrc.defaultZoom * zoom_ratio_;
800 if (zoom < static_cast<int>(zoom_min_))
802 lyxrc.currentZoom = zoom;
803 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
804 settings.beginGroup("views");
805 settings.beginGroup(QString::number(id_));
806 QString const icon_key = "icon_size";
807 if (!settings.contains(icon_key))
810 //code below is skipped when when ~/.config/LyX is (re)created
811 setIconSize(d.iconSize(settings.value(icon_key).toString()));
813 #if defined(Q_WS_X11) || defined(QPA_XCB)
814 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
815 QSize size = settings.value("size", QSize(690, 510)).toSize();
819 // Work-around for bug #6034: the window ends up in an undetermined
820 // state when trying to restore a maximized window when it is
821 // already maximized.
822 if (!(windowState() & Qt::WindowMaximized))
823 if (!restoreGeometry(settings.value("geometry").toByteArray()))
824 setGeometry(50, 50, 690, 510);
826 // Make sure layout is correctly oriented.
827 setLayoutDirection(qApp->layoutDirection());
829 // Allow the toc and view-source dock widget to be restored if needed.
831 if ((dialog = findOrBuild("toc", true)))
832 // see bug 5082. At least setup title and enabled state.
833 // Visibility will be adjusted by restoreState below.
834 dialog->prepareView();
835 if ((dialog = findOrBuild("view-source", true)))
836 dialog->prepareView();
837 if ((dialog = findOrBuild("progress", true)))
838 dialog->prepareView();
840 if (!restoreState(settings.value("layout").toByteArray(), 0))
843 // init the toolbars that have not been restored
844 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
845 Toolbars::Infos::iterator end = guiApp->toolbars().end();
846 for (; cit != end; ++cit) {
847 GuiToolbar * tb = toolbar(cit->name);
848 if (tb && !tb->isRestored())
849 initToolbar(cit->name);
852 // update lock (all) toolbars positions
853 updateLockToolbars();
860 GuiToolbar * GuiView::toolbar(string const & name)
862 ToolbarMap::iterator it = d.toolbars_.find(name);
863 if (it != d.toolbars_.end())
866 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
871 void GuiView::updateLockToolbars()
873 toolbarsMovable_ = false;
874 for (ToolbarInfo const & info : guiApp->toolbars()) {
875 GuiToolbar * tb = toolbar(info.name);
876 if (tb && tb->isMovable())
877 toolbarsMovable_ = true;
882 void GuiView::constructToolbars()
884 ToolbarMap::iterator it = d.toolbars_.begin();
885 for (; it != d.toolbars_.end(); ++it)
889 // I don't like doing this here, but the standard toolbar
890 // destroys this object when it's destroyed itself (vfr)
891 d.layout_ = new LayoutBox(*this);
892 d.stack_widget_->addWidget(d.layout_);
893 d.layout_->move(0,0);
895 // extracts the toolbars from the backend
896 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
897 Toolbars::Infos::iterator end = guiApp->toolbars().end();
898 for (; cit != end; ++cit)
899 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
903 void GuiView::initToolbars()
905 // extracts the toolbars from the backend
906 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
907 Toolbars::Infos::iterator end = guiApp->toolbars().end();
908 for (; cit != end; ++cit)
909 initToolbar(cit->name);
913 void GuiView::initToolbar(string const & name)
915 GuiToolbar * tb = toolbar(name);
918 int const visibility = guiApp->toolbars().defaultVisibility(name);
919 bool newline = !(visibility & Toolbars::SAMEROW);
920 tb->setVisible(false);
921 tb->setVisibility(visibility);
923 if (visibility & Toolbars::TOP) {
925 addToolBarBreak(Qt::TopToolBarArea);
926 addToolBar(Qt::TopToolBarArea, tb);
929 if (visibility & Toolbars::BOTTOM) {
931 addToolBarBreak(Qt::BottomToolBarArea);
932 addToolBar(Qt::BottomToolBarArea, tb);
935 if (visibility & Toolbars::LEFT) {
937 addToolBarBreak(Qt::LeftToolBarArea);
938 addToolBar(Qt::LeftToolBarArea, tb);
941 if (visibility & Toolbars::RIGHT) {
943 addToolBarBreak(Qt::RightToolBarArea);
944 addToolBar(Qt::RightToolBarArea, tb);
947 if (visibility & Toolbars::ON)
948 tb->setVisible(true);
950 tb->setMovable(true);
954 TocModels & GuiView::tocModels()
956 return d.toc_models_;
960 void GuiView::setFocus()
962 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
963 QMainWindow::setFocus();
967 bool GuiView::hasFocus() const
969 if (currentWorkArea())
970 return currentWorkArea()->hasFocus();
971 if (currentMainWorkArea())
972 return currentMainWorkArea()->hasFocus();
973 return d.bg_widget_->hasFocus();
977 void GuiView::focusInEvent(QFocusEvent * e)
979 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
980 QMainWindow::focusInEvent(e);
981 // Make sure guiApp points to the correct view.
982 guiApp->setCurrentView(this);
983 if (currentWorkArea())
984 currentWorkArea()->setFocus();
985 else if (currentMainWorkArea())
986 currentMainWorkArea()->setFocus();
988 d.bg_widget_->setFocus();
992 void GuiView::showEvent(QShowEvent * e)
994 LYXERR(Debug::GUI, "Passed Geometry "
995 << size().height() << "x" << size().width()
996 << "+" << pos().x() << "+" << pos().y());
998 if (d.splitter_->count() == 0)
999 // No work area, switch to the background widget.
1003 QMainWindow::showEvent(e);
1007 bool GuiView::closeScheduled()
1014 bool GuiView::prepareAllBuffersForLogout()
1016 Buffer * first = theBufferList().first();
1020 // First, iterate over all buffers and ask the users if unsaved
1021 // changes should be saved.
1022 // We cannot use a for loop as the buffer list cycles.
1025 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1027 b = theBufferList().next(b);
1028 } while (b != first);
1030 // Next, save session state
1031 // When a view/window was closed before without quitting LyX, there
1032 // are already entries in the lastOpened list.
1033 theSession().lastOpened().clear();
1040 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1041 ** is responsibility of the container (e.g., dialog)
1043 void GuiView::closeEvent(QCloseEvent * close_event)
1045 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1047 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1048 Alert::warning(_("Exit LyX"),
1049 _("LyX could not be closed because documents are being processed by LyX."));
1050 close_event->setAccepted(false);
1054 // If the user pressed the x (so we didn't call closeView
1055 // programmatically), we want to clear all existing entries.
1057 theSession().lastOpened().clear();
1062 // it can happen that this event arrives without selecting the view,
1063 // e.g. when clicking the close button on a background window.
1065 if (!closeWorkAreaAll()) {
1067 close_event->ignore();
1071 // Make sure that nothing will use this to be closed View.
1072 guiApp->unregisterView(this);
1074 if (isFullScreen()) {
1075 // Switch off fullscreen before closing.
1080 // Make sure the timer time out will not trigger a statusbar update.
1081 d.statusbar_timer_.stop();
1083 // Saving fullscreen requires additional tweaks in the toolbar code.
1084 // It wouldn't also work under linux natively.
1085 if (lyxrc.allow_geometry_session) {
1090 close_event->accept();
1094 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1096 if (event->mimeData()->hasUrls())
1098 /// \todo Ask lyx-devel is this is enough:
1099 /// if (event->mimeData()->hasFormat("text/plain"))
1100 /// event->acceptProposedAction();
1104 void GuiView::dropEvent(QDropEvent * event)
1106 QList<QUrl> files = event->mimeData()->urls();
1107 if (files.isEmpty())
1110 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1111 for (int i = 0; i != files.size(); ++i) {
1112 string const file = os::internal_path(fromqstr(
1113 files.at(i).toLocalFile()));
1117 string const ext = support::getExtension(file);
1118 vector<const Format *> found_formats;
1120 // Find all formats that have the correct extension.
1121 vector<const Format *> const & import_formats
1122 = theConverters().importableFormats();
1123 vector<const Format *>::const_iterator it = import_formats.begin();
1124 for (; it != import_formats.end(); ++it)
1125 if ((*it)->hasExtension(ext))
1126 found_formats.push_back(*it);
1129 if (found_formats.size() >= 1) {
1130 if (found_formats.size() > 1) {
1131 //FIXME: show a dialog to choose the correct importable format
1132 LYXERR(Debug::FILES,
1133 "Multiple importable formats found, selecting first");
1135 string const arg = found_formats[0]->name() + " " + file;
1136 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1139 //FIXME: do we have to explicitly check whether it's a lyx file?
1140 LYXERR(Debug::FILES,
1141 "No formats found, trying to open it as a lyx file");
1142 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1144 // add the functions to the queue
1145 guiApp->addToFuncRequestQueue(cmd);
1148 // now process the collected functions. We perform the events
1149 // asynchronously. This prevents potential problems in case the
1150 // BufferView is closed within an event.
1151 guiApp->processFuncRequestQueueAsync();
1155 void GuiView::message(docstring const & str)
1157 if (ForkedProcess::iAmAChild())
1160 // call is moved to GUI-thread by GuiProgress
1161 d.progress_->appendMessage(toqstr(str));
1165 void GuiView::clearMessageText()
1167 message(docstring());
1171 void GuiView::updateStatusBarMessage(QString const & str)
1173 statusBar()->showMessage(str);
1174 d.statusbar_timer_.stop();
1175 d.statusbar_timer_.start(3000);
1179 void GuiView::clearMessage()
1181 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1182 // the hasFocus function mostly returns false, even if the focus is on
1183 // a workarea in this view.
1187 d.statusbar_timer_.stop();
1191 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1193 if (wa != d.current_work_area_
1194 || wa->bufferView().buffer().isInternal())
1196 Buffer const & buf = wa->bufferView().buffer();
1197 // Set the windows title
1198 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1199 if (buf.notifiesExternalModification()) {
1200 title = bformat(_("%1$s (modified externally)"), title);
1201 // If the external modification status has changed, then maybe the status of
1202 // buffer-save has changed too.
1206 title += from_ascii(" - LyX");
1208 setWindowTitle(toqstr(title));
1209 // Sets the path for the window: this is used by OSX to
1210 // allow a context click on the title bar showing a menu
1211 // with the path up to the file
1212 setWindowFilePath(toqstr(buf.absFileName()));
1213 // Tell Qt whether the current document is changed
1214 setWindowModified(!buf.isClean());
1216 if (buf.params().shell_escape)
1217 shell_escape_->show();
1219 shell_escape_->hide();
1221 if (buf.hasReadonlyFlag())
1226 if (buf.lyxvc().inUse()) {
1227 version_control_->show();
1228 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1230 version_control_->hide();
1234 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1236 if (d.current_work_area_)
1237 // disconnect the current work area from all slots
1238 QObject::disconnect(d.current_work_area_, 0, this, 0);
1240 disconnectBufferView();
1241 connectBufferView(wa->bufferView());
1242 connectBuffer(wa->bufferView().buffer());
1243 d.current_work_area_ = wa;
1244 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1245 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1246 QObject::connect(wa, SIGNAL(busy(bool)),
1247 this, SLOT(setBusy(bool)));
1248 // connection of a signal to a signal
1249 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1250 this, SIGNAL(bufferViewChanged()));
1251 Q_EMIT updateWindowTitle(wa);
1252 Q_EMIT bufferViewChanged();
1256 void GuiView::onBufferViewChanged()
1259 // Buffer-dependent dialogs must be updated. This is done here because
1260 // some dialogs require buffer()->text.
1265 void GuiView::on_lastWorkAreaRemoved()
1268 // We already are in a close event. Nothing more to do.
1271 if (d.splitter_->count() > 1)
1272 // We have a splitter so don't close anything.
1275 // Reset and updates the dialogs.
1276 Q_EMIT bufferViewChanged();
1281 if (lyxrc.open_buffers_in_tabs)
1282 // Nothing more to do, the window should stay open.
1285 if (guiApp->viewIds().size() > 1) {
1291 // On Mac we also close the last window because the application stay
1292 // resident in memory. On other platforms we don't close the last
1293 // window because this would quit the application.
1299 void GuiView::updateStatusBar()
1301 // let the user see the explicit message
1302 if (d.statusbar_timer_.isActive())
1309 void GuiView::showMessage()
1313 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1314 if (msg.isEmpty()) {
1315 BufferView const * bv = currentBufferView();
1317 msg = toqstr(bv->cursor().currentState(devel_mode_));
1319 msg = qt_("Welcome to LyX!");
1321 statusBar()->showMessage(msg);
1325 bool GuiView::event(QEvent * e)
1329 // Useful debug code:
1330 //case QEvent::ActivationChange:
1331 //case QEvent::WindowDeactivate:
1332 //case QEvent::Paint:
1333 //case QEvent::Enter:
1334 //case QEvent::Leave:
1335 //case QEvent::HoverEnter:
1336 //case QEvent::HoverLeave:
1337 //case QEvent::HoverMove:
1338 //case QEvent::StatusTip:
1339 //case QEvent::DragEnter:
1340 //case QEvent::DragLeave:
1341 //case QEvent::Drop:
1344 case QEvent::WindowActivate: {
1345 GuiView * old_view = guiApp->currentView();
1346 if (this == old_view) {
1348 return QMainWindow::event(e);
1350 if (old_view && old_view->currentBufferView()) {
1351 // save current selection to the selection buffer to allow
1352 // middle-button paste in this window.
1353 cap::saveSelection(old_view->currentBufferView()->cursor());
1355 guiApp->setCurrentView(this);
1356 if (d.current_work_area_)
1357 on_currentWorkAreaChanged(d.current_work_area_);
1361 return QMainWindow::event(e);
1364 case QEvent::ShortcutOverride: {
1366 if (isFullScreen() && menuBar()->isHidden()) {
1367 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1368 // FIXME: we should also try to detect special LyX shortcut such as
1369 // Alt-P and Alt-M. Right now there is a hack in
1370 // GuiWorkArea::processKeySym() that hides again the menubar for
1372 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1374 return QMainWindow::event(e);
1377 return QMainWindow::event(e);
1381 return QMainWindow::event(e);
1385 void GuiView::resetWindowTitle()
1387 setWindowTitle(qt_("LyX"));
1390 bool GuiView::focusNextPrevChild(bool /*next*/)
1397 bool GuiView::busy() const
1403 void GuiView::setBusy(bool busy)
1405 bool const busy_before = busy_ > 0;
1406 busy ? ++busy_ : --busy_;
1407 if ((busy_ > 0) == busy_before)
1408 // busy state didn't change
1412 QApplication::setOverrideCursor(Qt::WaitCursor);
1415 QApplication::restoreOverrideCursor();
1420 void GuiView::resetCommandExecute()
1422 command_execute_ = false;
1427 double GuiView::pixelRatio() const
1429 #if QT_VERSION >= 0x050000
1430 return qt_scale_factor * devicePixelRatio();
1437 GuiWorkArea * GuiView::workArea(int index)
1439 if (TabWorkArea * twa = d.currentTabWorkArea())
1440 if (index < twa->count())
1441 return twa->workArea(index);
1446 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1448 if (currentWorkArea()
1449 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1450 return currentWorkArea();
1451 if (TabWorkArea * twa = d.currentTabWorkArea())
1452 return twa->workArea(buffer);
1457 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1459 // Automatically create a TabWorkArea if there are none yet.
1460 TabWorkArea * tab_widget = d.splitter_->count()
1461 ? d.currentTabWorkArea() : addTabWorkArea();
1462 return tab_widget->addWorkArea(buffer, *this);
1466 TabWorkArea * GuiView::addTabWorkArea()
1468 TabWorkArea * twa = new TabWorkArea;
1469 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1470 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1471 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1472 this, SLOT(on_lastWorkAreaRemoved()));
1474 d.splitter_->addWidget(twa);
1475 d.stack_widget_->setCurrentWidget(d.splitter_);
1480 GuiWorkArea const * GuiView::currentWorkArea() const
1482 return d.current_work_area_;
1486 GuiWorkArea * GuiView::currentWorkArea()
1488 return d.current_work_area_;
1492 GuiWorkArea const * GuiView::currentMainWorkArea() const
1494 if (!d.currentTabWorkArea())
1496 return d.currentTabWorkArea()->currentWorkArea();
1500 GuiWorkArea * GuiView::currentMainWorkArea()
1502 if (!d.currentTabWorkArea())
1504 return d.currentTabWorkArea()->currentWorkArea();
1508 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1510 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1512 d.current_work_area_ = 0;
1514 Q_EMIT bufferViewChanged();
1518 // FIXME: I've no clue why this is here and why it accesses
1519 // theGuiApp()->currentView, which might be 0 (bug 6464).
1520 // See also 27525 (vfr).
1521 if (theGuiApp()->currentView() == this
1522 && theGuiApp()->currentView()->currentWorkArea() == wa)
1525 if (currentBufferView())
1526 cap::saveSelection(currentBufferView()->cursor());
1528 theGuiApp()->setCurrentView(this);
1529 d.current_work_area_ = wa;
1531 // We need to reset this now, because it will need to be
1532 // right if the tabWorkArea gets reset in the for loop. We
1533 // will change it back if we aren't in that case.
1534 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1535 d.current_main_work_area_ = wa;
1537 for (int i = 0; i != d.splitter_->count(); ++i) {
1538 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1539 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1540 << ", Current main wa: " << currentMainWorkArea());
1545 d.current_main_work_area_ = old_cmwa;
1547 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1548 on_currentWorkAreaChanged(wa);
1549 BufferView & bv = wa->bufferView();
1550 bv.cursor().fixIfBroken();
1552 wa->setUpdatesEnabled(true);
1553 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1557 void GuiView::removeWorkArea(GuiWorkArea * wa)
1559 LASSERT(wa, return);
1560 if (wa == d.current_work_area_) {
1562 disconnectBufferView();
1563 d.current_work_area_ = 0;
1564 d.current_main_work_area_ = 0;
1567 bool found_twa = false;
1568 for (int i = 0; i != d.splitter_->count(); ++i) {
1569 TabWorkArea * twa = d.tabWorkArea(i);
1570 if (twa->removeWorkArea(wa)) {
1571 // Found in this tab group, and deleted the GuiWorkArea.
1573 if (twa->count() != 0) {
1574 if (d.current_work_area_ == 0)
1575 // This means that we are closing the current GuiWorkArea, so
1576 // switch to the next GuiWorkArea in the found TabWorkArea.
1577 setCurrentWorkArea(twa->currentWorkArea());
1579 // No more WorkAreas in this tab group, so delete it.
1586 // It is not a tabbed work area (i.e., the search work area), so it
1587 // should be deleted by other means.
1588 LASSERT(found_twa, return);
1590 if (d.current_work_area_ == 0) {
1591 if (d.splitter_->count() != 0) {
1592 TabWorkArea * twa = d.currentTabWorkArea();
1593 setCurrentWorkArea(twa->currentWorkArea());
1595 // No more work areas, switch to the background widget.
1596 setCurrentWorkArea(0);
1602 LayoutBox * GuiView::getLayoutDialog() const
1608 void GuiView::updateLayoutList()
1611 d.layout_->updateContents(false);
1615 void GuiView::updateToolbars()
1617 ToolbarMap::iterator end = d.toolbars_.end();
1618 if (d.current_work_area_) {
1620 if (d.current_work_area_->bufferView().cursor().inMathed()
1621 && !d.current_work_area_->bufferView().cursor().inRegexped())
1622 context |= Toolbars::MATH;
1623 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1624 context |= Toolbars::TABLE;
1625 if (currentBufferView()->buffer().areChangesPresent()
1626 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1627 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1628 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1629 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1630 context |= Toolbars::REVIEW;
1631 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1632 context |= Toolbars::MATHMACROTEMPLATE;
1633 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1634 context |= Toolbars::IPA;
1635 if (command_execute_)
1636 context |= Toolbars::MINIBUFFER;
1637 if (minibuffer_focus_) {
1638 context |= Toolbars::MINIBUFFER_FOCUS;
1639 minibuffer_focus_ = false;
1642 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1643 it->second->update(context);
1645 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1646 it->second->update();
1650 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1652 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1653 LASSERT(newBuffer, return);
1655 GuiWorkArea * wa = workArea(*newBuffer);
1658 newBuffer->masterBuffer()->updateBuffer();
1660 wa = addWorkArea(*newBuffer);
1661 // scroll to the position when the BufferView was last closed
1662 if (lyxrc.use_lastfilepos) {
1663 LastFilePosSection::FilePos filepos =
1664 theSession().lastFilePos().load(newBuffer->fileName());
1665 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1668 //Disconnect the old buffer...there's no new one.
1671 connectBuffer(*newBuffer);
1672 connectBufferView(wa->bufferView());
1674 setCurrentWorkArea(wa);
1678 void GuiView::connectBuffer(Buffer & buf)
1680 buf.setGuiDelegate(this);
1684 void GuiView::disconnectBuffer()
1686 if (d.current_work_area_)
1687 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1691 void GuiView::connectBufferView(BufferView & bv)
1693 bv.setGuiDelegate(this);
1697 void GuiView::disconnectBufferView()
1699 if (d.current_work_area_)
1700 d.current_work_area_->bufferView().setGuiDelegate(0);
1704 void GuiView::errors(string const & error_type, bool from_master)
1706 BufferView const * const bv = currentBufferView();
1710 ErrorList const & el = from_master ?
1711 bv->buffer().masterBuffer()->errorList(error_type) :
1712 bv->buffer().errorList(error_type);
1717 string err = error_type;
1719 err = "from_master|" + error_type;
1720 showDialog("errorlist", err);
1724 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1726 d.toc_models_.updateItem(toqstr(type), dit);
1730 void GuiView::structureChanged()
1732 // This is called from the Buffer, which has no way to ensure that cursors
1733 // in BufferView remain valid.
1734 if (documentBufferView())
1735 documentBufferView()->cursor().sanitize();
1736 // FIXME: This is slightly expensive, though less than the tocBackend update
1737 // (#9880). This also resets the view in the Toc Widget (#6675).
1738 d.toc_models_.reset(documentBufferView());
1739 // Navigator needs more than a simple update in this case. It needs to be
1741 updateDialog("toc", "");
1745 void GuiView::updateDialog(string const & name, string const & sdata)
1747 if (!isDialogVisible(name))
1750 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1751 if (it == d.dialogs_.end())
1754 Dialog * const dialog = it->second.get();
1755 if (dialog->isVisibleView())
1756 dialog->initialiseParams(sdata);
1760 BufferView * GuiView::documentBufferView()
1762 return currentMainWorkArea()
1763 ? ¤tMainWorkArea()->bufferView()
1768 BufferView const * GuiView::documentBufferView() const
1770 return currentMainWorkArea()
1771 ? ¤tMainWorkArea()->bufferView()
1776 BufferView * GuiView::currentBufferView()
1778 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1782 BufferView const * GuiView::currentBufferView() const
1784 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1788 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1789 Buffer const * orig, Buffer * clone)
1791 bool const success = clone->autoSave();
1793 busyBuffers.remove(orig);
1795 ? _("Automatic save done.")
1796 : _("Automatic save failed!");
1800 void GuiView::autoSave()
1802 LYXERR(Debug::INFO, "Running autoSave()");
1804 Buffer * buffer = documentBufferView()
1805 ? &documentBufferView()->buffer() : 0;
1807 resetAutosaveTimers();
1811 GuiViewPrivate::busyBuffers.insert(buffer);
1812 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1813 buffer, buffer->cloneBufferOnly());
1814 d.autosave_watcher_.setFuture(f);
1815 resetAutosaveTimers();
1819 void GuiView::resetAutosaveTimers()
1822 d.autosave_timeout_.restart();
1826 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1829 Buffer * buf = currentBufferView()
1830 ? ¤tBufferView()->buffer() : 0;
1831 Buffer * doc_buffer = documentBufferView()
1832 ? &(documentBufferView()->buffer()) : 0;
1835 /* In LyX/Mac, when a dialog is open, the menus of the
1836 application can still be accessed without giving focus to
1837 the main window. In this case, we want to disable the menu
1838 entries that are buffer-related.
1839 This code must not be used on Linux and Windows, since it
1840 would disable buffer-related entries when hovering over the
1841 menu (see bug #9574).
1843 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1849 // Check whether we need a buffer
1850 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1851 // no, exit directly
1852 flag.message(from_utf8(N_("Command not allowed with"
1853 "out any document open")));
1854 flag.setEnabled(false);
1858 if (cmd.origin() == FuncRequest::TOC) {
1859 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1860 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1861 flag.setEnabled(false);
1865 switch(cmd.action()) {
1866 case LFUN_BUFFER_IMPORT:
1869 case LFUN_MASTER_BUFFER_EXPORT:
1871 && (doc_buffer->parent() != 0
1872 || doc_buffer->hasChildren())
1873 && !d.processing_thread_watcher_.isRunning()
1874 // this launches a dialog, which would be in the wrong Buffer
1875 && !(::lyx::operator==(cmd.argument(), "custom"));
1878 case LFUN_MASTER_BUFFER_UPDATE:
1879 case LFUN_MASTER_BUFFER_VIEW:
1881 && (doc_buffer->parent() != 0
1882 || doc_buffer->hasChildren())
1883 && !d.processing_thread_watcher_.isRunning();
1886 case LFUN_BUFFER_UPDATE:
1887 case LFUN_BUFFER_VIEW: {
1888 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1892 string format = to_utf8(cmd.argument());
1893 if (cmd.argument().empty())
1894 format = doc_buffer->params().getDefaultOutputFormat();
1895 enable = doc_buffer->params().isExportable(format, true);
1899 case LFUN_BUFFER_RELOAD:
1900 enable = doc_buffer && !doc_buffer->isUnnamed()
1901 && doc_buffer->fileName().exists()
1902 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1905 case LFUN_BUFFER_CHILD_OPEN:
1906 enable = doc_buffer != 0;
1909 case LFUN_BUFFER_WRITE:
1910 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1913 //FIXME: This LFUN should be moved to GuiApplication.
1914 case LFUN_BUFFER_WRITE_ALL: {
1915 // We enable the command only if there are some modified buffers
1916 Buffer * first = theBufferList().first();
1921 // We cannot use a for loop as the buffer list is a cycle.
1923 if (!b->isClean()) {
1927 b = theBufferList().next(b);
1928 } while (b != first);
1932 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1933 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1936 case LFUN_BUFFER_EXPORT: {
1937 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1941 return doc_buffer->getStatus(cmd, flag);
1945 case LFUN_BUFFER_EXPORT_AS:
1946 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1951 case LFUN_BUFFER_WRITE_AS:
1952 enable = doc_buffer != 0;
1955 case LFUN_EXPORT_CANCEL:
1956 enable = d.processing_thread_watcher_.isRunning();
1959 case LFUN_BUFFER_CLOSE:
1960 case LFUN_VIEW_CLOSE:
1961 enable = doc_buffer != 0;
1964 case LFUN_BUFFER_CLOSE_ALL:
1965 enable = theBufferList().last() != theBufferList().first();
1968 case LFUN_BUFFER_CHKTEX: {
1969 // hide if we have no checktex command
1970 if (lyxrc.chktex_command.empty()) {
1971 flag.setUnknown(true);
1975 if (!doc_buffer || !doc_buffer->params().isLatex()
1976 || d.processing_thread_watcher_.isRunning()) {
1977 // grey out, don't hide
1985 case LFUN_VIEW_SPLIT:
1986 if (cmd.getArg(0) == "vertical")
1987 enable = doc_buffer && (d.splitter_->count() == 1 ||
1988 d.splitter_->orientation() == Qt::Vertical);
1990 enable = doc_buffer && (d.splitter_->count() == 1 ||
1991 d.splitter_->orientation() == Qt::Horizontal);
1994 case LFUN_TAB_GROUP_CLOSE:
1995 enable = d.tabWorkAreaCount() > 1;
1998 case LFUN_DEVEL_MODE_TOGGLE:
1999 flag.setOnOff(devel_mode_);
2002 case LFUN_TOOLBAR_TOGGLE: {
2003 string const name = cmd.getArg(0);
2004 if (GuiToolbar * t = toolbar(name))
2005 flag.setOnOff(t->isVisible());
2008 docstring const msg =
2009 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2015 case LFUN_TOOLBAR_MOVABLE: {
2016 string const name = cmd.getArg(0);
2017 // use negation since locked == !movable
2019 // toolbar name * locks all toolbars
2020 flag.setOnOff(!toolbarsMovable_);
2021 else if (GuiToolbar * t = toolbar(name))
2022 flag.setOnOff(!(t->isMovable()));
2025 docstring const msg =
2026 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2032 case LFUN_ICON_SIZE:
2033 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2036 case LFUN_DROP_LAYOUTS_CHOICE:
2040 case LFUN_UI_TOGGLE:
2041 flag.setOnOff(isFullScreen());
2044 case LFUN_DIALOG_DISCONNECT_INSET:
2047 case LFUN_DIALOG_HIDE:
2048 // FIXME: should we check if the dialog is shown?
2051 case LFUN_DIALOG_TOGGLE:
2052 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2055 case LFUN_DIALOG_SHOW: {
2056 string const name = cmd.getArg(0);
2058 enable = name == "aboutlyx"
2059 || name == "file" //FIXME: should be removed.
2061 || name == "texinfo"
2062 || name == "progress"
2063 || name == "compare";
2064 else if (name == "character" || name == "symbols"
2065 || name == "mathdelimiter" || name == "mathmatrix") {
2066 if (!buf || buf->isReadonly())
2069 Cursor const & cur = currentBufferView()->cursor();
2070 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2073 else if (name == "latexlog")
2074 enable = FileName(doc_buffer->logName()).isReadableFile();
2075 else if (name == "spellchecker")
2076 enable = theSpellChecker()
2077 && !doc_buffer->isReadonly()
2078 && !doc_buffer->text().empty();
2079 else if (name == "vclog")
2080 enable = doc_buffer->lyxvc().inUse();
2084 case LFUN_DIALOG_UPDATE: {
2085 string const name = cmd.getArg(0);
2087 enable = name == "prefs";
2091 case LFUN_COMMAND_EXECUTE:
2093 case LFUN_MENU_OPEN:
2094 // Nothing to check.
2097 case LFUN_COMPLETION_INLINE:
2098 if (!d.current_work_area_
2099 || !d.current_work_area_->completer().inlinePossible(
2100 currentBufferView()->cursor()))
2104 case LFUN_COMPLETION_POPUP:
2105 if (!d.current_work_area_
2106 || !d.current_work_area_->completer().popupPossible(
2107 currentBufferView()->cursor()))
2112 if (!d.current_work_area_
2113 || !d.current_work_area_->completer().inlinePossible(
2114 currentBufferView()->cursor()))
2118 case LFUN_COMPLETION_ACCEPT:
2119 if (!d.current_work_area_
2120 || (!d.current_work_area_->completer().popupVisible()
2121 && !d.current_work_area_->completer().inlineVisible()
2122 && !d.current_work_area_->completer().completionAvailable()))
2126 case LFUN_COMPLETION_CANCEL:
2127 if (!d.current_work_area_
2128 || (!d.current_work_area_->completer().popupVisible()
2129 && !d.current_work_area_->completer().inlineVisible()))
2133 case LFUN_BUFFER_ZOOM_OUT:
2134 case LFUN_BUFFER_ZOOM_IN: {
2135 // only diff between these two is that the default for ZOOM_OUT
2137 bool const neg_zoom =
2138 convert<int>(cmd.argument()) < 0 ||
2139 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2140 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2141 docstring const msg =
2142 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2146 enable = doc_buffer;
2150 case LFUN_BUFFER_ZOOM: {
2151 bool const less_than_min_zoom =
2152 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2153 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2154 docstring const msg =
2155 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2160 enable = doc_buffer;
2164 case LFUN_BUFFER_MOVE_NEXT:
2165 case LFUN_BUFFER_MOVE_PREVIOUS:
2166 // we do not cycle when moving
2167 case LFUN_BUFFER_NEXT:
2168 case LFUN_BUFFER_PREVIOUS:
2169 // because we cycle, it doesn't matter whether on first or last
2170 enable = (d.currentTabWorkArea()->count() > 1);
2172 case LFUN_BUFFER_SWITCH:
2173 // toggle on the current buffer, but do not toggle off
2174 // the other ones (is that a good idea?)
2176 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2177 flag.setOnOff(true);
2180 case LFUN_VC_REGISTER:
2181 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2183 case LFUN_VC_RENAME:
2184 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2187 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2189 case LFUN_VC_CHECK_IN:
2190 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2192 case LFUN_VC_CHECK_OUT:
2193 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2195 case LFUN_VC_LOCKING_TOGGLE:
2196 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2197 && doc_buffer->lyxvc().lockingToggleEnabled();
2198 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2200 case LFUN_VC_REVERT:
2201 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2202 && !doc_buffer->hasReadonlyFlag();
2204 case LFUN_VC_UNDO_LAST:
2205 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2207 case LFUN_VC_REPO_UPDATE:
2208 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2210 case LFUN_VC_COMMAND: {
2211 if (cmd.argument().empty())
2213 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2217 case LFUN_VC_COMPARE:
2218 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2221 case LFUN_SERVER_GOTO_FILE_ROW:
2222 case LFUN_LYX_ACTIVATE:
2224 case LFUN_FORWARD_SEARCH:
2225 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2228 case LFUN_FILE_INSERT_PLAINTEXT:
2229 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2230 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2233 case LFUN_SPELLING_CONTINUOUSLY:
2234 flag.setOnOff(lyxrc.spellcheck_continuously);
2242 flag.setEnabled(false);
2248 static FileName selectTemplateFile()
2250 FileDialog dlg(qt_("Select template file"));
2251 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2252 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2254 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2255 QStringList(qt_("LyX Documents (*.lyx)")));
2257 if (result.first == FileDialog::Later)
2259 if (result.second.isEmpty())
2261 return FileName(fromqstr(result.second));
2265 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2269 Buffer * newBuffer = 0;
2271 newBuffer = checkAndLoadLyXFile(filename);
2272 } catch (ExceptionMessage const & e) {
2279 message(_("Document not loaded."));
2283 setBuffer(newBuffer);
2284 newBuffer->errors("Parse");
2287 theSession().lastFiles().add(filename);
2288 theSession().writeFile();
2295 void GuiView::openDocument(string const & fname)
2297 string initpath = lyxrc.document_path;
2299 if (documentBufferView()) {
2300 string const trypath = documentBufferView()->buffer().filePath();
2301 // If directory is writeable, use this as default.
2302 if (FileName(trypath).isDirWritable())
2308 if (fname.empty()) {
2309 FileDialog dlg(qt_("Select document to open"));
2310 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2311 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2313 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2314 FileDialog::Result result =
2315 dlg.open(toqstr(initpath), filter);
2317 if (result.first == FileDialog::Later)
2320 filename = fromqstr(result.second);
2322 // check selected filename
2323 if (filename.empty()) {
2324 message(_("Canceled."));
2330 // get absolute path of file and add ".lyx" to the filename if
2332 FileName const fullname =
2333 fileSearch(string(), filename, "lyx", support::may_not_exist);
2334 if (!fullname.empty())
2335 filename = fullname.absFileName();
2337 if (!fullname.onlyPath().isDirectory()) {
2338 Alert::warning(_("Invalid filename"),
2339 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2340 from_utf8(fullname.absFileName())));
2344 // if the file doesn't exist and isn't already open (bug 6645),
2345 // let the user create one
2346 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2347 !LyXVC::file_not_found_hook(fullname)) {
2348 // the user specifically chose this name. Believe him.
2349 Buffer * const b = newFile(filename, string(), true);
2355 docstring const disp_fn = makeDisplayPath(filename);
2356 message(bformat(_("Opening document %1$s..."), disp_fn));
2359 Buffer * buf = loadDocument(fullname);
2361 str2 = bformat(_("Document %1$s opened."), disp_fn);
2362 if (buf->lyxvc().inUse())
2363 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2364 " " + _("Version control detected.");
2366 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2371 // FIXME: clean that
2372 static bool import(GuiView * lv, FileName const & filename,
2373 string const & format, ErrorList & errorList)
2375 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2377 string loader_format;
2378 vector<string> loaders = theConverters().loaders();
2379 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2380 vector<string>::const_iterator it = loaders.begin();
2381 vector<string>::const_iterator en = loaders.end();
2382 for (; it != en; ++it) {
2383 if (!theConverters().isReachable(format, *it))
2386 string const tofile =
2387 support::changeExtension(filename.absFileName(),
2388 theFormats().extension(*it));
2389 if (theConverters().convert(0, filename, FileName(tofile),
2390 filename, format, *it, errorList) != Converters::SUCCESS)
2392 loader_format = *it;
2395 if (loader_format.empty()) {
2396 frontend::Alert::error(_("Couldn't import file"),
2397 bformat(_("No information for importing the format %1$s."),
2398 theFormats().prettyName(format)));
2402 loader_format = format;
2404 if (loader_format == "lyx") {
2405 Buffer * buf = lv->loadDocument(lyxfile);
2409 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2413 bool as_paragraphs = loader_format == "textparagraph";
2414 string filename2 = (loader_format == format) ? filename.absFileName()
2415 : support::changeExtension(filename.absFileName(),
2416 theFormats().extension(loader_format));
2417 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2419 guiApp->setCurrentView(lv);
2420 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2427 void GuiView::importDocument(string const & argument)
2430 string filename = split(argument, format, ' ');
2432 LYXERR(Debug::INFO, format << " file: " << filename);
2434 // need user interaction
2435 if (filename.empty()) {
2436 string initpath = lyxrc.document_path;
2437 if (documentBufferView()) {
2438 string const trypath = documentBufferView()->buffer().filePath();
2439 // If directory is writeable, use this as default.
2440 if (FileName(trypath).isDirWritable())
2444 docstring const text = bformat(_("Select %1$s file to import"),
2445 theFormats().prettyName(format));
2447 FileDialog dlg(toqstr(text));
2448 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2449 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2451 docstring filter = theFormats().prettyName(format);
2454 filter += from_utf8(theFormats().extensions(format));
2457 FileDialog::Result result =
2458 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2460 if (result.first == FileDialog::Later)
2463 filename = fromqstr(result.second);
2465 // check selected filename
2466 if (filename.empty())
2467 message(_("Canceled."));
2470 if (filename.empty())
2473 // get absolute path of file
2474 FileName const fullname(support::makeAbsPath(filename));
2476 // Can happen if the user entered a path into the dialog
2478 if (fullname.onlyFileName().empty()) {
2479 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2480 "Aborting import."),
2481 from_utf8(fullname.absFileName()));
2482 frontend::Alert::error(_("File name error"), msg);
2483 message(_("Canceled."));
2488 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2490 // Check if the document already is open
2491 Buffer * buf = theBufferList().getBuffer(lyxfile);
2494 if (!closeBuffer()) {
2495 message(_("Canceled."));
2500 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2502 // if the file exists already, and we didn't do
2503 // -i lyx thefile.lyx, warn
2504 if (lyxfile.exists() && fullname != lyxfile) {
2506 docstring text = bformat(_("The document %1$s already exists.\n\n"
2507 "Do you want to overwrite that document?"), displaypath);
2508 int const ret = Alert::prompt(_("Overwrite document?"),
2509 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2512 message(_("Canceled."));
2517 message(bformat(_("Importing %1$s..."), displaypath));
2518 ErrorList errorList;
2519 if (import(this, fullname, format, errorList))
2520 message(_("imported."));
2522 message(_("file not imported!"));
2524 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2528 void GuiView::newDocument(string const & filename, bool from_template)
2530 FileName initpath(lyxrc.document_path);
2531 if (documentBufferView()) {
2532 FileName const trypath(documentBufferView()->buffer().filePath());
2533 // If directory is writeable, use this as default.
2534 if (trypath.isDirWritable())
2538 string templatefile;
2539 if (from_template) {
2540 templatefile = selectTemplateFile().absFileName();
2541 if (templatefile.empty())
2546 if (filename.empty())
2547 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2549 b = newFile(filename, templatefile, true);
2554 // If no new document could be created, it is unsure
2555 // whether there is a valid BufferView.
2556 if (currentBufferView())
2557 // Ensure the cursor is correctly positioned on screen.
2558 currentBufferView()->showCursor();
2562 void GuiView::insertLyXFile(docstring const & fname)
2564 BufferView * bv = documentBufferView();
2569 FileName filename(to_utf8(fname));
2570 if (filename.empty()) {
2571 // Launch a file browser
2573 string initpath = lyxrc.document_path;
2574 string const trypath = bv->buffer().filePath();
2575 // If directory is writeable, use this as default.
2576 if (FileName(trypath).isDirWritable())
2580 FileDialog dlg(qt_("Select LyX document to insert"));
2581 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2582 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2584 FileDialog::Result result = dlg.open(toqstr(initpath),
2585 QStringList(qt_("LyX Documents (*.lyx)")));
2587 if (result.first == FileDialog::Later)
2591 filename.set(fromqstr(result.second));
2593 // check selected filename
2594 if (filename.empty()) {
2595 // emit message signal.
2596 message(_("Canceled."));
2601 bv->insertLyXFile(filename);
2602 bv->buffer().errors("Parse");
2606 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2608 FileName fname = b.fileName();
2609 FileName const oldname = fname;
2611 if (!newname.empty()) {
2613 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2615 // Switch to this Buffer.
2618 // No argument? Ask user through dialog.
2620 FileDialog dlg(qt_("Choose a filename to save document as"));
2621 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2622 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2624 if (!isLyXFileName(fname.absFileName()))
2625 fname.changeExtension(".lyx");
2627 FileDialog::Result result =
2628 dlg.save(toqstr(fname.onlyPath().absFileName()),
2629 QStringList(qt_("LyX Documents (*.lyx)")),
2630 toqstr(fname.onlyFileName()));
2632 if (result.first == FileDialog::Later)
2635 fname.set(fromqstr(result.second));
2640 if (!isLyXFileName(fname.absFileName()))
2641 fname.changeExtension(".lyx");
2644 // fname is now the new Buffer location.
2646 // if there is already a Buffer open with this name, we do not want
2647 // to have another one. (the second test makes sure we're not just
2648 // trying to overwrite ourselves, which is fine.)
2649 if (theBufferList().exists(fname) && fname != oldname
2650 && theBufferList().getBuffer(fname) != &b) {
2651 docstring const text =
2652 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2653 "Please close it before attempting to overwrite it.\n"
2654 "Do you want to choose a new filename?"),
2655 from_utf8(fname.absFileName()));
2656 int const ret = Alert::prompt(_("Chosen File Already Open"),
2657 text, 0, 1, _("&Rename"), _("&Cancel"));
2659 case 0: return renameBuffer(b, docstring(), kind);
2660 case 1: return false;
2665 bool const existsLocal = fname.exists();
2666 bool const existsInVC = LyXVC::fileInVC(fname);
2667 if (existsLocal || existsInVC) {
2668 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2669 if (kind != LV_WRITE_AS && existsInVC) {
2670 // renaming to a name that is already in VC
2672 docstring text = bformat(_("The document %1$s "
2673 "is already registered.\n\n"
2674 "Do you want to choose a new name?"),
2676 docstring const title = (kind == LV_VC_RENAME) ?
2677 _("Rename document?") : _("Copy document?");
2678 docstring const button = (kind == LV_VC_RENAME) ?
2679 _("&Rename") : _("&Copy");
2680 int const ret = Alert::prompt(title, text, 0, 1,
2681 button, _("&Cancel"));
2683 case 0: return renameBuffer(b, docstring(), kind);
2684 case 1: return false;
2689 docstring text = bformat(_("The document %1$s "
2690 "already exists.\n\n"
2691 "Do you want to overwrite that document?"),
2693 int const ret = Alert::prompt(_("Overwrite document?"),
2694 text, 0, 2, _("&Overwrite"),
2695 _("&Rename"), _("&Cancel"));
2698 case 1: return renameBuffer(b, docstring(), kind);
2699 case 2: return false;
2705 case LV_VC_RENAME: {
2706 string msg = b.lyxvc().rename(fname);
2709 message(from_utf8(msg));
2713 string msg = b.lyxvc().copy(fname);
2716 message(from_utf8(msg));
2722 // LyXVC created the file already in case of LV_VC_RENAME or
2723 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2724 // relative paths of included stuff right if we moved e.g. from
2725 // /a/b.lyx to /a/c/b.lyx.
2727 bool const saved = saveBuffer(b, fname);
2734 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2736 FileName fname = b.fileName();
2738 FileDialog dlg(qt_("Choose a filename to export the document as"));
2739 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2742 QString const anyformat = qt_("Guess from extension (*.*)");
2745 vector<Format const *> export_formats;
2746 for (Format const & f : theFormats())
2747 if (f.documentFormat())
2748 export_formats.push_back(&f);
2749 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2750 map<QString, string> fmap;
2753 for (Format const * f : export_formats) {
2754 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2755 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2757 from_ascii(f->extension())));
2758 types << loc_filter;
2759 fmap[loc_filter] = f->name();
2760 if (from_ascii(f->name()) == iformat) {
2761 filter = loc_filter;
2762 ext = f->extension();
2765 string ofname = fname.onlyFileName();
2767 ofname = support::changeExtension(ofname, ext);
2768 FileDialog::Result result =
2769 dlg.save(toqstr(fname.onlyPath().absFileName()),
2773 if (result.first != FileDialog::Chosen)
2777 fname.set(fromqstr(result.second));
2778 if (filter == anyformat)
2779 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2781 fmt_name = fmap[filter];
2782 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2783 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2785 if (fmt_name.empty() || fname.empty())
2788 // fname is now the new Buffer location.
2789 if (FileName(fname).exists()) {
2790 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2791 docstring text = bformat(_("The document %1$s already "
2792 "exists.\n\nDo you want to "
2793 "overwrite that document?"),
2795 int const ret = Alert::prompt(_("Overwrite document?"),
2796 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2799 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2800 case 2: return false;
2804 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2807 return dr.dispatched();
2811 bool GuiView::saveBuffer(Buffer & b)
2813 return saveBuffer(b, FileName());
2817 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2819 if (workArea(b) && workArea(b)->inDialogMode())
2822 if (fn.empty() && b.isUnnamed())
2823 return renameBuffer(b, docstring());
2825 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2827 theSession().lastFiles().add(b.fileName());
2828 theSession().writeFile();
2832 // Switch to this Buffer.
2835 // FIXME: we don't tell the user *WHY* the save failed !!
2836 docstring const file = makeDisplayPath(b.absFileName(), 30);
2837 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2838 "Do you want to rename the document and "
2839 "try again?"), file);
2840 int const ret = Alert::prompt(_("Rename and save?"),
2841 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2844 if (!renameBuffer(b, docstring()))
2853 return saveBuffer(b, fn);
2857 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2859 return closeWorkArea(wa, false);
2863 // We only want to close the buffer if it is not visible in other workareas
2864 // of the same view, nor in other views, and if this is not a child
2865 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2867 Buffer & buf = wa->bufferView().buffer();
2869 bool last_wa = d.countWorkAreasOf(buf) == 1
2870 && !inOtherView(buf) && !buf.parent();
2872 bool close_buffer = last_wa;
2875 if (lyxrc.close_buffer_with_last_view == "yes")
2877 else if (lyxrc.close_buffer_with_last_view == "no")
2878 close_buffer = false;
2881 if (buf.isUnnamed())
2882 file = from_utf8(buf.fileName().onlyFileName());
2884 file = buf.fileName().displayName(30);
2885 docstring const text = bformat(
2886 _("Last view on document %1$s is being closed.\n"
2887 "Would you like to close or hide the document?\n"
2889 "Hidden documents can be displayed back through\n"
2890 "the menu: View->Hidden->...\n"
2892 "To remove this question, set your preference in:\n"
2893 " Tools->Preferences->Look&Feel->UserInterface\n"
2895 int ret = Alert::prompt(_("Close or hide document?"),
2896 text, 0, 1, _("&Close"), _("&Hide"));
2897 close_buffer = (ret == 0);
2901 return closeWorkArea(wa, close_buffer);
2905 bool GuiView::closeBuffer()
2907 GuiWorkArea * wa = currentMainWorkArea();
2908 // coverity complained about this
2909 // it seems unnecessary, but perhaps is worth the check
2910 LASSERT(wa, return false);
2912 setCurrentWorkArea(wa);
2913 Buffer & buf = wa->bufferView().buffer();
2914 return closeWorkArea(wa, !buf.parent());
2918 void GuiView::writeSession() const {
2919 GuiWorkArea const * active_wa = currentMainWorkArea();
2920 for (int i = 0; i < d.splitter_->count(); ++i) {
2921 TabWorkArea * twa = d.tabWorkArea(i);
2922 for (int j = 0; j < twa->count(); ++j) {
2923 GuiWorkArea * wa = twa->workArea(j);
2924 Buffer & buf = wa->bufferView().buffer();
2925 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2931 bool GuiView::closeBufferAll()
2933 // Close the workareas in all other views
2934 QList<int> const ids = guiApp->viewIds();
2935 for (int i = 0; i != ids.size(); ++i) {
2936 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2940 // Close our own workareas
2941 if (!closeWorkAreaAll())
2944 // Now close the hidden buffers. We prevent hidden buffers from being
2945 // dirty, so we can just close them.
2946 theBufferList().closeAll();
2951 bool GuiView::closeWorkAreaAll()
2953 setCurrentWorkArea(currentMainWorkArea());
2955 // We might be in a situation that there is still a tabWorkArea, but
2956 // there are no tabs anymore. This can happen when we get here after a
2957 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2958 // many TabWorkArea's have no documents anymore.
2961 // We have to call count() each time, because it can happen that
2962 // more than one splitter will disappear in one iteration (bug 5998).
2963 while (d.splitter_->count() > empty_twa) {
2964 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2966 if (twa->count() == 0)
2969 setCurrentWorkArea(twa->currentWorkArea());
2970 if (!closeTabWorkArea(twa))
2978 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2983 Buffer & buf = wa->bufferView().buffer();
2985 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2986 Alert::warning(_("Close document"),
2987 _("Document could not be closed because it is being processed by LyX."));
2992 return closeBuffer(buf);
2994 if (!inMultiTabs(wa))
2995 if (!saveBufferIfNeeded(buf, true))
3003 bool GuiView::closeBuffer(Buffer & buf)
3005 // If we are in a close_event all children will be closed in some time,
3006 // so no need to do it here. This will ensure that the children end up
3007 // in the session file in the correct order. If we close the master
3008 // buffer, we can close or release the child buffers here too.
3009 bool success = true;
3011 ListOfBuffers clist = buf.getChildren();
3012 ListOfBuffers::const_iterator it = clist.begin();
3013 ListOfBuffers::const_iterator const bend = clist.end();
3014 for (; it != bend; ++it) {
3015 Buffer * child_buf = *it;
3016 if (theBufferList().isOthersChild(&buf, child_buf)) {
3017 child_buf->setParent(0);
3021 // FIXME: should we look in other tabworkareas?
3022 // ANSWER: I don't think so. I've tested, and if the child is
3023 // open in some other window, it closes without a problem.
3024 GuiWorkArea * child_wa = workArea(*child_buf);
3026 success = closeWorkArea(child_wa, true);
3030 // In this case the child buffer is open but hidden.
3031 // It therefore should not (MUST NOT) be dirty!
3032 LATTEST(child_buf->isClean());
3033 theBufferList().release(child_buf);
3038 // goto bookmark to update bookmark pit.
3039 // FIXME: we should update only the bookmarks related to this buffer!
3040 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3041 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3042 guiApp->gotoBookmark(i+1, false, false);
3044 if (saveBufferIfNeeded(buf, false)) {
3045 buf.removeAutosaveFile();
3046 theBufferList().release(&buf);
3050 // open all children again to avoid a crash because of dangling
3051 // pointers (bug 6603)
3057 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3059 while (twa == d.currentTabWorkArea()) {
3060 twa->setCurrentIndex(twa->count() - 1);
3062 GuiWorkArea * wa = twa->currentWorkArea();
3063 Buffer & b = wa->bufferView().buffer();
3065 // We only want to close the buffer if the same buffer is not visible
3066 // in another view, and if this is not a child and if we are closing
3067 // a view (not a tabgroup).
3068 bool const close_buffer =
3069 !inOtherView(b) && !b.parent() && closing_;
3071 if (!closeWorkArea(wa, close_buffer))
3078 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3080 if (buf.isClean() || buf.paragraphs().empty())
3083 // Switch to this Buffer.
3089 if (buf.isUnnamed()) {
3090 file = from_utf8(buf.fileName().onlyFileName());
3093 FileName filename = buf.fileName();
3095 file = filename.displayName(30);
3096 exists = filename.exists();
3099 // Bring this window to top before asking questions.
3104 if (hiding && buf.isUnnamed()) {
3105 docstring const text = bformat(_("The document %1$s has not been "
3106 "saved yet.\n\nDo you want to save "
3107 "the document?"), file);
3108 ret = Alert::prompt(_("Save new document?"),
3109 text, 0, 1, _("&Save"), _("&Cancel"));
3113 docstring const text = exists ?
3114 bformat(_("The document %1$s has unsaved changes."
3115 "\n\nDo you want to save the document or "
3116 "discard the changes?"), file) :
3117 bformat(_("The document %1$s has not been saved yet."
3118 "\n\nDo you want to save the document or "
3119 "discard it entirely?"), file);
3120 docstring const title = exists ?
3121 _("Save changed document?") : _("Save document?");
3122 ret = Alert::prompt(title, text, 0, 2,
3123 _("&Save"), _("&Discard"), _("&Cancel"));
3128 if (!saveBuffer(buf))
3132 // If we crash after this we could have no autosave file
3133 // but I guess this is really improbable (Jug).
3134 // Sometimes improbable things happen:
3135 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3136 // buf.removeAutosaveFile();
3138 // revert all changes
3149 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3151 Buffer & buf = wa->bufferView().buffer();
3153 for (int i = 0; i != d.splitter_->count(); ++i) {
3154 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3155 if (wa_ && wa_ != wa)
3158 return inOtherView(buf);
3162 bool GuiView::inOtherView(Buffer & buf)
3164 QList<int> const ids = guiApp->viewIds();
3166 for (int i = 0; i != ids.size(); ++i) {
3170 if (guiApp->view(ids[i]).workArea(buf))
3177 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3179 if (!documentBufferView())
3182 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3183 Buffer * const curbuf = &documentBufferView()->buffer();
3184 int nwa = twa->count();
3185 for (int i = 0; i < nwa; ++i) {
3186 if (&workArea(i)->bufferView().buffer() == curbuf) {
3188 if (np == NEXTBUFFER)
3189 next_index = (i == nwa - 1 ? 0 : i + 1);
3191 next_index = (i == 0 ? nwa - 1 : i - 1);
3193 twa->moveTab(i, next_index);
3195 setBuffer(&workArea(next_index)->bufferView().buffer());
3203 /// make sure the document is saved
3204 static bool ensureBufferClean(Buffer * buffer)
3206 LASSERT(buffer, return false);
3207 if (buffer->isClean() && !buffer->isUnnamed())
3210 docstring const file = buffer->fileName().displayName(30);
3213 if (!buffer->isUnnamed()) {
3214 text = bformat(_("The document %1$s has unsaved "
3215 "changes.\n\nDo you want to save "
3216 "the document?"), file);
3217 title = _("Save changed document?");
3220 text = bformat(_("The document %1$s has not been "
3221 "saved yet.\n\nDo you want to save "
3222 "the document?"), file);
3223 title = _("Save new document?");
3225 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3228 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3230 return buffer->isClean() && !buffer->isUnnamed();
3234 bool GuiView::reloadBuffer(Buffer & buf)
3236 currentBufferView()->cursor().reset();
3237 Buffer::ReadStatus status = buf.reload();
3238 return status == Buffer::ReadSuccess;
3242 void GuiView::checkExternallyModifiedBuffers()
3244 BufferList::iterator bit = theBufferList().begin();
3245 BufferList::iterator const bend = theBufferList().end();
3246 for (; bit != bend; ++bit) {
3247 Buffer * buf = *bit;
3248 if (buf->fileName().exists() && buf->isChecksumModified()) {
3249 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3250 " Reload now? Any local changes will be lost."),
3251 from_utf8(buf->absFileName()));
3252 int const ret = Alert::prompt(_("Reload externally changed document?"),
3253 text, 0, 1, _("&Reload"), _("&Cancel"));
3261 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3263 Buffer * buffer = documentBufferView()
3264 ? &(documentBufferView()->buffer()) : 0;
3266 switch (cmd.action()) {
3267 case LFUN_VC_REGISTER:
3268 if (!buffer || !ensureBufferClean(buffer))
3270 if (!buffer->lyxvc().inUse()) {
3271 if (buffer->lyxvc().registrer()) {
3272 reloadBuffer(*buffer);
3273 dr.clearMessageUpdate();
3278 case LFUN_VC_RENAME:
3279 case LFUN_VC_COPY: {
3280 if (!buffer || !ensureBufferClean(buffer))
3282 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3283 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3284 // Some changes are not yet committed.
3285 // We test here and not in getStatus(), since
3286 // this test is expensive.
3288 LyXVC::CommandResult ret =
3289 buffer->lyxvc().checkIn(log);
3291 if (ret == LyXVC::ErrorCommand ||
3292 ret == LyXVC::VCSuccess)
3293 reloadBuffer(*buffer);
3294 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3295 frontend::Alert::error(
3296 _("Revision control error."),
3297 _("Document could not be checked in."));
3301 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3302 LV_VC_RENAME : LV_VC_COPY;
3303 renameBuffer(*buffer, cmd.argument(), kind);
3308 case LFUN_VC_CHECK_IN:
3309 if (!buffer || !ensureBufferClean(buffer))
3311 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3313 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3315 // Only skip reloading if the checkin was cancelled or
3316 // an error occurred before the real checkin VCS command
3317 // was executed, since the VCS might have changed the
3318 // file even if it could not checkin successfully.
3319 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3320 reloadBuffer(*buffer);
3324 case LFUN_VC_CHECK_OUT:
3325 if (!buffer || !ensureBufferClean(buffer))
3327 if (buffer->lyxvc().inUse()) {
3328 dr.setMessage(buffer->lyxvc().checkOut());
3329 reloadBuffer(*buffer);
3333 case LFUN_VC_LOCKING_TOGGLE:
3334 LASSERT(buffer, return);
3335 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3337 if (buffer->lyxvc().inUse()) {
3338 string res = buffer->lyxvc().lockingToggle();
3340 frontend::Alert::error(_("Revision control error."),
3341 _("Error when setting the locking property."));
3344 reloadBuffer(*buffer);
3349 case LFUN_VC_REVERT:
3350 LASSERT(buffer, return);
3351 if (buffer->lyxvc().revert()) {
3352 reloadBuffer(*buffer);
3353 dr.clearMessageUpdate();
3357 case LFUN_VC_UNDO_LAST:
3358 LASSERT(buffer, return);
3359 buffer->lyxvc().undoLast();
3360 reloadBuffer(*buffer);
3361 dr.clearMessageUpdate();
3364 case LFUN_VC_REPO_UPDATE:
3365 LASSERT(buffer, return);
3366 if (ensureBufferClean(buffer)) {
3367 dr.setMessage(buffer->lyxvc().repoUpdate());
3368 checkExternallyModifiedBuffers();
3372 case LFUN_VC_COMMAND: {
3373 string flag = cmd.getArg(0);
3374 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3377 if (contains(flag, 'M')) {
3378 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3381 string path = cmd.getArg(1);
3382 if (contains(path, "$$p") && buffer)
3383 path = subst(path, "$$p", buffer->filePath());
3384 LYXERR(Debug::LYXVC, "Directory: " << path);
3386 if (!pp.isReadableDirectory()) {
3387 lyxerr << _("Directory is not accessible.") << endl;
3390 support::PathChanger p(pp);
3392 string command = cmd.getArg(2);
3393 if (command.empty())
3396 command = subst(command, "$$i", buffer->absFileName());
3397 command = subst(command, "$$p", buffer->filePath());
3399 command = subst(command, "$$m", to_utf8(message));
3400 LYXERR(Debug::LYXVC, "Command: " << command);
3402 one.startscript(Systemcall::Wait, command);
3406 if (contains(flag, 'I'))
3407 buffer->markDirty();
3408 if (contains(flag, 'R'))
3409 reloadBuffer(*buffer);
3414 case LFUN_VC_COMPARE: {
3415 if (cmd.argument().empty()) {
3416 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3420 string rev1 = cmd.getArg(0);
3425 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3428 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3429 f2 = buffer->absFileName();
3431 string rev2 = cmd.getArg(1);
3435 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3439 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3440 f1 << "\n" << f2 << "\n" );
3441 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3442 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3452 void GuiView::openChildDocument(string const & fname)
3454 LASSERT(documentBufferView(), return);
3455 Buffer & buffer = documentBufferView()->buffer();
3456 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3457 documentBufferView()->saveBookmark(false);
3459 if (theBufferList().exists(filename)) {
3460 child = theBufferList().getBuffer(filename);
3463 message(bformat(_("Opening child document %1$s..."),
3464 makeDisplayPath(filename.absFileName())));
3465 child = loadDocument(filename, false);
3467 // Set the parent name of the child document.
3468 // This makes insertion of citations and references in the child work,
3469 // when the target is in the parent or another child document.
3471 child->setParent(&buffer);
3475 bool GuiView::goToFileRow(string const & argument)
3479 size_t i = argument.find_last_of(' ');
3480 if (i != string::npos) {
3481 file_name = os::internal_path(trim(argument.substr(0, i)));
3482 istringstream is(argument.substr(i + 1));
3487 if (i == string::npos) {
3488 LYXERR0("Wrong argument: " << argument);
3492 string const abstmp = package().temp_dir().absFileName();
3493 string const realtmp = package().temp_dir().realPath();
3494 // We have to use os::path_prefix_is() here, instead of
3495 // simply prefixIs(), because the file name comes from
3496 // an external application and may need case adjustment.
3497 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3498 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3499 // Needed by inverse dvi search. If it is a file
3500 // in tmpdir, call the apropriated function.
3501 // If tmpdir is a symlink, we may have the real
3502 // path passed back, so we correct for that.
3503 if (!prefixIs(file_name, abstmp))
3504 file_name = subst(file_name, realtmp, abstmp);
3505 buf = theBufferList().getBufferFromTmp(file_name);
3507 // Must replace extension of the file to be .lyx
3508 // and get full path
3509 FileName const s = fileSearch(string(),
3510 support::changeExtension(file_name, ".lyx"), "lyx");
3511 // Either change buffer or load the file
3512 if (theBufferList().exists(s))
3513 buf = theBufferList().getBuffer(s);
3514 else if (s.exists()) {
3515 buf = loadDocument(s);
3520 _("File does not exist: %1$s"),
3521 makeDisplayPath(file_name)));
3527 _("No buffer for file: %1$s."),
3528 makeDisplayPath(file_name))
3533 bool success = documentBufferView()->setCursorFromRow(row);
3535 LYXERR(Debug::LATEX,
3536 "setCursorFromRow: invalid position for row " << row);
3537 frontend::Alert::error(_("Inverse Search Failed"),
3538 _("Invalid position requested by inverse search.\n"
3539 "You may need to update the viewed document."));
3545 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3547 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3548 menu->exec(QCursor::pos());
3553 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3554 Buffer const * orig, Buffer * clone, string const & format)
3556 Buffer::ExportStatus const status = func(format);
3558 // the cloning operation will have produced a clone of the entire set of
3559 // documents, starting from the master. so we must delete those.
3560 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3562 busyBuffers.remove(orig);
3567 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3568 Buffer const * orig, Buffer * clone, string const & format)
3570 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3572 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3576 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3577 Buffer const * orig, Buffer * clone, string const & format)
3579 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3581 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3585 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3586 Buffer const * orig, Buffer * clone, string const & format)
3588 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3590 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3594 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3595 string const & argument,
3596 Buffer const * used_buffer,
3597 docstring const & msg,
3598 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3599 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3600 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3606 string format = argument;
3608 format = used_buffer->params().getDefaultOutputFormat();
3609 processing_format = format;
3611 progress_->clearMessages();
3614 #if EXPORT_in_THREAD
3616 GuiViewPrivate::busyBuffers.insert(used_buffer);
3617 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3618 if (!cloned_buffer) {
3619 Alert::error(_("Export Error"),
3620 _("Error cloning the Buffer."));
3623 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3628 setPreviewFuture(f);
3629 last_export_format = used_buffer->params().bufferFormat();
3632 // We are asynchronous, so we don't know here anything about the success
3635 Buffer::ExportStatus status;
3637 status = (used_buffer->*syncFunc)(format, false);
3638 } else if (previewFunc) {
3639 status = (used_buffer->*previewFunc)(format);
3642 handleExportStatus(gv_, status, format);
3644 return (status == Buffer::ExportSuccess
3645 || status == Buffer::PreviewSuccess);
3649 Buffer::ExportStatus status;
3651 status = (used_buffer->*syncFunc)(format, true);
3652 } else if (previewFunc) {
3653 status = (used_buffer->*previewFunc)(format);
3656 handleExportStatus(gv_, status, format);
3658 return (status == Buffer::ExportSuccess
3659 || status == Buffer::PreviewSuccess);
3663 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3665 BufferView * bv = currentBufferView();
3666 LASSERT(bv, return);
3668 // Let the current BufferView dispatch its own actions.
3669 bv->dispatch(cmd, dr);
3670 if (dr.dispatched())
3673 // Try with the document BufferView dispatch if any.
3674 BufferView * doc_bv = documentBufferView();
3675 if (doc_bv && doc_bv != bv) {
3676 doc_bv->dispatch(cmd, dr);
3677 if (dr.dispatched())
3681 // Then let the current Cursor dispatch its own actions.
3682 bv->cursor().dispatch(cmd);
3684 // update completion. We do it here and not in
3685 // processKeySym to avoid another redraw just for a
3686 // changed inline completion
3687 if (cmd.origin() == FuncRequest::KEYBOARD) {
3688 if (cmd.action() == LFUN_SELF_INSERT
3689 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3690 updateCompletion(bv->cursor(), true, true);
3691 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3692 updateCompletion(bv->cursor(), false, true);
3694 updateCompletion(bv->cursor(), false, false);
3697 dr = bv->cursor().result();
3701 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3703 BufferView * bv = currentBufferView();
3704 // By default we won't need any update.
3705 dr.screenUpdate(Update::None);
3706 // assume cmd will be dispatched
3707 dr.dispatched(true);
3709 Buffer * doc_buffer = documentBufferView()
3710 ? &(documentBufferView()->buffer()) : 0;
3712 if (cmd.origin() == FuncRequest::TOC) {
3713 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3714 // FIXME: do we need to pass a DispatchResult object here?
3715 toc->doDispatch(bv->cursor(), cmd);
3719 string const argument = to_utf8(cmd.argument());
3721 switch(cmd.action()) {
3722 case LFUN_BUFFER_CHILD_OPEN:
3723 openChildDocument(to_utf8(cmd.argument()));
3726 case LFUN_BUFFER_IMPORT:
3727 importDocument(to_utf8(cmd.argument()));
3730 case LFUN_MASTER_BUFFER_EXPORT:
3732 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3734 case LFUN_BUFFER_EXPORT: {
3737 // GCC only sees strfwd.h when building merged
3738 if (::lyx::operator==(cmd.argument(), "custom")) {
3739 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3740 // so the following test should not be needed.
3741 // In principle, we could try to switch to such a view...
3742 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3743 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3747 string const dest = cmd.getArg(1);
3748 FileName target_dir;
3749 if (!dest.empty() && FileName::isAbsolute(dest))
3750 target_dir = FileName(support::onlyPath(dest));
3752 target_dir = doc_buffer->fileName().onlyPath();
3754 string const format = (argument.empty() || argument == "default") ?
3755 doc_buffer->params().getDefaultOutputFormat() : argument;
3757 if ((dest.empty() && doc_buffer->isUnnamed())
3758 || !target_dir.isDirWritable()) {
3759 exportBufferAs(*doc_buffer, from_utf8(format));
3762 /* TODO/Review: Is it a problem to also export the children?
3763 See the update_unincluded flag */
3764 d.asyncBufferProcessing(format,
3767 &GuiViewPrivate::exportAndDestroy,
3769 0, cmd.allowAsync());
3770 // TODO Inform user about success
3774 case LFUN_BUFFER_EXPORT_AS: {
3775 LASSERT(doc_buffer, break);
3776 docstring f = cmd.argument();
3778 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3779 exportBufferAs(*doc_buffer, f);
3783 case LFUN_BUFFER_UPDATE: {
3784 d.asyncBufferProcessing(argument,
3787 &GuiViewPrivate::compileAndDestroy,
3789 0, cmd.allowAsync());
3792 case LFUN_BUFFER_VIEW: {
3793 d.asyncBufferProcessing(argument,
3795 _("Previewing ..."),
3796 &GuiViewPrivate::previewAndDestroy,
3798 &Buffer::preview, cmd.allowAsync());
3801 case LFUN_MASTER_BUFFER_UPDATE: {
3802 d.asyncBufferProcessing(argument,
3803 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3805 &GuiViewPrivate::compileAndDestroy,
3807 0, cmd.allowAsync());
3810 case LFUN_MASTER_BUFFER_VIEW: {
3811 d.asyncBufferProcessing(argument,
3812 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3814 &GuiViewPrivate::previewAndDestroy,
3815 0, &Buffer::preview, cmd.allowAsync());
3818 case LFUN_EXPORT_CANCEL: {
3819 Systemcall::killscript();
3822 case LFUN_BUFFER_SWITCH: {
3823 string const file_name = to_utf8(cmd.argument());
3824 if (!FileName::isAbsolute(file_name)) {
3826 dr.setMessage(_("Absolute filename expected."));
3830 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3833 dr.setMessage(_("Document not loaded"));
3837 // Do we open or switch to the buffer in this view ?
3838 if (workArea(*buffer)
3839 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3844 // Look for the buffer in other views
3845 QList<int> const ids = guiApp->viewIds();
3847 for (; i != ids.size(); ++i) {
3848 GuiView & gv = guiApp->view(ids[i]);
3849 if (gv.workArea(*buffer)) {
3851 gv.activateWindow();
3853 gv.setBuffer(buffer);
3858 // If necessary, open a new window as a last resort
3859 if (i == ids.size()) {
3860 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3866 case LFUN_BUFFER_NEXT:
3867 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3870 case LFUN_BUFFER_MOVE_NEXT:
3871 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3874 case LFUN_BUFFER_PREVIOUS:
3875 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3878 case LFUN_BUFFER_MOVE_PREVIOUS:
3879 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3882 case LFUN_BUFFER_CHKTEX:
3883 LASSERT(doc_buffer, break);
3884 doc_buffer->runChktex();
3887 case LFUN_COMMAND_EXECUTE: {
3888 command_execute_ = true;
3889 minibuffer_focus_ = true;
3892 case LFUN_DROP_LAYOUTS_CHOICE:
3893 d.layout_->showPopup();
3896 case LFUN_MENU_OPEN:
3897 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3898 menu->exec(QCursor::pos());
3901 case LFUN_FILE_INSERT:
3902 insertLyXFile(cmd.argument());
3905 case LFUN_FILE_INSERT_PLAINTEXT:
3906 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3907 string const fname = to_utf8(cmd.argument());
3908 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3909 dr.setMessage(_("Absolute filename expected."));
3913 FileName filename(fname);
3914 if (fname.empty()) {
3915 FileDialog dlg(qt_("Select file to insert"));
3917 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3918 QStringList(qt_("All Files (*)")));
3920 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3921 dr.setMessage(_("Canceled."));
3925 filename.set(fromqstr(result.second));
3929 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3930 bv->dispatch(new_cmd, dr);
3935 case LFUN_BUFFER_RELOAD: {
3936 LASSERT(doc_buffer, break);
3939 bool drop = (cmd.argument() == "dump");
3942 if (!drop && !doc_buffer->isClean()) {
3943 docstring const file =
3944 makeDisplayPath(doc_buffer->absFileName(), 20);
3945 if (doc_buffer->notifiesExternalModification()) {
3946 docstring text = _("The current version will be lost. "
3947 "Are you sure you want to load the version on disk "
3948 "of the document %1$s?");
3949 ret = Alert::prompt(_("Reload saved document?"),
3950 bformat(text, file), 1, 1,
3951 _("&Reload"), _("&Cancel"));
3953 docstring text = _("Any changes will be lost. "
3954 "Are you sure you want to revert to the saved version "
3955 "of the document %1$s?");
3956 ret = Alert::prompt(_("Revert to saved document?"),
3957 bformat(text, file), 1, 1,
3958 _("&Revert"), _("&Cancel"));
3963 doc_buffer->markClean();
3964 reloadBuffer(*doc_buffer);
3965 dr.forceBufferUpdate();
3970 case LFUN_BUFFER_WRITE:
3971 LASSERT(doc_buffer, break);
3972 saveBuffer(*doc_buffer);
3975 case LFUN_BUFFER_WRITE_AS:
3976 LASSERT(doc_buffer, break);
3977 renameBuffer(*doc_buffer, cmd.argument());
3980 case LFUN_BUFFER_WRITE_ALL: {
3981 Buffer * first = theBufferList().first();
3984 message(_("Saving all documents..."));
3985 // We cannot use a for loop as the buffer list cycles.
3988 if (!b->isClean()) {
3990 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3992 b = theBufferList().next(b);
3993 } while (b != first);
3994 dr.setMessage(_("All documents saved."));
3998 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3999 LASSERT(doc_buffer, break);
4000 doc_buffer->clearExternalModification();
4003 case LFUN_BUFFER_CLOSE:
4007 case LFUN_BUFFER_CLOSE_ALL:
4011 case LFUN_DEVEL_MODE_TOGGLE:
4012 devel_mode_ = !devel_mode_;
4014 dr.setMessage(_("Developer mode is now enabled."));
4016 dr.setMessage(_("Developer mode is now disabled."));
4019 case LFUN_TOOLBAR_TOGGLE: {
4020 string const name = cmd.getArg(0);
4021 if (GuiToolbar * t = toolbar(name))
4026 case LFUN_TOOLBAR_MOVABLE: {
4027 string const name = cmd.getArg(0);
4029 // toggle (all) toolbars movablility
4030 toolbarsMovable_ = !toolbarsMovable_;
4031 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4032 GuiToolbar * tb = toolbar(ti.name);
4033 if (tb && tb->isMovable() != toolbarsMovable_)
4034 // toggle toolbar movablity if it does not fit lock
4035 // (all) toolbars positions state silent = true, since
4036 // status bar notifications are slow
4039 if (toolbarsMovable_)
4040 dr.setMessage(_("Toolbars unlocked."));
4042 dr.setMessage(_("Toolbars locked."));
4043 } else if (GuiToolbar * t = toolbar(name)) {
4044 // toggle current toolbar movablity
4046 // update lock (all) toolbars positions
4047 updateLockToolbars();
4052 case LFUN_ICON_SIZE: {
4053 QSize size = d.iconSize(cmd.argument());
4055 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4056 size.width(), size.height()));
4060 case LFUN_DIALOG_UPDATE: {
4061 string const name = to_utf8(cmd.argument());
4062 if (name == "prefs" || name == "document")
4063 updateDialog(name, string());
4064 else if (name == "paragraph")
4065 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4066 else if (currentBufferView()) {
4067 Inset * inset = currentBufferView()->editedInset(name);
4068 // Can only update a dialog connected to an existing inset
4070 // FIXME: get rid of this indirection; GuiView ask the inset
4071 // if he is kind enough to update itself...
4072 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4073 //FIXME: pass DispatchResult here?
4074 inset->dispatch(currentBufferView()->cursor(), fr);
4080 case LFUN_DIALOG_TOGGLE: {
4081 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4082 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4083 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4087 case LFUN_DIALOG_DISCONNECT_INSET:
4088 disconnectDialog(to_utf8(cmd.argument()));
4091 case LFUN_DIALOG_HIDE: {
4092 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4096 case LFUN_DIALOG_SHOW: {
4097 string const name = cmd.getArg(0);
4098 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4100 if (name == "character") {
4101 sdata = freefont2string();
4103 showDialog("character", sdata);
4104 } else if (name == "latexlog") {
4105 // gettatus checks that
4106 LATTEST(doc_buffer);
4107 Buffer::LogType type;
4108 string const logfile = doc_buffer->logName(&type);
4110 case Buffer::latexlog:
4113 case Buffer::buildlog:
4114 sdata = "literate ";
4117 sdata += Lexer::quoteString(logfile);
4118 showDialog("log", sdata);
4119 } else if (name == "vclog") {
4120 // getStatus checks that
4121 LATTEST(doc_buffer);
4122 string const sdata2 = "vc " +
4123 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4124 showDialog("log", sdata2);
4125 } else if (name == "symbols") {
4126 sdata = bv->cursor().getEncoding()->name();
4128 showDialog("symbols", sdata);
4130 } else if (name == "prefs" && isFullScreen()) {
4131 lfunUiToggle("fullscreen");
4132 showDialog("prefs", sdata);
4134 showDialog(name, sdata);
4139 dr.setMessage(cmd.argument());
4142 case LFUN_UI_TOGGLE: {
4143 string arg = cmd.getArg(0);
4144 if (!lfunUiToggle(arg)) {
4145 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4146 dr.setMessage(bformat(msg, from_utf8(arg)));
4148 // Make sure the keyboard focus stays in the work area.
4153 case LFUN_VIEW_SPLIT: {
4154 LASSERT(doc_buffer, break);
4155 string const orientation = cmd.getArg(0);
4156 d.splitter_->setOrientation(orientation == "vertical"
4157 ? Qt::Vertical : Qt::Horizontal);
4158 TabWorkArea * twa = addTabWorkArea();
4159 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4160 setCurrentWorkArea(wa);
4163 case LFUN_TAB_GROUP_CLOSE:
4164 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4165 closeTabWorkArea(twa);
4166 d.current_work_area_ = 0;
4167 twa = d.currentTabWorkArea();
4168 // Switch to the next GuiWorkArea in the found TabWorkArea.
4170 // Make sure the work area is up to date.
4171 setCurrentWorkArea(twa->currentWorkArea());
4173 setCurrentWorkArea(0);
4178 case LFUN_VIEW_CLOSE:
4179 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4180 closeWorkArea(twa->currentWorkArea());
4181 d.current_work_area_ = 0;
4182 twa = d.currentTabWorkArea();
4183 // Switch to the next GuiWorkArea in the found TabWorkArea.
4185 // Make sure the work area is up to date.
4186 setCurrentWorkArea(twa->currentWorkArea());
4188 setCurrentWorkArea(0);
4193 case LFUN_COMPLETION_INLINE:
4194 if (d.current_work_area_)
4195 d.current_work_area_->completer().showInline();
4198 case LFUN_COMPLETION_POPUP:
4199 if (d.current_work_area_)
4200 d.current_work_area_->completer().showPopup();
4205 if (d.current_work_area_)
4206 d.current_work_area_->completer().tab();
4209 case LFUN_COMPLETION_CANCEL:
4210 if (d.current_work_area_) {
4211 if (d.current_work_area_->completer().popupVisible())
4212 d.current_work_area_->completer().hidePopup();
4214 d.current_work_area_->completer().hideInline();
4218 case LFUN_COMPLETION_ACCEPT:
4219 if (d.current_work_area_)
4220 d.current_work_area_->completer().activate();
4223 case LFUN_BUFFER_ZOOM_IN:
4224 case LFUN_BUFFER_ZOOM_OUT:
4225 case LFUN_BUFFER_ZOOM: {
4226 if (cmd.argument().empty()) {
4227 if (cmd.action() == LFUN_BUFFER_ZOOM)
4229 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4234 if (cmd.action() == LFUN_BUFFER_ZOOM)
4235 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4236 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4237 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4239 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4242 // Actual zoom value: default zoom + fractional extra value
4243 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4244 if (zoom < static_cast<int>(zoom_min_))
4247 lyxrc.currentZoom = zoom;
4249 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4250 lyxrc.currentZoom, lyxrc.defaultZoom));
4252 // The global QPixmapCache is used in GuiPainter to cache text
4253 // painting so we must reset it.
4254 QPixmapCache::clear();
4255 guiApp->fontLoader().update();
4256 dr.screenUpdate(Update::Force | Update::FitCursor);
4260 case LFUN_VC_REGISTER:
4261 case LFUN_VC_RENAME:
4263 case LFUN_VC_CHECK_IN:
4264 case LFUN_VC_CHECK_OUT:
4265 case LFUN_VC_REPO_UPDATE:
4266 case LFUN_VC_LOCKING_TOGGLE:
4267 case LFUN_VC_REVERT:
4268 case LFUN_VC_UNDO_LAST:
4269 case LFUN_VC_COMMAND:
4270 case LFUN_VC_COMPARE:
4271 dispatchVC(cmd, dr);
4274 case LFUN_SERVER_GOTO_FILE_ROW:
4275 if(goToFileRow(to_utf8(cmd.argument())))
4276 dr.screenUpdate(Update::Force | Update::FitCursor);
4279 case LFUN_LYX_ACTIVATE:
4283 case LFUN_FORWARD_SEARCH: {
4284 // it seems safe to assume we have a document buffer, since
4285 // getStatus wants one.
4286 LATTEST(doc_buffer);
4287 Buffer const * doc_master = doc_buffer->masterBuffer();
4288 FileName const path(doc_master->temppath());
4289 string const texname = doc_master->isChild(doc_buffer)
4290 ? DocFileName(changeExtension(
4291 doc_buffer->absFileName(),
4292 "tex")).mangledFileName()
4293 : doc_buffer->latexName();
4294 string const fulltexname =
4295 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4296 string const mastername =
4297 removeExtension(doc_master->latexName());
4298 FileName const dviname(addName(path.absFileName(),
4299 addExtension(mastername, "dvi")));
4300 FileName const pdfname(addName(path.absFileName(),
4301 addExtension(mastername, "pdf")));
4302 bool const have_dvi = dviname.exists();
4303 bool const have_pdf = pdfname.exists();
4304 if (!have_dvi && !have_pdf) {
4305 dr.setMessage(_("Please, preview the document first."));
4308 string outname = dviname.onlyFileName();
4309 string command = lyxrc.forward_search_dvi;
4310 if (!have_dvi || (have_pdf &&
4311 pdfname.lastModified() > dviname.lastModified())) {
4312 outname = pdfname.onlyFileName();
4313 command = lyxrc.forward_search_pdf;
4316 DocIterator cur = bv->cursor();
4317 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4318 LYXERR(Debug::ACTION, "Forward search: row:" << row
4320 if (row == -1 || command.empty()) {
4321 dr.setMessage(_("Couldn't proceed."));
4324 string texrow = convert<string>(row);
4326 command = subst(command, "$$n", texrow);
4327 command = subst(command, "$$f", fulltexname);
4328 command = subst(command, "$$t", texname);
4329 command = subst(command, "$$o", outname);
4331 PathChanger p(path);
4333 one.startscript(Systemcall::DontWait, command);
4337 case LFUN_SPELLING_CONTINUOUSLY:
4338 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4339 dr.screenUpdate(Update::Force);
4343 // The LFUN must be for one of BufferView, Buffer or Cursor;
4345 dispatchToBufferView(cmd, dr);
4349 // Part of automatic menu appearance feature.
4350 if (isFullScreen()) {
4351 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4355 // Need to update bv because many LFUNs here might have destroyed it
4356 bv = currentBufferView();
4358 // Clear non-empty selections
4359 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4361 Cursor & cur = bv->cursor();
4362 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4363 cur.clearSelection();
4369 bool GuiView::lfunUiToggle(string const & ui_component)
4371 if (ui_component == "scrollbar") {
4372 // hide() is of no help
4373 if (d.current_work_area_->verticalScrollBarPolicy() ==
4374 Qt::ScrollBarAlwaysOff)
4376 d.current_work_area_->setVerticalScrollBarPolicy(
4377 Qt::ScrollBarAsNeeded);
4379 d.current_work_area_->setVerticalScrollBarPolicy(
4380 Qt::ScrollBarAlwaysOff);
4381 } else if (ui_component == "statusbar") {
4382 statusBar()->setVisible(!statusBar()->isVisible());
4383 } else if (ui_component == "menubar") {
4384 menuBar()->setVisible(!menuBar()->isVisible());
4386 if (ui_component == "frame") {
4388 getContentsMargins(&l, &t, &r, &b);
4389 //are the frames in default state?
4390 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4392 setContentsMargins(-2, -2, -2, -2);
4394 setContentsMargins(0, 0, 0, 0);
4397 if (ui_component == "fullscreen") {
4405 void GuiView::toggleFullScreen()
4407 if (isFullScreen()) {
4408 for (int i = 0; i != d.splitter_->count(); ++i)
4409 d.tabWorkArea(i)->setFullScreen(false);
4410 setContentsMargins(0, 0, 0, 0);
4411 setWindowState(windowState() ^ Qt::WindowFullScreen);
4414 statusBar()->show();
4417 hideDialogs("prefs", 0);
4418 for (int i = 0; i != d.splitter_->count(); ++i)
4419 d.tabWorkArea(i)->setFullScreen(true);
4420 setContentsMargins(-2, -2, -2, -2);
4422 setWindowState(windowState() ^ Qt::WindowFullScreen);
4423 if (lyxrc.full_screen_statusbar)
4424 statusBar()->hide();
4425 if (lyxrc.full_screen_menubar)
4427 if (lyxrc.full_screen_toolbars) {
4428 ToolbarMap::iterator end = d.toolbars_.end();
4429 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4434 // give dialogs like the TOC a chance to adapt
4439 Buffer const * GuiView::updateInset(Inset const * inset)
4444 Buffer const * inset_buffer = &(inset->buffer());
4446 for (int i = 0; i != d.splitter_->count(); ++i) {
4447 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4450 Buffer const * buffer = &(wa->bufferView().buffer());
4451 if (inset_buffer == buffer)
4452 wa->scheduleRedraw(true);
4454 return inset_buffer;
4458 void GuiView::restartCaret()
4460 /* When we move around, or type, it's nice to be able to see
4461 * the caret immediately after the keypress.
4463 if (d.current_work_area_)
4464 d.current_work_area_->startBlinkingCaret();
4466 // Take this occasion to update the other GUI elements.
4472 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4474 if (d.current_work_area_)
4475 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4480 // This list should be kept in sync with the list of insets in
4481 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4482 // dialog should have the same name as the inset.
4483 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4484 // docs in LyXAction.cpp.
4486 char const * const dialognames[] = {
4488 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4489 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4490 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4491 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4492 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4493 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4494 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4495 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4497 char const * const * const end_dialognames =
4498 dialognames + (sizeof(dialognames) / sizeof(char *));
4502 cmpCStr(char const * name) : name_(name) {}
4503 bool operator()(char const * other) {
4504 return strcmp(other, name_) == 0;
4511 bool isValidName(string const & name)
4513 return find_if(dialognames, end_dialognames,
4514 cmpCStr(name.c_str())) != end_dialognames;
4520 void GuiView::resetDialogs()
4522 // Make sure that no LFUN uses any GuiView.
4523 guiApp->setCurrentView(0);
4527 constructToolbars();
4528 guiApp->menus().fillMenuBar(menuBar(), this, false);
4529 d.layout_->updateContents(true);
4530 // Now update controls with current buffer.
4531 guiApp->setCurrentView(this);
4537 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4539 if (!isValidName(name))
4542 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4544 if (it != d.dialogs_.end()) {
4546 it->second->hideView();
4547 return it->second.get();
4550 Dialog * dialog = build(name);
4551 d.dialogs_[name].reset(dialog);
4552 if (lyxrc.allow_geometry_session)
4553 dialog->restoreSession();
4560 void GuiView::showDialog(string const & name, string const & sdata,
4563 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4567 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4573 const string name = fromqstr(qname);
4574 const string sdata = fromqstr(qdata);
4578 Dialog * dialog = findOrBuild(name, false);
4580 bool const visible = dialog->isVisibleView();
4581 dialog->showData(sdata);
4582 if (inset && currentBufferView())
4583 currentBufferView()->editInset(name, inset);
4584 // We only set the focus to the new dialog if it was not yet
4585 // visible in order not to change the existing previous behaviour
4587 // activateWindow is needed for floating dockviews
4588 dialog->asQWidget()->raise();
4589 dialog->asQWidget()->activateWindow();
4590 dialog->asQWidget()->setFocus();
4594 catch (ExceptionMessage const & ex) {
4602 bool GuiView::isDialogVisible(string const & name) const
4604 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4605 if (it == d.dialogs_.end())
4607 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4611 void GuiView::hideDialog(string const & name, Inset * inset)
4613 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4614 if (it == d.dialogs_.end())
4618 if (!currentBufferView())
4620 if (inset != currentBufferView()->editedInset(name))
4624 Dialog * const dialog = it->second.get();
4625 if (dialog->isVisibleView())
4627 if (currentBufferView())
4628 currentBufferView()->editInset(name, 0);
4632 void GuiView::disconnectDialog(string const & name)
4634 if (!isValidName(name))
4636 if (currentBufferView())
4637 currentBufferView()->editInset(name, 0);
4641 void GuiView::hideAll() const
4643 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4644 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4646 for(; it != end; ++it)
4647 it->second->hideView();
4651 void GuiView::updateDialogs()
4653 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4654 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4656 for(; it != end; ++it) {
4657 Dialog * dialog = it->second.get();
4659 if (dialog->needBufferOpen() && !documentBufferView())
4660 hideDialog(fromqstr(dialog->name()), 0);
4661 else if (dialog->isVisibleView())
4662 dialog->checkStatus();
4669 Dialog * createDialog(GuiView & lv, string const & name);
4671 // will be replaced by a proper factory...
4672 Dialog * createGuiAbout(GuiView & lv);
4673 Dialog * createGuiBibtex(GuiView & lv);
4674 Dialog * createGuiChanges(GuiView & lv);
4675 Dialog * createGuiCharacter(GuiView & lv);
4676 Dialog * createGuiCitation(GuiView & lv);
4677 Dialog * createGuiCompare(GuiView & lv);
4678 Dialog * createGuiCompareHistory(GuiView & lv);
4679 Dialog * createGuiDelimiter(GuiView & lv);
4680 Dialog * createGuiDocument(GuiView & lv);
4681 Dialog * createGuiErrorList(GuiView & lv);
4682 Dialog * createGuiExternal(GuiView & lv);
4683 Dialog * createGuiGraphics(GuiView & lv);
4684 Dialog * createGuiInclude(GuiView & lv);
4685 Dialog * createGuiIndex(GuiView & lv);
4686 Dialog * createGuiListings(GuiView & lv);
4687 Dialog * createGuiLog(GuiView & lv);
4688 Dialog * createGuiMathMatrix(GuiView & lv);
4689 Dialog * createGuiNote(GuiView & lv);
4690 Dialog * createGuiParagraph(GuiView & lv);
4691 Dialog * createGuiPhantom(GuiView & lv);
4692 Dialog * createGuiPreferences(GuiView & lv);
4693 Dialog * createGuiPrint(GuiView & lv);
4694 Dialog * createGuiPrintindex(GuiView & lv);
4695 Dialog * createGuiRef(GuiView & lv);
4696 Dialog * createGuiSearch(GuiView & lv);
4697 Dialog * createGuiSearchAdv(GuiView & lv);
4698 Dialog * createGuiSendTo(GuiView & lv);
4699 Dialog * createGuiShowFile(GuiView & lv);
4700 Dialog * createGuiSpellchecker(GuiView & lv);
4701 Dialog * createGuiSymbols(GuiView & lv);
4702 Dialog * createGuiTabularCreate(GuiView & lv);
4703 Dialog * createGuiTexInfo(GuiView & lv);
4704 Dialog * createGuiToc(GuiView & lv);
4705 Dialog * createGuiThesaurus(GuiView & lv);
4706 Dialog * createGuiViewSource(GuiView & lv);
4707 Dialog * createGuiWrap(GuiView & lv);
4708 Dialog * createGuiProgressView(GuiView & lv);
4712 Dialog * GuiView::build(string const & name)
4714 LASSERT(isValidName(name), return 0);
4716 Dialog * dialog = createDialog(*this, name);
4720 if (name == "aboutlyx")
4721 return createGuiAbout(*this);
4722 if (name == "bibtex")
4723 return createGuiBibtex(*this);
4724 if (name == "changes")
4725 return createGuiChanges(*this);
4726 if (name == "character")
4727 return createGuiCharacter(*this);
4728 if (name == "citation")
4729 return createGuiCitation(*this);
4730 if (name == "compare")
4731 return createGuiCompare(*this);
4732 if (name == "comparehistory")
4733 return createGuiCompareHistory(*this);
4734 if (name == "document")
4735 return createGuiDocument(*this);
4736 if (name == "errorlist")
4737 return createGuiErrorList(*this);
4738 if (name == "external")
4739 return createGuiExternal(*this);
4741 return createGuiShowFile(*this);
4742 if (name == "findreplace")
4743 return createGuiSearch(*this);
4744 if (name == "findreplaceadv")
4745 return createGuiSearchAdv(*this);
4746 if (name == "graphics")
4747 return createGuiGraphics(*this);
4748 if (name == "include")
4749 return createGuiInclude(*this);
4750 if (name == "index")
4751 return createGuiIndex(*this);
4752 if (name == "index_print")
4753 return createGuiPrintindex(*this);
4754 if (name == "listings")
4755 return createGuiListings(*this);
4757 return createGuiLog(*this);
4758 if (name == "mathdelimiter")
4759 return createGuiDelimiter(*this);
4760 if (name == "mathmatrix")
4761 return createGuiMathMatrix(*this);
4763 return createGuiNote(*this);
4764 if (name == "paragraph")
4765 return createGuiParagraph(*this);
4766 if (name == "phantom")
4767 return createGuiPhantom(*this);
4768 if (name == "prefs")
4769 return createGuiPreferences(*this);
4771 return createGuiRef(*this);
4772 if (name == "sendto")
4773 return createGuiSendTo(*this);
4774 if (name == "spellchecker")
4775 return createGuiSpellchecker(*this);
4776 if (name == "symbols")
4777 return createGuiSymbols(*this);
4778 if (name == "tabularcreate")
4779 return createGuiTabularCreate(*this);
4780 if (name == "texinfo")
4781 return createGuiTexInfo(*this);
4782 if (name == "thesaurus")
4783 return createGuiThesaurus(*this);
4785 return createGuiToc(*this);
4786 if (name == "view-source")
4787 return createGuiViewSource(*this);
4789 return createGuiWrap(*this);
4790 if (name == "progress")
4791 return createGuiProgressView(*this);
4797 SEMenu::SEMenu(QWidget * parent)
4799 QAction * action = addAction(qt_("Disable Shell Escape"));
4800 connect(action, SIGNAL(triggered()),
4801 parent, SLOT(disableShellEscape()));
4804 } // namespace frontend
4807 #include "moc_GuiView.cpp"