3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
56 #include "LyXAction.h"
60 #include "Paragraph.h"
61 #include "SpellChecker.h"
64 #include "TextClass.h"
69 #include "support/convert.h"
70 #include "support/debug.h"
71 #include "support/ExceptionMessage.h"
72 #include "support/FileName.h"
73 #include "support/filetools.h"
74 #include "support/gettext.h"
75 #include "support/filetools.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
90 #include <QDesktopWidget>
91 #include <QDragEnterEvent>
94 #include <QFutureWatcher>
103 #include <QPixmapCache>
105 #include <QPushButton>
106 #include <QScrollBar>
108 #include <QShowEvent>
110 #include <QStackedWidget>
111 #include <QStatusBar>
112 #include <QSvgRenderer>
113 #include <QtConcurrentRun>
121 // sync with GuiAlert.cpp
122 #define EXPORT_in_THREAD 1
125 #include "support/bind.h"
129 #ifdef HAVE_SYS_TIME_H
130 # include <sys/time.h>
138 using namespace lyx::support;
142 using support::addExtension;
143 using support::changeExtension;
144 using support::removeExtension;
150 class BackgroundWidget : public QWidget
153 BackgroundWidget(int width, int height)
154 : width_(width), height_(height)
156 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
157 if (!lyxrc.show_banner)
159 /// The text to be written on top of the pixmap
160 QString const text = lyx_version ?
161 qt_("version ") + lyx_version : qt_("unknown version");
162 #if QT_VERSION >= 0x050000
163 QString imagedir = "images/";
164 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
165 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
166 if (svgRenderer.isValid()) {
167 splash_ = QPixmap(splashSize());
168 QPainter painter(&splash_);
169 svgRenderer.render(&painter);
170 splash_.setDevicePixelRatio(pixelRatio());
172 splash_ = getPixmap("images/", "banner", "png");
175 splash_ = getPixmap("images/", "banner", "svgz,png");
178 QPainter pain(&splash_);
179 pain.setPen(QColor(0, 0, 0));
180 qreal const fsize = fontSize();
181 QPointF const position = textPosition();
183 "widget pixel ratio: " << pixelRatio() <<
184 " splash pixel ratio: " << splashPixelRatio() <<
185 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
187 // The font used to display the version info
188 font.setStyleHint(QFont::SansSerif);
189 font.setWeight(QFont::Bold);
190 font.setPointSizeF(fsize);
192 pain.drawText(position, text);
193 setFocusPolicy(Qt::StrongFocus);
196 void paintEvent(QPaintEvent *)
198 int const w = width_;
199 int const h = height_;
200 int const x = (width() - w) / 2;
201 int const y = (height() - h) / 2;
203 "widget pixel ratio: " << pixelRatio() <<
204 " splash pixel ratio: " << splashPixelRatio() <<
205 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
207 pain.drawPixmap(x, y, w, h, splash_);
210 void keyPressEvent(QKeyEvent * ev)
213 setKeySymbol(&sym, ev);
215 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
227 /// Current ratio between physical pixels and device-independent pixels
228 double pixelRatio() const {
229 #if QT_VERSION >= 0x050000
230 return qt_scale_factor * devicePixelRatio();
236 qreal fontSize() const {
237 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
240 QPointF textPosition() const {
241 return QPointF(width_/2 - 18, height_/2 + 45);
244 QSize splashSize() const {
246 static_cast<unsigned int>(width_ * pixelRatio()),
247 static_cast<unsigned int>(height_ * pixelRatio()));
250 /// Ratio between physical pixels and device-independent pixels of splash image
251 double splashPixelRatio() const {
252 #if QT_VERSION >= 0x050000
253 return splash_.devicePixelRatio();
261 /// Toolbar store providing access to individual toolbars by name.
262 typedef map<string, GuiToolbar *> ToolbarMap;
264 typedef shared_ptr<Dialog> DialogPtr;
269 class GuiView::GuiViewPrivate
272 GuiViewPrivate(GuiViewPrivate const &);
273 void operator=(GuiViewPrivate const &);
275 GuiViewPrivate(GuiView * gv)
276 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
277 layout_(0), autosave_timeout_(5000),
280 // hardcode here the platform specific icon size
281 smallIconSize = 16; // scaling problems
282 normalIconSize = 20; // ok, default if iconsize.png is missing
283 bigIconSize = 26; // better for some math icons
284 hugeIconSize = 32; // better for hires displays
287 // if it exists, use width of iconsize.png as normal size
288 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
289 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
291 QImage image(toqstr(fn.absFileName()));
292 if (image.width() < int(smallIconSize))
293 normalIconSize = smallIconSize;
294 else if (image.width() > int(giantIconSize))
295 normalIconSize = giantIconSize;
297 normalIconSize = image.width();
300 splitter_ = new QSplitter;
301 bg_widget_ = new BackgroundWidget(400, 250);
302 stack_widget_ = new QStackedWidget;
303 stack_widget_->addWidget(bg_widget_);
304 stack_widget_->addWidget(splitter_);
307 // TODO cleanup, remove the singleton, handle multiple Windows?
308 progress_ = ProgressInterface::instance();
309 if (!dynamic_cast<GuiProgress*>(progress_)) {
310 progress_ = new GuiProgress; // TODO who deletes it
311 ProgressInterface::setInstance(progress_);
314 dynamic_cast<GuiProgress*>(progress_),
315 SIGNAL(updateStatusBarMessage(QString const&)),
316 gv, SLOT(updateStatusBarMessage(QString const&)));
318 dynamic_cast<GuiProgress*>(progress_),
319 SIGNAL(clearMessageText()),
320 gv, SLOT(clearMessageText()));
327 delete stack_widget_;
332 stack_widget_->setCurrentWidget(bg_widget_);
333 bg_widget_->setUpdatesEnabled(true);
334 bg_widget_->setFocus();
337 int tabWorkAreaCount()
339 return splitter_->count();
342 TabWorkArea * tabWorkArea(int i)
344 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
347 TabWorkArea * currentTabWorkArea()
349 int areas = tabWorkAreaCount();
351 // The first TabWorkArea is always the first one, if any.
352 return tabWorkArea(0);
354 for (int i = 0; i != areas; ++i) {
355 TabWorkArea * twa = tabWorkArea(i);
356 if (current_main_work_area_ == twa->currentWorkArea())
360 // None has the focus so we just take the first one.
361 return tabWorkArea(0);
364 int countWorkAreasOf(Buffer & buf)
366 int areas = tabWorkAreaCount();
368 for (int i = 0; i != areas; ++i) {
369 TabWorkArea * twa = tabWorkArea(i);
370 if (twa->workArea(buf))
376 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
378 if (processing_thread_watcher_.isRunning()) {
379 // we prefer to cancel this preview in order to keep a snappy
383 processing_thread_watcher_.setFuture(f);
386 QSize iconSize(docstring const & icon_size)
389 if (icon_size == "small")
390 size = smallIconSize;
391 else if (icon_size == "normal")
392 size = normalIconSize;
393 else if (icon_size == "big")
395 else if (icon_size == "huge")
397 else if (icon_size == "giant")
398 size = giantIconSize;
400 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
402 if (size < smallIconSize)
403 size = smallIconSize;
405 return QSize(size, size);
408 QSize iconSize(QString const & icon_size)
410 return iconSize(qstring_to_ucs4(icon_size));
413 string & iconSize(QSize const & qsize)
415 LATTEST(qsize.width() == qsize.height());
417 static string icon_size;
419 unsigned int size = qsize.width();
421 if (size < smallIconSize)
422 size = smallIconSize;
424 if (size == smallIconSize)
426 else if (size == normalIconSize)
427 icon_size = "normal";
428 else if (size == bigIconSize)
430 else if (size == hugeIconSize)
432 else if (size == giantIconSize)
435 icon_size = convert<string>(size);
442 GuiWorkArea * current_work_area_;
443 GuiWorkArea * current_main_work_area_;
444 QSplitter * splitter_;
445 QStackedWidget * stack_widget_;
446 BackgroundWidget * bg_widget_;
448 ToolbarMap toolbars_;
449 ProgressInterface* progress_;
450 /// The main layout box.
452 * \warning Don't Delete! The layout box is actually owned by
453 * whichever toolbar contains it. All the GuiView class needs is a
454 * means of accessing it.
456 * FIXME: replace that with a proper model so that we are not limited
457 * to only one dialog.
462 map<string, DialogPtr> dialogs_;
464 unsigned int smallIconSize;
465 unsigned int normalIconSize;
466 unsigned int bigIconSize;
467 unsigned int hugeIconSize;
468 unsigned int giantIconSize;
470 QTimer statusbar_timer_;
471 /// auto-saving of buffers
472 Timeout autosave_timeout_;
473 /// flag against a race condition due to multiclicks, see bug #1119
477 TocModels toc_models_;
480 QFutureWatcher<docstring> autosave_watcher_;
481 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
483 string last_export_format;
484 string processing_format;
486 static QSet<Buffer const *> busyBuffers;
487 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
488 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
489 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
490 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
493 static Buffer::ExportStatus runAndDestroy(const T& func, Buffer const * orig, Buffer * buffer, string const & format);
495 // TODO syncFunc/previewFunc: use bind
496 bool asyncBufferProcessing(string const & argument,
497 Buffer const * used_buffer,
498 docstring const & msg,
499 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
500 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
501 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const);
503 QVector<GuiWorkArea*> guiWorkAreas();
506 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
509 GuiView::GuiView(int id)
510 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
511 command_execute_(false), minibuffer_focus_(false), devel_mode_(false)
513 connect(this, SIGNAL(bufferViewChanged()),
514 this, SLOT(onBufferViewChanged()));
516 // GuiToolbars *must* be initialised before the menu bar.
517 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
520 // set ourself as the current view. This is needed for the menu bar
521 // filling, at least for the static special menu item on Mac. Otherwise
522 // they are greyed out.
523 guiApp->setCurrentView(this);
525 // Fill up the menu bar.
526 guiApp->menus().fillMenuBar(menuBar(), this, true);
528 setCentralWidget(d.stack_widget_);
530 // Start autosave timer
531 if (lyxrc.autosave) {
532 // The connection is closed when this is destroyed.
533 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
534 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
535 d.autosave_timeout_.start();
537 connect(&d.statusbar_timer_, SIGNAL(timeout()),
538 this, SLOT(clearMessage()));
540 // We don't want to keep the window in memory if it is closed.
541 setAttribute(Qt::WA_DeleteOnClose, true);
543 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
544 // QIcon::fromTheme was introduced in Qt 4.6
545 #if (QT_VERSION >= 0x040600)
546 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
547 // since the icon is provided in the application bundle. We use a themed
548 // version when available and use the bundled one as fallback.
549 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
551 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
557 // use tabbed dock area for multiple docks
558 // (such as "source" and "messages")
559 setDockOptions(QMainWindow::ForceTabbedDocks);
562 setAcceptDrops(true);
564 // add busy indicator to statusbar
565 QLabel * busylabel = new QLabel(statusBar());
566 statusBar()->addPermanentWidget(busylabel);
567 search_mode mode = theGuiApp()->imageSearchMode();
568 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
569 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
570 busylabel->setMovie(busyanim);
574 connect(&d.processing_thread_watcher_, SIGNAL(started()),
575 busylabel, SLOT(show()));
576 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
577 busylabel, SLOT(hide()));
579 QFontMetrics const fm(statusBar()->fontMetrics());
580 int const iconheight = max(int(d.normalIconSize), fm.height());
581 QSize const iconsize(iconheight, iconheight);
583 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
584 shell_escape_ = new QLabel(statusBar());
585 shell_escape_->setPixmap(shellescape);
586 shell_escape_->setScaledContents(true);
587 shell_escape_->setAlignment(Qt::AlignCenter);
588 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
589 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
590 "external commands for this document. "
591 "Right click to change."));
592 SEMenu * menu = new SEMenu(this);
593 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
594 menu, SLOT(showMenu(QPoint)));
595 shell_escape_->hide();
596 statusBar()->addPermanentWidget(shell_escape_);
598 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
599 read_only_ = new QLabel(statusBar());
600 read_only_->setPixmap(readonly);
601 read_only_->setScaledContents(true);
602 read_only_->setAlignment(Qt::AlignCenter);
604 statusBar()->addPermanentWidget(read_only_);
606 version_control_ = new QLabel(statusBar());
607 version_control_->setAlignment(Qt::AlignCenter);
608 version_control_->setFrameStyle(QFrame::StyledPanel);
609 version_control_->hide();
610 statusBar()->addPermanentWidget(version_control_);
612 statusBar()->setSizeGripEnabled(true);
615 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
616 SLOT(autoSaveThreadFinished()));
618 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
619 SLOT(processingThreadStarted()));
620 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
621 SLOT(processingThreadFinished()));
623 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
624 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
626 // set custom application bars context menu, e.g. tool bar and menu bar
627 setContextMenuPolicy(Qt::CustomContextMenu);
628 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
629 SLOT(toolBarPopup(const QPoint &)));
631 // Forbid too small unresizable window because it can happen
632 // with some window manager under X11.
633 setMinimumSize(300, 200);
635 if (lyxrc.allow_geometry_session) {
636 // Now take care of session management.
641 // no session handling, default to a sane size.
642 setGeometry(50, 50, 690, 510);
645 // clear session data if any.
647 settings.remove("views");
657 void GuiView::disableShellEscape()
659 BufferView * bv = documentBufferView();
662 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
663 bv->buffer().params().shell_escape = false;
664 bv->processUpdateFlags(Update::Force);
668 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
670 QVector<GuiWorkArea*> areas;
671 for (int i = 0; i < tabWorkAreaCount(); i++) {
672 TabWorkArea* ta = tabWorkArea(i);
673 for (int u = 0; u < ta->count(); u++) {
674 areas << ta->workArea(u);
680 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
681 string const & format)
683 docstring const fmt = theFormats().prettyName(format);
686 case Buffer::ExportSuccess:
687 msg = bformat(_("Successful export to format: %1$s"), fmt);
689 case Buffer::ExportCancel:
690 msg = _("Document export cancelled.");
692 case Buffer::ExportError:
693 case Buffer::ExportNoPathToFormat:
694 case Buffer::ExportTexPathHasSpaces:
695 case Buffer::ExportConverterError:
696 msg = bformat(_("Error while exporting format: %1$s"), fmt);
698 case Buffer::PreviewSuccess:
699 msg = bformat(_("Successful preview of format: %1$s"), fmt);
701 case Buffer::PreviewError:
702 msg = bformat(_("Error while previewing format: %1$s"), fmt);
709 void GuiView::processingThreadStarted()
714 void GuiView::processingThreadFinished()
716 QFutureWatcher<Buffer::ExportStatus> const * watcher =
717 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
719 Buffer::ExportStatus const status = watcher->result();
720 handleExportStatus(this, status, d.processing_format);
723 BufferView const * const bv = currentBufferView();
724 if (bv && !bv->buffer().errorList("Export").empty()) {
729 bool const error = (status != Buffer::ExportSuccess &&
730 status != Buffer::PreviewSuccess &&
731 status != Buffer::ExportCancel);
733 ErrorList & el = bv->buffer().errorList(d.last_export_format);
734 // at this point, we do not know if buffer-view or
735 // master-buffer-view was called. If there was an export error,
736 // and the current buffer's error log is empty, we guess that
737 // it must be master-buffer-view that was called so we set
739 errors(d.last_export_format, el.empty());
744 void GuiView::autoSaveThreadFinished()
746 QFutureWatcher<docstring> const * watcher =
747 static_cast<QFutureWatcher<docstring> const *>(sender());
748 message(watcher->result());
753 void GuiView::saveLayout() const
756 settings.setValue("zoom_ratio", zoom_ratio_);
757 settings.setValue("devel_mode", devel_mode_);
758 settings.beginGroup("views");
759 settings.beginGroup(QString::number(id_));
760 #if defined(Q_WS_X11) || defined(QPA_XCB)
761 settings.setValue("pos", pos());
762 settings.setValue("size", size());
764 settings.setValue("geometry", saveGeometry());
766 settings.setValue("layout", saveState(0));
767 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
771 void GuiView::saveUISettings() const
775 // Save the toolbar private states
776 ToolbarMap::iterator end = d.toolbars_.end();
777 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
778 it->second->saveSession(settings);
779 // Now take care of all other dialogs
780 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
781 for (; it!= d.dialogs_.end(); ++it)
782 it->second->saveSession(settings);
786 bool GuiView::restoreLayout()
789 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
790 // Actual zoom value: default zoom + fractional offset
791 int zoom = lyxrc.defaultZoom * zoom_ratio_;
792 if (zoom < static_cast<int>(zoom_min_))
794 lyxrc.currentZoom = zoom;
795 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
796 settings.beginGroup("views");
797 settings.beginGroup(QString::number(id_));
798 QString const icon_key = "icon_size";
799 if (!settings.contains(icon_key))
802 //code below is skipped when when ~/.config/LyX is (re)created
803 setIconSize(d.iconSize(settings.value(icon_key).toString()));
805 #if defined(Q_WS_X11) || defined(QPA_XCB)
806 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
807 QSize size = settings.value("size", QSize(690, 510)).toSize();
811 // Work-around for bug #6034: the window ends up in an undetermined
812 // state when trying to restore a maximized window when it is
813 // already maximized.
814 if (!(windowState() & Qt::WindowMaximized))
815 if (!restoreGeometry(settings.value("geometry").toByteArray()))
816 setGeometry(50, 50, 690, 510);
818 // Make sure layout is correctly oriented.
819 setLayoutDirection(qApp->layoutDirection());
821 // Allow the toc and view-source dock widget to be restored if needed.
823 if ((dialog = findOrBuild("toc", true)))
824 // see bug 5082. At least setup title and enabled state.
825 // Visibility will be adjusted by restoreState below.
826 dialog->prepareView();
827 if ((dialog = findOrBuild("view-source", true)))
828 dialog->prepareView();
829 if ((dialog = findOrBuild("progress", true)))
830 dialog->prepareView();
832 if (!restoreState(settings.value("layout").toByteArray(), 0))
835 // init the toolbars that have not been restored
836 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
837 Toolbars::Infos::iterator end = guiApp->toolbars().end();
838 for (; cit != end; ++cit) {
839 GuiToolbar * tb = toolbar(cit->name);
840 if (tb && !tb->isRestored())
841 initToolbar(cit->name);
844 // update lock (all) toolbars positions
845 updateLockToolbars();
852 GuiToolbar * GuiView::toolbar(string const & name)
854 ToolbarMap::iterator it = d.toolbars_.find(name);
855 if (it != d.toolbars_.end())
858 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
863 void GuiView::updateLockToolbars()
865 toolbarsMovable_ = false;
866 for (ToolbarInfo const & info : guiApp->toolbars()) {
867 GuiToolbar * tb = toolbar(info.name);
868 if (tb && tb->isMovable())
869 toolbarsMovable_ = true;
874 void GuiView::constructToolbars()
876 ToolbarMap::iterator it = d.toolbars_.begin();
877 for (; it != d.toolbars_.end(); ++it)
881 // I don't like doing this here, but the standard toolbar
882 // destroys this object when it's destroyed itself (vfr)
883 d.layout_ = new LayoutBox(*this);
884 d.stack_widget_->addWidget(d.layout_);
885 d.layout_->move(0,0);
887 // extracts the toolbars from the backend
888 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
889 Toolbars::Infos::iterator end = guiApp->toolbars().end();
890 for (; cit != end; ++cit)
891 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
895 void GuiView::initToolbars()
897 // extracts the toolbars from the backend
898 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
899 Toolbars::Infos::iterator end = guiApp->toolbars().end();
900 for (; cit != end; ++cit)
901 initToolbar(cit->name);
905 void GuiView::initToolbar(string const & name)
907 GuiToolbar * tb = toolbar(name);
910 int const visibility = guiApp->toolbars().defaultVisibility(name);
911 bool newline = !(visibility & Toolbars::SAMEROW);
912 tb->setVisible(false);
913 tb->setVisibility(visibility);
915 if (visibility & Toolbars::TOP) {
917 addToolBarBreak(Qt::TopToolBarArea);
918 addToolBar(Qt::TopToolBarArea, tb);
921 if (visibility & Toolbars::BOTTOM) {
923 addToolBarBreak(Qt::BottomToolBarArea);
924 addToolBar(Qt::BottomToolBarArea, tb);
927 if (visibility & Toolbars::LEFT) {
929 addToolBarBreak(Qt::LeftToolBarArea);
930 addToolBar(Qt::LeftToolBarArea, tb);
933 if (visibility & Toolbars::RIGHT) {
935 addToolBarBreak(Qt::RightToolBarArea);
936 addToolBar(Qt::RightToolBarArea, tb);
939 if (visibility & Toolbars::ON)
940 tb->setVisible(true);
942 tb->setMovable(true);
946 TocModels & GuiView::tocModels()
948 return d.toc_models_;
952 void GuiView::setFocus()
954 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
955 QMainWindow::setFocus();
959 bool GuiView::hasFocus() const
961 if (currentWorkArea())
962 return currentWorkArea()->hasFocus();
963 if (currentMainWorkArea())
964 return currentMainWorkArea()->hasFocus();
965 return d.bg_widget_->hasFocus();
969 void GuiView::focusInEvent(QFocusEvent * e)
971 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
972 QMainWindow::focusInEvent(e);
973 // Make sure guiApp points to the correct view.
974 guiApp->setCurrentView(this);
975 if (currentWorkArea())
976 currentWorkArea()->setFocus();
977 else if (currentMainWorkArea())
978 currentMainWorkArea()->setFocus();
980 d.bg_widget_->setFocus();
984 void GuiView::showEvent(QShowEvent * e)
986 LYXERR(Debug::GUI, "Passed Geometry "
987 << size().height() << "x" << size().width()
988 << "+" << pos().x() << "+" << pos().y());
990 if (d.splitter_->count() == 0)
991 // No work area, switch to the background widget.
995 QMainWindow::showEvent(e);
999 bool GuiView::closeScheduled()
1006 bool GuiView::prepareAllBuffersForLogout()
1008 Buffer * first = theBufferList().first();
1012 // First, iterate over all buffers and ask the users if unsaved
1013 // changes should be saved.
1014 // We cannot use a for loop as the buffer list cycles.
1017 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1019 b = theBufferList().next(b);
1020 } while (b != first);
1022 // Next, save session state
1023 // When a view/window was closed before without quitting LyX, there
1024 // are already entries in the lastOpened list.
1025 theSession().lastOpened().clear();
1032 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1033 ** is responsibility of the container (e.g., dialog)
1035 void GuiView::closeEvent(QCloseEvent * close_event)
1037 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1039 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1040 Alert::warning(_("Exit LyX"),
1041 _("LyX could not be closed because documents are being processed by LyX."));
1042 close_event->setAccepted(false);
1046 // If the user pressed the x (so we didn't call closeView
1047 // programmatically), we want to clear all existing entries.
1049 theSession().lastOpened().clear();
1054 // it can happen that this event arrives without selecting the view,
1055 // e.g. when clicking the close button on a background window.
1057 if (!closeWorkAreaAll()) {
1059 close_event->ignore();
1063 // Make sure that nothing will use this to be closed View.
1064 guiApp->unregisterView(this);
1066 if (isFullScreen()) {
1067 // Switch off fullscreen before closing.
1072 // Make sure the timer time out will not trigger a statusbar update.
1073 d.statusbar_timer_.stop();
1075 // Saving fullscreen requires additional tweaks in the toolbar code.
1076 // It wouldn't also work under linux natively.
1077 if (lyxrc.allow_geometry_session) {
1082 close_event->accept();
1086 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1088 if (event->mimeData()->hasUrls())
1090 /// \todo Ask lyx-devel is this is enough:
1091 /// if (event->mimeData()->hasFormat("text/plain"))
1092 /// event->acceptProposedAction();
1096 void GuiView::dropEvent(QDropEvent * event)
1098 QList<QUrl> files = event->mimeData()->urls();
1099 if (files.isEmpty())
1102 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1103 for (int i = 0; i != files.size(); ++i) {
1104 string const file = os::internal_path(fromqstr(
1105 files.at(i).toLocalFile()));
1109 string const ext = support::getExtension(file);
1110 vector<const Format *> found_formats;
1112 // Find all formats that have the correct extension.
1113 vector<const Format *> const & import_formats
1114 = theConverters().importableFormats();
1115 vector<const Format *>::const_iterator it = import_formats.begin();
1116 for (; it != import_formats.end(); ++it)
1117 if ((*it)->hasExtension(ext))
1118 found_formats.push_back(*it);
1121 if (found_formats.size() >= 1) {
1122 if (found_formats.size() > 1) {
1123 //FIXME: show a dialog to choose the correct importable format
1124 LYXERR(Debug::FILES,
1125 "Multiple importable formats found, selecting first");
1127 string const arg = found_formats[0]->name() + " " + file;
1128 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1131 //FIXME: do we have to explicitly check whether it's a lyx file?
1132 LYXERR(Debug::FILES,
1133 "No formats found, trying to open it as a lyx file");
1134 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1136 // add the functions to the queue
1137 guiApp->addToFuncRequestQueue(cmd);
1140 // now process the collected functions. We perform the events
1141 // asynchronously. This prevents potential problems in case the
1142 // BufferView is closed within an event.
1143 guiApp->processFuncRequestQueueAsync();
1147 void GuiView::message(docstring const & str)
1149 if (ForkedProcess::iAmAChild())
1152 // call is moved to GUI-thread by GuiProgress
1153 d.progress_->appendMessage(toqstr(str));
1157 void GuiView::clearMessageText()
1159 message(docstring());
1163 void GuiView::updateStatusBarMessage(QString const & str)
1165 statusBar()->showMessage(str);
1166 d.statusbar_timer_.stop();
1167 d.statusbar_timer_.start(3000);
1171 void GuiView::clearMessage()
1173 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1174 // the hasFocus function mostly returns false, even if the focus is on
1175 // a workarea in this view.
1179 d.statusbar_timer_.stop();
1183 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1185 if (wa != d.current_work_area_
1186 || wa->bufferView().buffer().isInternal())
1188 Buffer const & buf = wa->bufferView().buffer();
1189 // Set the windows title
1190 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1191 if (buf.notifiesExternalModification()) {
1192 title = bformat(_("%1$s (modified externally)"), title);
1193 // If the external modification status has changed, then maybe the status of
1194 // buffer-save has changed too.
1198 title += from_ascii(" - LyX");
1200 setWindowTitle(toqstr(title));
1201 // Sets the path for the window: this is used by OSX to
1202 // allow a context click on the title bar showing a menu
1203 // with the path up to the file
1204 setWindowFilePath(toqstr(buf.absFileName()));
1205 // Tell Qt whether the current document is changed
1206 setWindowModified(!buf.isClean());
1208 if (buf.params().shell_escape)
1209 shell_escape_->show();
1211 shell_escape_->hide();
1213 if (buf.hasReadonlyFlag())
1218 if (buf.lyxvc().inUse()) {
1219 version_control_->show();
1220 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1222 version_control_->hide();
1226 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1228 if (d.current_work_area_)
1229 // disconnect the current work area from all slots
1230 QObject::disconnect(d.current_work_area_, 0, this, 0);
1232 disconnectBufferView();
1233 connectBufferView(wa->bufferView());
1234 connectBuffer(wa->bufferView().buffer());
1235 d.current_work_area_ = wa;
1236 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1237 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1238 QObject::connect(wa, SIGNAL(busy(bool)),
1239 this, SLOT(setBusy(bool)));
1240 // connection of a signal to a signal
1241 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1242 this, SIGNAL(bufferViewChanged()));
1243 Q_EMIT updateWindowTitle(wa);
1244 Q_EMIT bufferViewChanged();
1248 void GuiView::onBufferViewChanged()
1251 // Buffer-dependent dialogs must be updated. This is done here because
1252 // some dialogs require buffer()->text.
1257 void GuiView::on_lastWorkAreaRemoved()
1260 // We already are in a close event. Nothing more to do.
1263 if (d.splitter_->count() > 1)
1264 // We have a splitter so don't close anything.
1267 // Reset and updates the dialogs.
1268 Q_EMIT bufferViewChanged();
1273 if (lyxrc.open_buffers_in_tabs)
1274 // Nothing more to do, the window should stay open.
1277 if (guiApp->viewIds().size() > 1) {
1283 // On Mac we also close the last window because the application stay
1284 // resident in memory. On other platforms we don't close the last
1285 // window because this would quit the application.
1291 void GuiView::updateStatusBar()
1293 // let the user see the explicit message
1294 if (d.statusbar_timer_.isActive())
1301 void GuiView::showMessage()
1305 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1306 if (msg.isEmpty()) {
1307 BufferView const * bv = currentBufferView();
1309 msg = toqstr(bv->cursor().currentState(devel_mode_));
1311 msg = qt_("Welcome to LyX!");
1313 statusBar()->showMessage(msg);
1317 bool GuiView::event(QEvent * e)
1321 // Useful debug code:
1322 //case QEvent::ActivationChange:
1323 //case QEvent::WindowDeactivate:
1324 //case QEvent::Paint:
1325 //case QEvent::Enter:
1326 //case QEvent::Leave:
1327 //case QEvent::HoverEnter:
1328 //case QEvent::HoverLeave:
1329 //case QEvent::HoverMove:
1330 //case QEvent::StatusTip:
1331 //case QEvent::DragEnter:
1332 //case QEvent::DragLeave:
1333 //case QEvent::Drop:
1336 case QEvent::WindowActivate: {
1337 GuiView * old_view = guiApp->currentView();
1338 if (this == old_view) {
1340 return QMainWindow::event(e);
1342 if (old_view && old_view->currentBufferView()) {
1343 // save current selection to the selection buffer to allow
1344 // middle-button paste in this window.
1345 cap::saveSelection(old_view->currentBufferView()->cursor());
1347 guiApp->setCurrentView(this);
1348 if (d.current_work_area_)
1349 on_currentWorkAreaChanged(d.current_work_area_);
1353 return QMainWindow::event(e);
1356 case QEvent::ShortcutOverride: {
1358 if (isFullScreen() && menuBar()->isHidden()) {
1359 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1360 // FIXME: we should also try to detect special LyX shortcut such as
1361 // Alt-P and Alt-M. Right now there is a hack in
1362 // GuiWorkArea::processKeySym() that hides again the menubar for
1364 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1366 return QMainWindow::event(e);
1369 return QMainWindow::event(e);
1373 return QMainWindow::event(e);
1377 void GuiView::resetWindowTitle()
1379 setWindowTitle(qt_("LyX"));
1382 bool GuiView::focusNextPrevChild(bool /*next*/)
1389 bool GuiView::busy() const
1395 void GuiView::setBusy(bool busy)
1397 bool const busy_before = busy_ > 0;
1398 busy ? ++busy_ : --busy_;
1399 if ((busy_ > 0) == busy_before)
1400 // busy state didn't change
1404 QApplication::setOverrideCursor(Qt::WaitCursor);
1407 QApplication::restoreOverrideCursor();
1412 void GuiView::resetCommandExecute()
1414 command_execute_ = false;
1419 double GuiView::pixelRatio() const
1421 #if QT_VERSION >= 0x050000
1422 return qt_scale_factor * devicePixelRatio();
1429 GuiWorkArea * GuiView::workArea(int index)
1431 if (TabWorkArea * twa = d.currentTabWorkArea())
1432 if (index < twa->count())
1433 return twa->workArea(index);
1438 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1440 if (currentWorkArea()
1441 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1442 return currentWorkArea();
1443 if (TabWorkArea * twa = d.currentTabWorkArea())
1444 return twa->workArea(buffer);
1449 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1451 // Automatically create a TabWorkArea if there are none yet.
1452 TabWorkArea * tab_widget = d.splitter_->count()
1453 ? d.currentTabWorkArea() : addTabWorkArea();
1454 return tab_widget->addWorkArea(buffer, *this);
1458 TabWorkArea * GuiView::addTabWorkArea()
1460 TabWorkArea * twa = new TabWorkArea;
1461 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1462 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1463 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1464 this, SLOT(on_lastWorkAreaRemoved()));
1466 d.splitter_->addWidget(twa);
1467 d.stack_widget_->setCurrentWidget(d.splitter_);
1472 GuiWorkArea const * GuiView::currentWorkArea() const
1474 return d.current_work_area_;
1478 GuiWorkArea * GuiView::currentWorkArea()
1480 return d.current_work_area_;
1484 GuiWorkArea const * GuiView::currentMainWorkArea() const
1486 if (!d.currentTabWorkArea())
1488 return d.currentTabWorkArea()->currentWorkArea();
1492 GuiWorkArea * GuiView::currentMainWorkArea()
1494 if (!d.currentTabWorkArea())
1496 return d.currentTabWorkArea()->currentWorkArea();
1500 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1502 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1504 d.current_work_area_ = 0;
1506 Q_EMIT bufferViewChanged();
1510 // FIXME: I've no clue why this is here and why it accesses
1511 // theGuiApp()->currentView, which might be 0 (bug 6464).
1512 // See also 27525 (vfr).
1513 if (theGuiApp()->currentView() == this
1514 && theGuiApp()->currentView()->currentWorkArea() == wa)
1517 if (currentBufferView())
1518 cap::saveSelection(currentBufferView()->cursor());
1520 theGuiApp()->setCurrentView(this);
1521 d.current_work_area_ = wa;
1523 // We need to reset this now, because it will need to be
1524 // right if the tabWorkArea gets reset in the for loop. We
1525 // will change it back if we aren't in that case.
1526 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1527 d.current_main_work_area_ = wa;
1529 for (int i = 0; i != d.splitter_->count(); ++i) {
1530 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1531 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1532 << ", Current main wa: " << currentMainWorkArea());
1537 d.current_main_work_area_ = old_cmwa;
1539 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1540 on_currentWorkAreaChanged(wa);
1541 BufferView & bv = wa->bufferView();
1542 bv.cursor().fixIfBroken();
1544 wa->setUpdatesEnabled(true);
1545 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1549 void GuiView::removeWorkArea(GuiWorkArea * wa)
1551 LASSERT(wa, return);
1552 if (wa == d.current_work_area_) {
1554 disconnectBufferView();
1555 d.current_work_area_ = 0;
1556 d.current_main_work_area_ = 0;
1559 bool found_twa = false;
1560 for (int i = 0; i != d.splitter_->count(); ++i) {
1561 TabWorkArea * twa = d.tabWorkArea(i);
1562 if (twa->removeWorkArea(wa)) {
1563 // Found in this tab group, and deleted the GuiWorkArea.
1565 if (twa->count() != 0) {
1566 if (d.current_work_area_ == 0)
1567 // This means that we are closing the current GuiWorkArea, so
1568 // switch to the next GuiWorkArea in the found TabWorkArea.
1569 setCurrentWorkArea(twa->currentWorkArea());
1571 // No more WorkAreas in this tab group, so delete it.
1578 // It is not a tabbed work area (i.e., the search work area), so it
1579 // should be deleted by other means.
1580 LASSERT(found_twa, return);
1582 if (d.current_work_area_ == 0) {
1583 if (d.splitter_->count() != 0) {
1584 TabWorkArea * twa = d.currentTabWorkArea();
1585 setCurrentWorkArea(twa->currentWorkArea());
1587 // No more work areas, switch to the background widget.
1588 setCurrentWorkArea(0);
1594 LayoutBox * GuiView::getLayoutDialog() const
1600 void GuiView::updateLayoutList()
1603 d.layout_->updateContents(false);
1607 void GuiView::updateToolbars()
1609 ToolbarMap::iterator end = d.toolbars_.end();
1610 if (d.current_work_area_) {
1612 if (d.current_work_area_->bufferView().cursor().inMathed()
1613 && !d.current_work_area_->bufferView().cursor().inRegexped())
1614 context |= Toolbars::MATH;
1615 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1616 context |= Toolbars::TABLE;
1617 if (currentBufferView()->buffer().areChangesPresent()
1618 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1619 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1620 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1621 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1622 context |= Toolbars::REVIEW;
1623 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1624 context |= Toolbars::MATHMACROTEMPLATE;
1625 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1626 context |= Toolbars::IPA;
1627 if (command_execute_)
1628 context |= Toolbars::MINIBUFFER;
1629 if (minibuffer_focus_) {
1630 context |= Toolbars::MINIBUFFER_FOCUS;
1631 minibuffer_focus_ = false;
1634 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1635 it->second->update(context);
1637 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1638 it->second->update();
1642 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1644 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1645 LASSERT(newBuffer, return);
1647 GuiWorkArea * wa = workArea(*newBuffer);
1650 newBuffer->masterBuffer()->updateBuffer();
1652 wa = addWorkArea(*newBuffer);
1653 // scroll to the position when the BufferView was last closed
1654 if (lyxrc.use_lastfilepos) {
1655 LastFilePosSection::FilePos filepos =
1656 theSession().lastFilePos().load(newBuffer->fileName());
1657 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1660 //Disconnect the old buffer...there's no new one.
1663 connectBuffer(*newBuffer);
1664 connectBufferView(wa->bufferView());
1666 setCurrentWorkArea(wa);
1670 void GuiView::connectBuffer(Buffer & buf)
1672 buf.setGuiDelegate(this);
1676 void GuiView::disconnectBuffer()
1678 if (d.current_work_area_)
1679 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1683 void GuiView::connectBufferView(BufferView & bv)
1685 bv.setGuiDelegate(this);
1689 void GuiView::disconnectBufferView()
1691 if (d.current_work_area_)
1692 d.current_work_area_->bufferView().setGuiDelegate(0);
1696 void GuiView::errors(string const & error_type, bool from_master)
1698 BufferView const * const bv = currentBufferView();
1702 ErrorList const & el = from_master ?
1703 bv->buffer().masterBuffer()->errorList(error_type) :
1704 bv->buffer().errorList(error_type);
1709 string err = error_type;
1711 err = "from_master|" + error_type;
1712 showDialog("errorlist", err);
1716 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1718 d.toc_models_.updateItem(toqstr(type), dit);
1722 void GuiView::structureChanged()
1724 // This is called from the Buffer, which has no way to ensure that cursors
1725 // in BufferView remain valid.
1726 if (documentBufferView())
1727 documentBufferView()->cursor().sanitize();
1728 // FIXME: This is slightly expensive, though less than the tocBackend update
1729 // (#9880). This also resets the view in the Toc Widget (#6675).
1730 d.toc_models_.reset(documentBufferView());
1731 // Navigator needs more than a simple update in this case. It needs to be
1733 updateDialog("toc", "");
1737 void GuiView::updateDialog(string const & name, string const & sdata)
1739 if (!isDialogVisible(name))
1742 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1743 if (it == d.dialogs_.end())
1746 Dialog * const dialog = it->second.get();
1747 if (dialog->isVisibleView())
1748 dialog->initialiseParams(sdata);
1752 BufferView * GuiView::documentBufferView()
1754 return currentMainWorkArea()
1755 ? ¤tMainWorkArea()->bufferView()
1760 BufferView const * GuiView::documentBufferView() const
1762 return currentMainWorkArea()
1763 ? ¤tMainWorkArea()->bufferView()
1768 BufferView * GuiView::currentBufferView()
1770 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1774 BufferView const * GuiView::currentBufferView() const
1776 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1780 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1781 Buffer const * orig, Buffer * clone)
1783 bool const success = clone->autoSave();
1785 busyBuffers.remove(orig);
1787 ? _("Automatic save done.")
1788 : _("Automatic save failed!");
1792 void GuiView::autoSave()
1794 LYXERR(Debug::INFO, "Running autoSave()");
1796 Buffer * buffer = documentBufferView()
1797 ? &documentBufferView()->buffer() : 0;
1799 resetAutosaveTimers();
1803 GuiViewPrivate::busyBuffers.insert(buffer);
1804 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1805 buffer, buffer->cloneBufferOnly());
1806 d.autosave_watcher_.setFuture(f);
1807 resetAutosaveTimers();
1811 void GuiView::resetAutosaveTimers()
1814 d.autosave_timeout_.restart();
1818 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1821 Buffer * buf = currentBufferView()
1822 ? ¤tBufferView()->buffer() : 0;
1823 Buffer * doc_buffer = documentBufferView()
1824 ? &(documentBufferView()->buffer()) : 0;
1827 /* In LyX/Mac, when a dialog is open, the menus of the
1828 application can still be accessed without giving focus to
1829 the main window. In this case, we want to disable the menu
1830 entries that are buffer-related.
1831 This code must not be used on Linux and Windows, since it
1832 would disable buffer-related entries when hovering over the
1833 menu (see bug #9574).
1835 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1841 // Check whether we need a buffer
1842 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1843 // no, exit directly
1844 flag.message(from_utf8(N_("Command not allowed with"
1845 "out any document open")));
1846 flag.setEnabled(false);
1850 if (cmd.origin() == FuncRequest::TOC) {
1851 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1852 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1853 flag.setEnabled(false);
1857 switch(cmd.action()) {
1858 case LFUN_BUFFER_IMPORT:
1861 case LFUN_MASTER_BUFFER_UPDATE:
1862 case LFUN_MASTER_BUFFER_VIEW:
1864 && (doc_buffer->parent() != 0
1865 || doc_buffer->hasChildren())
1866 && !d.processing_thread_watcher_.isRunning();
1869 case LFUN_BUFFER_UPDATE:
1870 case LFUN_BUFFER_VIEW: {
1871 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1875 string format = to_utf8(cmd.argument());
1876 if (cmd.argument().empty())
1877 format = doc_buffer->params().getDefaultOutputFormat();
1878 enable = doc_buffer->params().isExportable(format, true);
1882 case LFUN_BUFFER_RELOAD:
1883 enable = doc_buffer && !doc_buffer->isUnnamed()
1884 && doc_buffer->fileName().exists()
1885 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1888 case LFUN_BUFFER_CHILD_OPEN:
1889 enable = doc_buffer != 0;
1892 case LFUN_BUFFER_WRITE:
1893 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1896 //FIXME: This LFUN should be moved to GuiApplication.
1897 case LFUN_BUFFER_WRITE_ALL: {
1898 // We enable the command only if there are some modified buffers
1899 Buffer * first = theBufferList().first();
1904 // We cannot use a for loop as the buffer list is a cycle.
1906 if (!b->isClean()) {
1910 b = theBufferList().next(b);
1911 } while (b != first);
1915 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1916 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1919 case LFUN_BUFFER_EXPORT: {
1920 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1924 return doc_buffer->getStatus(cmd, flag);
1928 case LFUN_BUFFER_EXPORT_AS:
1929 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1934 case LFUN_BUFFER_WRITE_AS:
1935 enable = doc_buffer != 0;
1938 case LFUN_EXPORT_CANCEL:
1939 enable = d.processing_thread_watcher_.isRunning();
1942 case LFUN_BUFFER_CLOSE:
1943 case LFUN_VIEW_CLOSE:
1944 enable = doc_buffer != 0;
1947 case LFUN_BUFFER_CLOSE_ALL:
1948 enable = theBufferList().last() != theBufferList().first();
1951 case LFUN_BUFFER_CHKTEX: {
1952 // hide if we have no checktex command
1953 if (lyxrc.chktex_command.empty()) {
1954 flag.setUnknown(true);
1958 if (!doc_buffer || !doc_buffer->params().isLatex()
1959 || d.processing_thread_watcher_.isRunning()) {
1960 // grey out, don't hide
1968 case LFUN_VIEW_SPLIT:
1969 if (cmd.getArg(0) == "vertical")
1970 enable = doc_buffer && (d.splitter_->count() == 1 ||
1971 d.splitter_->orientation() == Qt::Vertical);
1973 enable = doc_buffer && (d.splitter_->count() == 1 ||
1974 d.splitter_->orientation() == Qt::Horizontal);
1977 case LFUN_TAB_GROUP_CLOSE:
1978 enable = d.tabWorkAreaCount() > 1;
1981 case LFUN_DEVEL_MODE_TOGGLE:
1982 flag.setOnOff(devel_mode_);
1985 case LFUN_TOOLBAR_TOGGLE: {
1986 string const name = cmd.getArg(0);
1987 if (GuiToolbar * t = toolbar(name))
1988 flag.setOnOff(t->isVisible());
1991 docstring const msg =
1992 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1998 case LFUN_TOOLBAR_MOVABLE: {
1999 string const name = cmd.getArg(0);
2000 // use negation since locked == !movable
2002 // toolbar name * locks all toolbars
2003 flag.setOnOff(!toolbarsMovable_);
2004 else if (GuiToolbar * t = toolbar(name))
2005 flag.setOnOff(!(t->isMovable()));
2008 docstring const msg =
2009 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2015 case LFUN_ICON_SIZE:
2016 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2019 case LFUN_DROP_LAYOUTS_CHOICE:
2023 case LFUN_UI_TOGGLE:
2024 flag.setOnOff(isFullScreen());
2027 case LFUN_DIALOG_DISCONNECT_INSET:
2030 case LFUN_DIALOG_HIDE:
2031 // FIXME: should we check if the dialog is shown?
2034 case LFUN_DIALOG_TOGGLE:
2035 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2038 case LFUN_DIALOG_SHOW: {
2039 string const name = cmd.getArg(0);
2041 enable = name == "aboutlyx"
2042 || name == "file" //FIXME: should be removed.
2044 || name == "texinfo"
2045 || name == "progress"
2046 || name == "compare";
2047 else if (name == "character" || name == "symbols"
2048 || name == "mathdelimiter" || name == "mathmatrix") {
2049 if (!buf || buf->isReadonly())
2052 Cursor const & cur = currentBufferView()->cursor();
2053 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2056 else if (name == "latexlog")
2057 enable = FileName(doc_buffer->logName()).isReadableFile();
2058 else if (name == "spellchecker")
2059 enable = theSpellChecker()
2060 && !doc_buffer->isReadonly()
2061 && !doc_buffer->text().empty();
2062 else if (name == "vclog")
2063 enable = doc_buffer->lyxvc().inUse();
2067 case LFUN_DIALOG_UPDATE: {
2068 string const name = cmd.getArg(0);
2070 enable = name == "prefs";
2074 case LFUN_COMMAND_EXECUTE:
2076 case LFUN_MENU_OPEN:
2077 // Nothing to check.
2080 case LFUN_COMPLETION_INLINE:
2081 if (!d.current_work_area_
2082 || !d.current_work_area_->completer().inlinePossible(
2083 currentBufferView()->cursor()))
2087 case LFUN_COMPLETION_POPUP:
2088 if (!d.current_work_area_
2089 || !d.current_work_area_->completer().popupPossible(
2090 currentBufferView()->cursor()))
2095 if (!d.current_work_area_
2096 || !d.current_work_area_->completer().inlinePossible(
2097 currentBufferView()->cursor()))
2101 case LFUN_COMPLETION_ACCEPT:
2102 if (!d.current_work_area_
2103 || (!d.current_work_area_->completer().popupVisible()
2104 && !d.current_work_area_->completer().inlineVisible()
2105 && !d.current_work_area_->completer().completionAvailable()))
2109 case LFUN_COMPLETION_CANCEL:
2110 if (!d.current_work_area_
2111 || (!d.current_work_area_->completer().popupVisible()
2112 && !d.current_work_area_->completer().inlineVisible()))
2116 case LFUN_BUFFER_ZOOM_OUT:
2117 case LFUN_BUFFER_ZOOM_IN: {
2118 // only diff between these two is that the default for ZOOM_OUT
2120 bool const neg_zoom =
2121 convert<int>(cmd.argument()) < 0 ||
2122 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2123 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2124 docstring const msg =
2125 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2129 enable = doc_buffer;
2133 case LFUN_BUFFER_ZOOM: {
2134 bool const less_than_min_zoom =
2135 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2136 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2137 docstring const msg =
2138 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2143 enable = doc_buffer;
2147 case LFUN_BUFFER_MOVE_NEXT:
2148 case LFUN_BUFFER_MOVE_PREVIOUS:
2149 // we do not cycle when moving
2150 case LFUN_BUFFER_NEXT:
2151 case LFUN_BUFFER_PREVIOUS:
2152 // because we cycle, it doesn't matter whether on first or last
2153 enable = (d.currentTabWorkArea()->count() > 1);
2155 case LFUN_BUFFER_SWITCH:
2156 // toggle on the current buffer, but do not toggle off
2157 // the other ones (is that a good idea?)
2159 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2160 flag.setOnOff(true);
2163 case LFUN_VC_REGISTER:
2164 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2166 case LFUN_VC_RENAME:
2167 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2170 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2172 case LFUN_VC_CHECK_IN:
2173 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2175 case LFUN_VC_CHECK_OUT:
2176 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2178 case LFUN_VC_LOCKING_TOGGLE:
2179 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2180 && doc_buffer->lyxvc().lockingToggleEnabled();
2181 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2183 case LFUN_VC_REVERT:
2184 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2185 && !doc_buffer->hasReadonlyFlag();
2187 case LFUN_VC_UNDO_LAST:
2188 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2190 case LFUN_VC_REPO_UPDATE:
2191 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2193 case LFUN_VC_COMMAND: {
2194 if (cmd.argument().empty())
2196 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2200 case LFUN_VC_COMPARE:
2201 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2204 case LFUN_SERVER_GOTO_FILE_ROW:
2205 case LFUN_LYX_ACTIVATE:
2207 case LFUN_FORWARD_SEARCH:
2208 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2211 case LFUN_FILE_INSERT_PLAINTEXT:
2212 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2213 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2216 case LFUN_SPELLING_CONTINUOUSLY:
2217 flag.setOnOff(lyxrc.spellcheck_continuously);
2225 flag.setEnabled(false);
2231 static FileName selectTemplateFile()
2233 FileDialog dlg(qt_("Select template file"));
2234 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2235 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2237 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2238 QStringList(qt_("LyX Documents (*.lyx)")));
2240 if (result.first == FileDialog::Later)
2242 if (result.second.isEmpty())
2244 return FileName(fromqstr(result.second));
2248 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2252 Buffer * newBuffer = 0;
2254 newBuffer = checkAndLoadLyXFile(filename);
2255 } catch (ExceptionMessage const & e) {
2262 message(_("Document not loaded."));
2266 setBuffer(newBuffer);
2267 newBuffer->errors("Parse");
2270 theSession().lastFiles().add(filename);
2271 theSession().writeFile();
2278 void GuiView::openDocument(string const & fname)
2280 string initpath = lyxrc.document_path;
2282 if (documentBufferView()) {
2283 string const trypath = documentBufferView()->buffer().filePath();
2284 // If directory is writeable, use this as default.
2285 if (FileName(trypath).isDirWritable())
2291 if (fname.empty()) {
2292 FileDialog dlg(qt_("Select document to open"));
2293 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2294 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2296 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2297 FileDialog::Result result =
2298 dlg.open(toqstr(initpath), filter);
2300 if (result.first == FileDialog::Later)
2303 filename = fromqstr(result.second);
2305 // check selected filename
2306 if (filename.empty()) {
2307 message(_("Canceled."));
2313 // get absolute path of file and add ".lyx" to the filename if
2315 FileName const fullname =
2316 fileSearch(string(), filename, "lyx", support::may_not_exist);
2317 if (!fullname.empty())
2318 filename = fullname.absFileName();
2320 if (!fullname.onlyPath().isDirectory()) {
2321 Alert::warning(_("Invalid filename"),
2322 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2323 from_utf8(fullname.absFileName())));
2327 // if the file doesn't exist and isn't already open (bug 6645),
2328 // let the user create one
2329 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2330 !LyXVC::file_not_found_hook(fullname)) {
2331 // the user specifically chose this name. Believe him.
2332 Buffer * const b = newFile(filename, string(), true);
2338 docstring const disp_fn = makeDisplayPath(filename);
2339 message(bformat(_("Opening document %1$s..."), disp_fn));
2342 Buffer * buf = loadDocument(fullname);
2344 str2 = bformat(_("Document %1$s opened."), disp_fn);
2345 if (buf->lyxvc().inUse())
2346 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2347 " " + _("Version control detected.");
2349 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2354 // FIXME: clean that
2355 static bool import(GuiView * lv, FileName const & filename,
2356 string const & format, ErrorList & errorList)
2358 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2360 string loader_format;
2361 vector<string> loaders = theConverters().loaders();
2362 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2363 vector<string>::const_iterator it = loaders.begin();
2364 vector<string>::const_iterator en = loaders.end();
2365 for (; it != en; ++it) {
2366 if (!theConverters().isReachable(format, *it))
2369 string const tofile =
2370 support::changeExtension(filename.absFileName(),
2371 theFormats().extension(*it));
2372 if (!theConverters().convert(0, filename, FileName(tofile),
2373 filename, format, *it, errorList))
2375 loader_format = *it;
2378 if (loader_format.empty()) {
2379 frontend::Alert::error(_("Couldn't import file"),
2380 bformat(_("No information for importing the format %1$s."),
2381 theFormats().prettyName(format)));
2385 loader_format = format;
2387 if (loader_format == "lyx") {
2388 Buffer * buf = lv->loadDocument(lyxfile);
2392 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2396 bool as_paragraphs = loader_format == "textparagraph";
2397 string filename2 = (loader_format == format) ? filename.absFileName()
2398 : support::changeExtension(filename.absFileName(),
2399 theFormats().extension(loader_format));
2400 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2402 guiApp->setCurrentView(lv);
2403 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2410 void GuiView::importDocument(string const & argument)
2413 string filename = split(argument, format, ' ');
2415 LYXERR(Debug::INFO, format << " file: " << filename);
2417 // need user interaction
2418 if (filename.empty()) {
2419 string initpath = lyxrc.document_path;
2420 if (documentBufferView()) {
2421 string const trypath = documentBufferView()->buffer().filePath();
2422 // If directory is writeable, use this as default.
2423 if (FileName(trypath).isDirWritable())
2427 docstring const text = bformat(_("Select %1$s file to import"),
2428 theFormats().prettyName(format));
2430 FileDialog dlg(toqstr(text));
2431 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2432 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2434 docstring filter = theFormats().prettyName(format);
2437 filter += from_utf8(theFormats().extensions(format));
2440 FileDialog::Result result =
2441 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2443 if (result.first == FileDialog::Later)
2446 filename = fromqstr(result.second);
2448 // check selected filename
2449 if (filename.empty())
2450 message(_("Canceled."));
2453 if (filename.empty())
2456 // get absolute path of file
2457 FileName const fullname(support::makeAbsPath(filename));
2459 // Can happen if the user entered a path into the dialog
2461 if (fullname.onlyFileName().empty()) {
2462 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2463 "Aborting import."),
2464 from_utf8(fullname.absFileName()));
2465 frontend::Alert::error(_("File name error"), msg);
2466 message(_("Canceled."));
2471 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2473 // Check if the document already is open
2474 Buffer * buf = theBufferList().getBuffer(lyxfile);
2477 if (!closeBuffer()) {
2478 message(_("Canceled."));
2483 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2485 // if the file exists already, and we didn't do
2486 // -i lyx thefile.lyx, warn
2487 if (lyxfile.exists() && fullname != lyxfile) {
2489 docstring text = bformat(_("The document %1$s already exists.\n\n"
2490 "Do you want to overwrite that document?"), displaypath);
2491 int const ret = Alert::prompt(_("Overwrite document?"),
2492 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2495 message(_("Canceled."));
2500 message(bformat(_("Importing %1$s..."), displaypath));
2501 ErrorList errorList;
2502 if (import(this, fullname, format, errorList))
2503 message(_("imported."));
2505 message(_("file not imported!"));
2507 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2511 void GuiView::newDocument(string const & filename, bool from_template)
2513 FileName initpath(lyxrc.document_path);
2514 if (documentBufferView()) {
2515 FileName const trypath(documentBufferView()->buffer().filePath());
2516 // If directory is writeable, use this as default.
2517 if (trypath.isDirWritable())
2521 string templatefile;
2522 if (from_template) {
2523 templatefile = selectTemplateFile().absFileName();
2524 if (templatefile.empty())
2529 if (filename.empty())
2530 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2532 b = newFile(filename, templatefile, true);
2537 // If no new document could be created, it is unsure
2538 // whether there is a valid BufferView.
2539 if (currentBufferView())
2540 // Ensure the cursor is correctly positioned on screen.
2541 currentBufferView()->showCursor();
2545 void GuiView::insertLyXFile(docstring const & fname)
2547 BufferView * bv = documentBufferView();
2552 FileName filename(to_utf8(fname));
2553 if (filename.empty()) {
2554 // Launch a file browser
2556 string initpath = lyxrc.document_path;
2557 string const trypath = bv->buffer().filePath();
2558 // If directory is writeable, use this as default.
2559 if (FileName(trypath).isDirWritable())
2563 FileDialog dlg(qt_("Select LyX document to insert"));
2564 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2565 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2567 FileDialog::Result result = dlg.open(toqstr(initpath),
2568 QStringList(qt_("LyX Documents (*.lyx)")));
2570 if (result.first == FileDialog::Later)
2574 filename.set(fromqstr(result.second));
2576 // check selected filename
2577 if (filename.empty()) {
2578 // emit message signal.
2579 message(_("Canceled."));
2584 bv->insertLyXFile(filename);
2585 bv->buffer().errors("Parse");
2589 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2591 FileName fname = b.fileName();
2592 FileName const oldname = fname;
2594 if (!newname.empty()) {
2596 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2598 // Switch to this Buffer.
2601 // No argument? Ask user through dialog.
2603 FileDialog dlg(qt_("Choose a filename to save document as"));
2604 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2605 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2607 if (!isLyXFileName(fname.absFileName()))
2608 fname.changeExtension(".lyx");
2610 FileDialog::Result result =
2611 dlg.save(toqstr(fname.onlyPath().absFileName()),
2612 QStringList(qt_("LyX Documents (*.lyx)")),
2613 toqstr(fname.onlyFileName()));
2615 if (result.first == FileDialog::Later)
2618 fname.set(fromqstr(result.second));
2623 if (!isLyXFileName(fname.absFileName()))
2624 fname.changeExtension(".lyx");
2627 // fname is now the new Buffer location.
2629 // if there is already a Buffer open with this name, we do not want
2630 // to have another one. (the second test makes sure we're not just
2631 // trying to overwrite ourselves, which is fine.)
2632 if (theBufferList().exists(fname) && fname != oldname
2633 && theBufferList().getBuffer(fname) != &b) {
2634 docstring const text =
2635 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2636 "Please close it before attempting to overwrite it.\n"
2637 "Do you want to choose a new filename?"),
2638 from_utf8(fname.absFileName()));
2639 int const ret = Alert::prompt(_("Chosen File Already Open"),
2640 text, 0, 1, _("&Rename"), _("&Cancel"));
2642 case 0: return renameBuffer(b, docstring(), kind);
2643 case 1: return false;
2648 bool const existsLocal = fname.exists();
2649 bool const existsInVC = LyXVC::fileInVC(fname);
2650 if (existsLocal || existsInVC) {
2651 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2652 if (kind != LV_WRITE_AS && existsInVC) {
2653 // renaming to a name that is already in VC
2655 docstring text = bformat(_("The document %1$s "
2656 "is already registered.\n\n"
2657 "Do you want to choose a new name?"),
2659 docstring const title = (kind == LV_VC_RENAME) ?
2660 _("Rename document?") : _("Copy document?");
2661 docstring const button = (kind == LV_VC_RENAME) ?
2662 _("&Rename") : _("&Copy");
2663 int const ret = Alert::prompt(title, text, 0, 1,
2664 button, _("&Cancel"));
2666 case 0: return renameBuffer(b, docstring(), kind);
2667 case 1: return false;
2672 docstring text = bformat(_("The document %1$s "
2673 "already exists.\n\n"
2674 "Do you want to overwrite that document?"),
2676 int const ret = Alert::prompt(_("Overwrite document?"),
2677 text, 0, 2, _("&Overwrite"),
2678 _("&Rename"), _("&Cancel"));
2681 case 1: return renameBuffer(b, docstring(), kind);
2682 case 2: return false;
2688 case LV_VC_RENAME: {
2689 string msg = b.lyxvc().rename(fname);
2692 message(from_utf8(msg));
2696 string msg = b.lyxvc().copy(fname);
2699 message(from_utf8(msg));
2705 // LyXVC created the file already in case of LV_VC_RENAME or
2706 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2707 // relative paths of included stuff right if we moved e.g. from
2708 // /a/b.lyx to /a/c/b.lyx.
2710 bool const saved = saveBuffer(b, fname);
2717 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2719 FileName fname = b.fileName();
2721 FileDialog dlg(qt_("Choose a filename to export the document as"));
2722 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2725 QString const anyformat = qt_("Guess from extension (*.*)");
2728 vector<Format const *> export_formats;
2729 for (Format const & f : theFormats())
2730 if (f.documentFormat())
2731 export_formats.push_back(&f);
2732 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2733 map<QString, string> fmap;
2736 for (Format const * f : export_formats) {
2737 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2738 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2740 from_ascii(f->extension())));
2741 types << loc_filter;
2742 fmap[loc_filter] = f->name();
2743 if (from_ascii(f->name()) == iformat) {
2744 filter = loc_filter;
2745 ext = f->extension();
2748 string ofname = fname.onlyFileName();
2750 ofname = support::changeExtension(ofname, ext);
2751 FileDialog::Result result =
2752 dlg.save(toqstr(fname.onlyPath().absFileName()),
2756 if (result.first != FileDialog::Chosen)
2760 fname.set(fromqstr(result.second));
2761 if (filter == anyformat)
2762 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2764 fmt_name = fmap[filter];
2765 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2766 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2768 if (fmt_name.empty() || fname.empty())
2771 // fname is now the new Buffer location.
2772 if (FileName(fname).exists()) {
2773 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2774 docstring text = bformat(_("The document %1$s already "
2775 "exists.\n\nDo you want to "
2776 "overwrite that document?"),
2778 int const ret = Alert::prompt(_("Overwrite document?"),
2779 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2782 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2783 case 2: return false;
2787 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2790 return dr.dispatched();
2794 bool GuiView::saveBuffer(Buffer & b)
2796 return saveBuffer(b, FileName());
2800 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2802 if (workArea(b) && workArea(b)->inDialogMode())
2805 if (fn.empty() && b.isUnnamed())
2806 return renameBuffer(b, docstring());
2808 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2810 theSession().lastFiles().add(b.fileName());
2811 theSession().writeFile();
2815 // Switch to this Buffer.
2818 // FIXME: we don't tell the user *WHY* the save failed !!
2819 docstring const file = makeDisplayPath(b.absFileName(), 30);
2820 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2821 "Do you want to rename the document and "
2822 "try again?"), file);
2823 int const ret = Alert::prompt(_("Rename and save?"),
2824 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2827 if (!renameBuffer(b, docstring()))
2836 return saveBuffer(b, fn);
2840 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2842 return closeWorkArea(wa, false);
2846 // We only want to close the buffer if it is not visible in other workareas
2847 // of the same view, nor in other views, and if this is not a child
2848 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2850 Buffer & buf = wa->bufferView().buffer();
2852 bool last_wa = d.countWorkAreasOf(buf) == 1
2853 && !inOtherView(buf) && !buf.parent();
2855 bool close_buffer = last_wa;
2858 if (lyxrc.close_buffer_with_last_view == "yes")
2860 else if (lyxrc.close_buffer_with_last_view == "no")
2861 close_buffer = false;
2864 if (buf.isUnnamed())
2865 file = from_utf8(buf.fileName().onlyFileName());
2867 file = buf.fileName().displayName(30);
2868 docstring const text = bformat(
2869 _("Last view on document %1$s is being closed.\n"
2870 "Would you like to close or hide the document?\n"
2872 "Hidden documents can be displayed back through\n"
2873 "the menu: View->Hidden->...\n"
2875 "To remove this question, set your preference in:\n"
2876 " Tools->Preferences->Look&Feel->UserInterface\n"
2878 int ret = Alert::prompt(_("Close or hide document?"),
2879 text, 0, 1, _("&Close"), _("&Hide"));
2880 close_buffer = (ret == 0);
2884 return closeWorkArea(wa, close_buffer);
2888 bool GuiView::closeBuffer()
2890 GuiWorkArea * wa = currentMainWorkArea();
2891 // coverity complained about this
2892 // it seems unnecessary, but perhaps is worth the check
2893 LASSERT(wa, return false);
2895 setCurrentWorkArea(wa);
2896 Buffer & buf = wa->bufferView().buffer();
2897 return closeWorkArea(wa, !buf.parent());
2901 void GuiView::writeSession() const {
2902 GuiWorkArea const * active_wa = currentMainWorkArea();
2903 for (int i = 0; i < d.splitter_->count(); ++i) {
2904 TabWorkArea * twa = d.tabWorkArea(i);
2905 for (int j = 0; j < twa->count(); ++j) {
2906 GuiWorkArea * wa = twa->workArea(j);
2907 Buffer & buf = wa->bufferView().buffer();
2908 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2914 bool GuiView::closeBufferAll()
2916 // Close the workareas in all other views
2917 QList<int> const ids = guiApp->viewIds();
2918 for (int i = 0; i != ids.size(); ++i) {
2919 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2923 // Close our own workareas
2924 if (!closeWorkAreaAll())
2927 // Now close the hidden buffers. We prevent hidden buffers from being
2928 // dirty, so we can just close them.
2929 theBufferList().closeAll();
2934 bool GuiView::closeWorkAreaAll()
2936 setCurrentWorkArea(currentMainWorkArea());
2938 // We might be in a situation that there is still a tabWorkArea, but
2939 // there are no tabs anymore. This can happen when we get here after a
2940 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2941 // many TabWorkArea's have no documents anymore.
2944 // We have to call count() each time, because it can happen that
2945 // more than one splitter will disappear in one iteration (bug 5998).
2946 while (d.splitter_->count() > empty_twa) {
2947 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2949 if (twa->count() == 0)
2952 setCurrentWorkArea(twa->currentWorkArea());
2953 if (!closeTabWorkArea(twa))
2961 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2966 Buffer & buf = wa->bufferView().buffer();
2968 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2969 Alert::warning(_("Close document"),
2970 _("Document could not be closed because it is being processed by LyX."));
2975 return closeBuffer(buf);
2977 if (!inMultiTabs(wa))
2978 if (!saveBufferIfNeeded(buf, true))
2986 bool GuiView::closeBuffer(Buffer & buf)
2988 // If we are in a close_event all children will be closed in some time,
2989 // so no need to do it here. This will ensure that the children end up
2990 // in the session file in the correct order. If we close the master
2991 // buffer, we can close or release the child buffers here too.
2992 bool success = true;
2994 ListOfBuffers clist = buf.getChildren();
2995 ListOfBuffers::const_iterator it = clist.begin();
2996 ListOfBuffers::const_iterator const bend = clist.end();
2997 for (; it != bend; ++it) {
2998 Buffer * child_buf = *it;
2999 if (theBufferList().isOthersChild(&buf, child_buf)) {
3000 child_buf->setParent(0);
3004 // FIXME: should we look in other tabworkareas?
3005 // ANSWER: I don't think so. I've tested, and if the child is
3006 // open in some other window, it closes without a problem.
3007 GuiWorkArea * child_wa = workArea(*child_buf);
3009 success = closeWorkArea(child_wa, true);
3013 // In this case the child buffer is open but hidden.
3014 // It therefore should not (MUST NOT) be dirty!
3015 LATTEST(child_buf->isClean());
3016 theBufferList().release(child_buf);
3021 // goto bookmark to update bookmark pit.
3022 // FIXME: we should update only the bookmarks related to this buffer!
3023 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3024 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3025 guiApp->gotoBookmark(i+1, false, false);
3027 if (saveBufferIfNeeded(buf, false)) {
3028 buf.removeAutosaveFile();
3029 theBufferList().release(&buf);
3033 // open all children again to avoid a crash because of dangling
3034 // pointers (bug 6603)
3040 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3042 while (twa == d.currentTabWorkArea()) {
3043 twa->setCurrentIndex(twa->count() - 1);
3045 GuiWorkArea * wa = twa->currentWorkArea();
3046 Buffer & b = wa->bufferView().buffer();
3048 // We only want to close the buffer if the same buffer is not visible
3049 // in another view, and if this is not a child and if we are closing
3050 // a view (not a tabgroup).
3051 bool const close_buffer =
3052 !inOtherView(b) && !b.parent() && closing_;
3054 if (!closeWorkArea(wa, close_buffer))
3061 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3063 if (buf.isClean() || buf.paragraphs().empty())
3066 // Switch to this Buffer.
3072 if (buf.isUnnamed()) {
3073 file = from_utf8(buf.fileName().onlyFileName());
3076 FileName filename = buf.fileName();
3078 file = filename.displayName(30);
3079 exists = filename.exists();
3082 // Bring this window to top before asking questions.
3087 if (hiding && buf.isUnnamed()) {
3088 docstring const text = bformat(_("The document %1$s has not been "
3089 "saved yet.\n\nDo you want to save "
3090 "the document?"), file);
3091 ret = Alert::prompt(_("Save new document?"),
3092 text, 0, 1, _("&Save"), _("&Cancel"));
3096 docstring const text = exists ?
3097 bformat(_("The document %1$s has unsaved changes."
3098 "\n\nDo you want to save the document or "
3099 "discard the changes?"), file) :
3100 bformat(_("The document %1$s has not been saved yet."
3101 "\n\nDo you want to save the document or "
3102 "discard it entirely?"), file);
3103 docstring const title = exists ?
3104 _("Save changed document?") : _("Save document?");
3105 ret = Alert::prompt(title, text, 0, 2,
3106 _("&Save"), _("&Discard"), _("&Cancel"));
3111 if (!saveBuffer(buf))
3115 // If we crash after this we could have no autosave file
3116 // but I guess this is really improbable (Jug).
3117 // Sometimes improbable things happen:
3118 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3119 // buf.removeAutosaveFile();
3121 // revert all changes
3132 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3134 Buffer & buf = wa->bufferView().buffer();
3136 for (int i = 0; i != d.splitter_->count(); ++i) {
3137 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3138 if (wa_ && wa_ != wa)
3141 return inOtherView(buf);
3145 bool GuiView::inOtherView(Buffer & buf)
3147 QList<int> const ids = guiApp->viewIds();
3149 for (int i = 0; i != ids.size(); ++i) {
3153 if (guiApp->view(ids[i]).workArea(buf))
3160 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3162 if (!documentBufferView())
3165 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3166 Buffer * const curbuf = &documentBufferView()->buffer();
3167 int nwa = twa->count();
3168 for (int i = 0; i < nwa; ++i) {
3169 if (&workArea(i)->bufferView().buffer() == curbuf) {
3171 if (np == NEXTBUFFER)
3172 next_index = (i == nwa - 1 ? 0 : i + 1);
3174 next_index = (i == 0 ? nwa - 1 : i - 1);
3176 twa->moveTab(i, next_index);
3178 setBuffer(&workArea(next_index)->bufferView().buffer());
3186 /// make sure the document is saved
3187 static bool ensureBufferClean(Buffer * buffer)
3189 LASSERT(buffer, return false);
3190 if (buffer->isClean() && !buffer->isUnnamed())
3193 docstring const file = buffer->fileName().displayName(30);
3196 if (!buffer->isUnnamed()) {
3197 text = bformat(_("The document %1$s has unsaved "
3198 "changes.\n\nDo you want to save "
3199 "the document?"), file);
3200 title = _("Save changed document?");
3203 text = bformat(_("The document %1$s has not been "
3204 "saved yet.\n\nDo you want to save "
3205 "the document?"), file);
3206 title = _("Save new document?");
3208 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3211 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3213 return buffer->isClean() && !buffer->isUnnamed();
3217 bool GuiView::reloadBuffer(Buffer & buf)
3219 currentBufferView()->cursor().reset();
3220 Buffer::ReadStatus status = buf.reload();
3221 return status == Buffer::ReadSuccess;
3225 void GuiView::checkExternallyModifiedBuffers()
3227 BufferList::iterator bit = theBufferList().begin();
3228 BufferList::iterator const bend = theBufferList().end();
3229 for (; bit != bend; ++bit) {
3230 Buffer * buf = *bit;
3231 if (buf->fileName().exists() && buf->isChecksumModified()) {
3232 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3233 " Reload now? Any local changes will be lost."),
3234 from_utf8(buf->absFileName()));
3235 int const ret = Alert::prompt(_("Reload externally changed document?"),
3236 text, 0, 1, _("&Reload"), _("&Cancel"));
3244 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3246 Buffer * buffer = documentBufferView()
3247 ? &(documentBufferView()->buffer()) : 0;
3249 switch (cmd.action()) {
3250 case LFUN_VC_REGISTER:
3251 if (!buffer || !ensureBufferClean(buffer))
3253 if (!buffer->lyxvc().inUse()) {
3254 if (buffer->lyxvc().registrer()) {
3255 reloadBuffer(*buffer);
3256 dr.clearMessageUpdate();
3261 case LFUN_VC_RENAME:
3262 case LFUN_VC_COPY: {
3263 if (!buffer || !ensureBufferClean(buffer))
3265 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3266 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3267 // Some changes are not yet committed.
3268 // We test here and not in getStatus(), since
3269 // this test is expensive.
3271 LyXVC::CommandResult ret =
3272 buffer->lyxvc().checkIn(log);
3274 if (ret == LyXVC::ErrorCommand ||
3275 ret == LyXVC::VCSuccess)
3276 reloadBuffer(*buffer);
3277 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3278 frontend::Alert::error(
3279 _("Revision control error."),
3280 _("Document could not be checked in."));
3284 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3285 LV_VC_RENAME : LV_VC_COPY;
3286 renameBuffer(*buffer, cmd.argument(), kind);
3291 case LFUN_VC_CHECK_IN:
3292 if (!buffer || !ensureBufferClean(buffer))
3294 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3296 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3298 // Only skip reloading if the checkin was cancelled or
3299 // an error occurred before the real checkin VCS command
3300 // was executed, since the VCS might have changed the
3301 // file even if it could not checkin successfully.
3302 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3303 reloadBuffer(*buffer);
3307 case LFUN_VC_CHECK_OUT:
3308 if (!buffer || !ensureBufferClean(buffer))
3310 if (buffer->lyxvc().inUse()) {
3311 dr.setMessage(buffer->lyxvc().checkOut());
3312 reloadBuffer(*buffer);
3316 case LFUN_VC_LOCKING_TOGGLE:
3317 LASSERT(buffer, return);
3318 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3320 if (buffer->lyxvc().inUse()) {
3321 string res = buffer->lyxvc().lockingToggle();
3323 frontend::Alert::error(_("Revision control error."),
3324 _("Error when setting the locking property."));
3327 reloadBuffer(*buffer);
3332 case LFUN_VC_REVERT:
3333 LASSERT(buffer, return);
3334 if (buffer->lyxvc().revert()) {
3335 reloadBuffer(*buffer);
3336 dr.clearMessageUpdate();
3340 case LFUN_VC_UNDO_LAST:
3341 LASSERT(buffer, return);
3342 buffer->lyxvc().undoLast();
3343 reloadBuffer(*buffer);
3344 dr.clearMessageUpdate();
3347 case LFUN_VC_REPO_UPDATE:
3348 LASSERT(buffer, return);
3349 if (ensureBufferClean(buffer)) {
3350 dr.setMessage(buffer->lyxvc().repoUpdate());
3351 checkExternallyModifiedBuffers();
3355 case LFUN_VC_COMMAND: {
3356 string flag = cmd.getArg(0);
3357 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3360 if (contains(flag, 'M')) {
3361 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3364 string path = cmd.getArg(1);
3365 if (contains(path, "$$p") && buffer)
3366 path = subst(path, "$$p", buffer->filePath());
3367 LYXERR(Debug::LYXVC, "Directory: " << path);
3369 if (!pp.isReadableDirectory()) {
3370 lyxerr << _("Directory is not accessible.") << endl;
3373 support::PathChanger p(pp);
3375 string command = cmd.getArg(2);
3376 if (command.empty())
3379 command = subst(command, "$$i", buffer->absFileName());
3380 command = subst(command, "$$p", buffer->filePath());
3382 command = subst(command, "$$m", to_utf8(message));
3383 LYXERR(Debug::LYXVC, "Command: " << command);
3385 one.startscript(Systemcall::Wait, command);
3389 if (contains(flag, 'I'))
3390 buffer->markDirty();
3391 if (contains(flag, 'R'))
3392 reloadBuffer(*buffer);
3397 case LFUN_VC_COMPARE: {
3398 if (cmd.argument().empty()) {
3399 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3403 string rev1 = cmd.getArg(0);
3408 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3411 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3412 f2 = buffer->absFileName();
3414 string rev2 = cmd.getArg(1);
3418 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3422 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3423 f1 << "\n" << f2 << "\n" );
3424 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3425 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3435 void GuiView::openChildDocument(string const & fname)
3437 LASSERT(documentBufferView(), return);
3438 Buffer & buffer = documentBufferView()->buffer();
3439 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3440 documentBufferView()->saveBookmark(false);
3442 if (theBufferList().exists(filename)) {
3443 child = theBufferList().getBuffer(filename);
3446 message(bformat(_("Opening child document %1$s..."),
3447 makeDisplayPath(filename.absFileName())));
3448 child = loadDocument(filename, false);
3450 // Set the parent name of the child document.
3451 // This makes insertion of citations and references in the child work,
3452 // when the target is in the parent or another child document.
3454 child->setParent(&buffer);
3458 bool GuiView::goToFileRow(string const & argument)
3462 size_t i = argument.find_last_of(' ');
3463 if (i != string::npos) {
3464 file_name = os::internal_path(trim(argument.substr(0, i)));
3465 istringstream is(argument.substr(i + 1));
3470 if (i == string::npos) {
3471 LYXERR0("Wrong argument: " << argument);
3475 string const abstmp = package().temp_dir().absFileName();
3476 string const realtmp = package().temp_dir().realPath();
3477 // We have to use os::path_prefix_is() here, instead of
3478 // simply prefixIs(), because the file name comes from
3479 // an external application and may need case adjustment.
3480 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3481 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3482 // Needed by inverse dvi search. If it is a file
3483 // in tmpdir, call the apropriated function.
3484 // If tmpdir is a symlink, we may have the real
3485 // path passed back, so we correct for that.
3486 if (!prefixIs(file_name, abstmp))
3487 file_name = subst(file_name, realtmp, abstmp);
3488 buf = theBufferList().getBufferFromTmp(file_name);
3490 // Must replace extension of the file to be .lyx
3491 // and get full path
3492 FileName const s = fileSearch(string(),
3493 support::changeExtension(file_name, ".lyx"), "lyx");
3494 // Either change buffer or load the file
3495 if (theBufferList().exists(s))
3496 buf = theBufferList().getBuffer(s);
3497 else if (s.exists()) {
3498 buf = loadDocument(s);
3503 _("File does not exist: %1$s"),
3504 makeDisplayPath(file_name)));
3510 _("No buffer for file: %1$s."),
3511 makeDisplayPath(file_name))
3516 bool success = documentBufferView()->setCursorFromRow(row);
3518 LYXERR(Debug::LATEX,
3519 "setCursorFromRow: invalid position for row " << row);
3520 frontend::Alert::error(_("Inverse Search Failed"),
3521 _("Invalid position requested by inverse search.\n"
3522 "You may need to update the viewed document."));
3528 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3530 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3531 menu->exec(QCursor::pos());
3536 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3538 Buffer::ExportStatus const status = func(format);
3540 // the cloning operation will have produced a clone of the entire set of
3541 // documents, starting from the master. so we must delete those.
3542 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3544 busyBuffers.remove(orig);
3549 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3551 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3552 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3556 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3558 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3559 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3563 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3565 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3566 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3570 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3571 string const & argument,
3572 Buffer const * used_buffer,
3573 docstring const & msg,
3574 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3575 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3576 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3581 string format = argument;
3583 format = used_buffer->params().getDefaultOutputFormat();
3584 processing_format = format;
3586 progress_->clearMessages();
3589 #if EXPORT_in_THREAD
3590 GuiViewPrivate::busyBuffers.insert(used_buffer);
3591 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3592 if (!cloned_buffer) {
3593 Alert::error(_("Export Error"),
3594 _("Error cloning the Buffer."));
3597 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3602 setPreviewFuture(f);
3603 last_export_format = used_buffer->params().bufferFormat();
3606 // We are asynchronous, so we don't know here anything about the success
3609 Buffer::ExportStatus status;
3611 status = (used_buffer->*syncFunc)(format, true);
3612 } else if (previewFunc) {
3613 status = (used_buffer->*previewFunc)(format);
3616 handleExportStatus(gv_, status, format);
3618 return (status == Buffer::ExportSuccess
3619 || status == Buffer::PreviewSuccess);
3623 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3625 BufferView * bv = currentBufferView();
3626 LASSERT(bv, return);
3628 // Let the current BufferView dispatch its own actions.
3629 bv->dispatch(cmd, dr);
3630 if (dr.dispatched())
3633 // Try with the document BufferView dispatch if any.
3634 BufferView * doc_bv = documentBufferView();
3635 if (doc_bv && doc_bv != bv) {
3636 doc_bv->dispatch(cmd, dr);
3637 if (dr.dispatched())
3641 // Then let the current Cursor dispatch its own actions.
3642 bv->cursor().dispatch(cmd);
3644 // update completion. We do it here and not in
3645 // processKeySym to avoid another redraw just for a
3646 // changed inline completion
3647 if (cmd.origin() == FuncRequest::KEYBOARD) {
3648 if (cmd.action() == LFUN_SELF_INSERT
3649 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3650 updateCompletion(bv->cursor(), true, true);
3651 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3652 updateCompletion(bv->cursor(), false, true);
3654 updateCompletion(bv->cursor(), false, false);
3657 dr = bv->cursor().result();
3661 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3663 BufferView * bv = currentBufferView();
3664 // By default we won't need any update.
3665 dr.screenUpdate(Update::None);
3666 // assume cmd will be dispatched
3667 dr.dispatched(true);
3669 Buffer * doc_buffer = documentBufferView()
3670 ? &(documentBufferView()->buffer()) : 0;
3672 if (cmd.origin() == FuncRequest::TOC) {
3673 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3674 // FIXME: do we need to pass a DispatchResult object here?
3675 toc->doDispatch(bv->cursor(), cmd);
3679 string const argument = to_utf8(cmd.argument());
3681 switch(cmd.action()) {
3682 case LFUN_BUFFER_CHILD_OPEN:
3683 openChildDocument(to_utf8(cmd.argument()));
3686 case LFUN_BUFFER_IMPORT:
3687 importDocument(to_utf8(cmd.argument()));
3690 case LFUN_BUFFER_EXPORT: {
3693 // GCC only sees strfwd.h when building merged
3694 if (::lyx::operator==(cmd.argument(), "custom")) {
3695 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3699 string const dest = cmd.getArg(1);
3700 FileName target_dir;
3701 if (!dest.empty() && FileName::isAbsolute(dest))
3702 target_dir = FileName(support::onlyPath(dest));
3704 target_dir = doc_buffer->fileName().onlyPath();
3706 string const format = (argument.empty() || argument == "default") ?
3707 doc_buffer->params().getDefaultOutputFormat() : argument;
3709 if ((dest.empty() && doc_buffer->isUnnamed())
3710 || !target_dir.isDirWritable()) {
3711 exportBufferAs(*doc_buffer, from_utf8(format));
3714 /* TODO/Review: Is it a problem to also export the children?
3715 See the update_unincluded flag */
3716 d.asyncBufferProcessing(format,
3719 &GuiViewPrivate::exportAndDestroy,
3722 // TODO Inform user about success
3726 case LFUN_BUFFER_EXPORT_AS: {
3727 LASSERT(doc_buffer, break);
3728 docstring f = cmd.argument();
3730 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3731 exportBufferAs(*doc_buffer, f);
3735 case LFUN_BUFFER_UPDATE: {
3736 d.asyncBufferProcessing(argument,
3739 &GuiViewPrivate::compileAndDestroy,
3744 case LFUN_BUFFER_VIEW: {
3745 d.asyncBufferProcessing(argument,
3747 _("Previewing ..."),
3748 &GuiViewPrivate::previewAndDestroy,
3753 case LFUN_MASTER_BUFFER_UPDATE: {
3754 d.asyncBufferProcessing(argument,
3755 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3757 &GuiViewPrivate::compileAndDestroy,
3762 case LFUN_MASTER_BUFFER_VIEW: {
3763 d.asyncBufferProcessing(argument,
3764 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3766 &GuiViewPrivate::previewAndDestroy,
3767 0, &Buffer::preview);
3770 case LFUN_EXPORT_CANCEL: {
3771 Systemcall::killscript();
3774 case LFUN_BUFFER_SWITCH: {
3775 string const file_name = to_utf8(cmd.argument());
3776 if (!FileName::isAbsolute(file_name)) {
3778 dr.setMessage(_("Absolute filename expected."));
3782 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3785 dr.setMessage(_("Document not loaded"));
3789 // Do we open or switch to the buffer in this view ?
3790 if (workArea(*buffer)
3791 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3796 // Look for the buffer in other views
3797 QList<int> const ids = guiApp->viewIds();
3799 for (; i != ids.size(); ++i) {
3800 GuiView & gv = guiApp->view(ids[i]);
3801 if (gv.workArea(*buffer)) {
3803 gv.activateWindow();
3805 gv.setBuffer(buffer);
3810 // If necessary, open a new window as a last resort
3811 if (i == ids.size()) {
3812 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3818 case LFUN_BUFFER_NEXT:
3819 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3822 case LFUN_BUFFER_MOVE_NEXT:
3823 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3826 case LFUN_BUFFER_PREVIOUS:
3827 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3830 case LFUN_BUFFER_MOVE_PREVIOUS:
3831 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3834 case LFUN_BUFFER_CHKTEX:
3835 LASSERT(doc_buffer, break);
3836 doc_buffer->runChktex();
3839 case LFUN_COMMAND_EXECUTE: {
3840 command_execute_ = true;
3841 minibuffer_focus_ = true;
3844 case LFUN_DROP_LAYOUTS_CHOICE:
3845 d.layout_->showPopup();
3848 case LFUN_MENU_OPEN:
3849 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3850 menu->exec(QCursor::pos());
3853 case LFUN_FILE_INSERT:
3854 insertLyXFile(cmd.argument());
3857 case LFUN_FILE_INSERT_PLAINTEXT:
3858 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3859 string const fname = to_utf8(cmd.argument());
3860 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3861 dr.setMessage(_("Absolute filename expected."));
3865 FileName filename(fname);
3866 if (fname.empty()) {
3867 FileDialog dlg(qt_("Select file to insert"));
3869 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3870 QStringList(qt_("All Files (*)")));
3872 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3873 dr.setMessage(_("Canceled."));
3877 filename.set(fromqstr(result.second));
3881 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3882 bv->dispatch(new_cmd, dr);
3887 case LFUN_BUFFER_RELOAD: {
3888 LASSERT(doc_buffer, break);
3891 bool drop = (cmd.argument()=="dump");
3894 if (!drop && !doc_buffer->isClean()) {
3895 docstring const file =
3896 makeDisplayPath(doc_buffer->absFileName(), 20);
3897 if (doc_buffer->notifiesExternalModification()) {
3898 docstring text = _("The current version will be lost. "
3899 "Are you sure you want to load the version on disk "
3900 "of the document %1$s?");
3901 ret = Alert::prompt(_("Reload saved document?"),
3902 bformat(text, file), 1, 1,
3903 _("&Reload"), _("&Cancel"));
3905 docstring text = _("Any changes will be lost. "
3906 "Are you sure you want to revert to the saved version "
3907 "of the document %1$s?");
3908 ret = Alert::prompt(_("Revert to saved document?"),
3909 bformat(text, file), 1, 1,
3910 _("&Revert"), _("&Cancel"));
3915 doc_buffer->markClean();
3916 reloadBuffer(*doc_buffer);
3917 dr.forceBufferUpdate();
3922 case LFUN_BUFFER_WRITE:
3923 LASSERT(doc_buffer, break);
3924 saveBuffer(*doc_buffer);
3927 case LFUN_BUFFER_WRITE_AS:
3928 LASSERT(doc_buffer, break);
3929 renameBuffer(*doc_buffer, cmd.argument());
3932 case LFUN_BUFFER_WRITE_ALL: {
3933 Buffer * first = theBufferList().first();
3936 message(_("Saving all documents..."));
3937 // We cannot use a for loop as the buffer list cycles.
3940 if (!b->isClean()) {
3942 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3944 b = theBufferList().next(b);
3945 } while (b != first);
3946 dr.setMessage(_("All documents saved."));
3950 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3951 LASSERT(doc_buffer, break);
3952 doc_buffer->clearExternalModification();
3955 case LFUN_BUFFER_CLOSE:
3959 case LFUN_BUFFER_CLOSE_ALL:
3963 case LFUN_DEVEL_MODE_TOGGLE:
3964 devel_mode_ = !devel_mode_;
3966 dr.setMessage(_("Developer mode is now enabled."));
3968 dr.setMessage(_("Developer mode is now disabled."));
3971 case LFUN_TOOLBAR_TOGGLE: {
3972 string const name = cmd.getArg(0);
3973 if (GuiToolbar * t = toolbar(name))
3978 case LFUN_TOOLBAR_MOVABLE: {
3979 string const name = cmd.getArg(0);
3981 // toggle (all) toolbars movablility
3982 toolbarsMovable_ = !toolbarsMovable_;
3983 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3984 GuiToolbar * tb = toolbar(ti.name);
3985 if (tb && tb->isMovable() != toolbarsMovable_)
3986 // toggle toolbar movablity if it does not fit lock
3987 // (all) toolbars positions state silent = true, since
3988 // status bar notifications are slow
3991 if (toolbarsMovable_)
3992 dr.setMessage(_("Toolbars unlocked."));
3994 dr.setMessage(_("Toolbars locked."));
3995 } else if (GuiToolbar * t = toolbar(name)) {
3996 // toggle current toolbar movablity
3998 // update lock (all) toolbars positions
3999 updateLockToolbars();
4004 case LFUN_ICON_SIZE: {
4005 QSize size = d.iconSize(cmd.argument());
4007 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4008 size.width(), size.height()));
4012 case LFUN_DIALOG_UPDATE: {
4013 string const name = to_utf8(cmd.argument());
4014 if (name == "prefs" || name == "document")
4015 updateDialog(name, string());
4016 else if (name == "paragraph")
4017 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4018 else if (currentBufferView()) {
4019 Inset * inset = currentBufferView()->editedInset(name);
4020 // Can only update a dialog connected to an existing inset
4022 // FIXME: get rid of this indirection; GuiView ask the inset
4023 // if he is kind enough to update itself...
4024 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4025 //FIXME: pass DispatchResult here?
4026 inset->dispatch(currentBufferView()->cursor(), fr);
4032 case LFUN_DIALOG_TOGGLE: {
4033 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4034 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4035 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4039 case LFUN_DIALOG_DISCONNECT_INSET:
4040 disconnectDialog(to_utf8(cmd.argument()));
4043 case LFUN_DIALOG_HIDE: {
4044 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4048 case LFUN_DIALOG_SHOW: {
4049 string const name = cmd.getArg(0);
4050 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4052 if (name == "character") {
4053 sdata = freefont2string();
4055 showDialog("character", sdata);
4056 } else if (name == "latexlog") {
4057 // gettatus checks that
4058 LATTEST(doc_buffer);
4059 Buffer::LogType type;
4060 string const logfile = doc_buffer->logName(&type);
4062 case Buffer::latexlog:
4065 case Buffer::buildlog:
4066 sdata = "literate ";
4069 sdata += Lexer::quoteString(logfile);
4070 showDialog("log", sdata);
4071 } else if (name == "vclog") {
4072 // getStatus checks that
4073 LATTEST(doc_buffer);
4074 string const sdata2 = "vc " +
4075 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4076 showDialog("log", sdata2);
4077 } else if (name == "symbols") {
4078 sdata = bv->cursor().getEncoding()->name();
4080 showDialog("symbols", sdata);
4082 } else if (name == "prefs" && isFullScreen()) {
4083 lfunUiToggle("fullscreen");
4084 showDialog("prefs", sdata);
4086 showDialog(name, sdata);
4091 dr.setMessage(cmd.argument());
4094 case LFUN_UI_TOGGLE: {
4095 string arg = cmd.getArg(0);
4096 if (!lfunUiToggle(arg)) {
4097 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4098 dr.setMessage(bformat(msg, from_utf8(arg)));
4100 // Make sure the keyboard focus stays in the work area.
4105 case LFUN_VIEW_SPLIT: {
4106 LASSERT(doc_buffer, break);
4107 string const orientation = cmd.getArg(0);
4108 d.splitter_->setOrientation(orientation == "vertical"
4109 ? Qt::Vertical : Qt::Horizontal);
4110 TabWorkArea * twa = addTabWorkArea();
4111 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4112 setCurrentWorkArea(wa);
4115 case LFUN_TAB_GROUP_CLOSE:
4116 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4117 closeTabWorkArea(twa);
4118 d.current_work_area_ = 0;
4119 twa = d.currentTabWorkArea();
4120 // Switch to the next GuiWorkArea in the found TabWorkArea.
4122 // Make sure the work area is up to date.
4123 setCurrentWorkArea(twa->currentWorkArea());
4125 setCurrentWorkArea(0);
4130 case LFUN_VIEW_CLOSE:
4131 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4132 closeWorkArea(twa->currentWorkArea());
4133 d.current_work_area_ = 0;
4134 twa = d.currentTabWorkArea();
4135 // Switch to the next GuiWorkArea in the found TabWorkArea.
4137 // Make sure the work area is up to date.
4138 setCurrentWorkArea(twa->currentWorkArea());
4140 setCurrentWorkArea(0);
4145 case LFUN_COMPLETION_INLINE:
4146 if (d.current_work_area_)
4147 d.current_work_area_->completer().showInline();
4150 case LFUN_COMPLETION_POPUP:
4151 if (d.current_work_area_)
4152 d.current_work_area_->completer().showPopup();
4157 if (d.current_work_area_)
4158 d.current_work_area_->completer().tab();
4161 case LFUN_COMPLETION_CANCEL:
4162 if (d.current_work_area_) {
4163 if (d.current_work_area_->completer().popupVisible())
4164 d.current_work_area_->completer().hidePopup();
4166 d.current_work_area_->completer().hideInline();
4170 case LFUN_COMPLETION_ACCEPT:
4171 if (d.current_work_area_)
4172 d.current_work_area_->completer().activate();
4175 case LFUN_BUFFER_ZOOM_IN:
4176 case LFUN_BUFFER_ZOOM_OUT:
4177 case LFUN_BUFFER_ZOOM: {
4178 if (cmd.argument().empty()) {
4179 if (cmd.action() == LFUN_BUFFER_ZOOM)
4181 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4186 if (cmd.action() == LFUN_BUFFER_ZOOM)
4187 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4188 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4189 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4191 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4194 // Actual zoom value: default zoom + fractional extra value
4195 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4196 if (zoom < static_cast<int>(zoom_min_))
4199 lyxrc.currentZoom = zoom;
4201 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4202 lyxrc.currentZoom, lyxrc.defaultZoom));
4204 // The global QPixmapCache is used in GuiPainter to cache text
4205 // painting so we must reset it.
4206 QPixmapCache::clear();
4207 guiApp->fontLoader().update();
4208 dr.screenUpdate(Update::Force | Update::FitCursor);
4212 case LFUN_VC_REGISTER:
4213 case LFUN_VC_RENAME:
4215 case LFUN_VC_CHECK_IN:
4216 case LFUN_VC_CHECK_OUT:
4217 case LFUN_VC_REPO_UPDATE:
4218 case LFUN_VC_LOCKING_TOGGLE:
4219 case LFUN_VC_REVERT:
4220 case LFUN_VC_UNDO_LAST:
4221 case LFUN_VC_COMMAND:
4222 case LFUN_VC_COMPARE:
4223 dispatchVC(cmd, dr);
4226 case LFUN_SERVER_GOTO_FILE_ROW:
4227 if(goToFileRow(to_utf8(cmd.argument())))
4228 dr.screenUpdate(Update::Force | Update::FitCursor);
4231 case LFUN_LYX_ACTIVATE:
4235 case LFUN_FORWARD_SEARCH: {
4236 // it seems safe to assume we have a document buffer, since
4237 // getStatus wants one.
4238 LATTEST(doc_buffer);
4239 Buffer const * doc_master = doc_buffer->masterBuffer();
4240 FileName const path(doc_master->temppath());
4241 string const texname = doc_master->isChild(doc_buffer)
4242 ? DocFileName(changeExtension(
4243 doc_buffer->absFileName(),
4244 "tex")).mangledFileName()
4245 : doc_buffer->latexName();
4246 string const fulltexname =
4247 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4248 string const mastername =
4249 removeExtension(doc_master->latexName());
4250 FileName const dviname(addName(path.absFileName(),
4251 addExtension(mastername, "dvi")));
4252 FileName const pdfname(addName(path.absFileName(),
4253 addExtension(mastername, "pdf")));
4254 bool const have_dvi = dviname.exists();
4255 bool const have_pdf = pdfname.exists();
4256 if (!have_dvi && !have_pdf) {
4257 dr.setMessage(_("Please, preview the document first."));
4260 string outname = dviname.onlyFileName();
4261 string command = lyxrc.forward_search_dvi;
4262 if (!have_dvi || (have_pdf &&
4263 pdfname.lastModified() > dviname.lastModified())) {
4264 outname = pdfname.onlyFileName();
4265 command = lyxrc.forward_search_pdf;
4268 DocIterator cur = bv->cursor();
4269 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4270 LYXERR(Debug::ACTION, "Forward search: row:" << row
4272 if (row == -1 || command.empty()) {
4273 dr.setMessage(_("Couldn't proceed."));
4276 string texrow = convert<string>(row);
4278 command = subst(command, "$$n", texrow);
4279 command = subst(command, "$$f", fulltexname);
4280 command = subst(command, "$$t", texname);
4281 command = subst(command, "$$o", outname);
4283 PathChanger p(path);
4285 one.startscript(Systemcall::DontWait, command);
4289 case LFUN_SPELLING_CONTINUOUSLY:
4290 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4291 dr.screenUpdate(Update::Force);
4295 // The LFUN must be for one of BufferView, Buffer or Cursor;
4297 dispatchToBufferView(cmd, dr);
4301 // Part of automatic menu appearance feature.
4302 if (isFullScreen()) {
4303 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4307 // Need to update bv because many LFUNs here might have destroyed it
4308 bv = currentBufferView();
4310 // Clear non-empty selections
4311 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4313 Cursor & cur = bv->cursor();
4314 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4315 cur.clearSelection();
4321 bool GuiView::lfunUiToggle(string const & ui_component)
4323 if (ui_component == "scrollbar") {
4324 // hide() is of no help
4325 if (d.current_work_area_->verticalScrollBarPolicy() ==
4326 Qt::ScrollBarAlwaysOff)
4328 d.current_work_area_->setVerticalScrollBarPolicy(
4329 Qt::ScrollBarAsNeeded);
4331 d.current_work_area_->setVerticalScrollBarPolicy(
4332 Qt::ScrollBarAlwaysOff);
4333 } else if (ui_component == "statusbar") {
4334 statusBar()->setVisible(!statusBar()->isVisible());
4335 } else if (ui_component == "menubar") {
4336 menuBar()->setVisible(!menuBar()->isVisible());
4338 if (ui_component == "frame") {
4340 getContentsMargins(&l, &t, &r, &b);
4341 //are the frames in default state?
4342 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4344 setContentsMargins(-2, -2, -2, -2);
4346 setContentsMargins(0, 0, 0, 0);
4349 if (ui_component == "fullscreen") {
4357 void GuiView::toggleFullScreen()
4359 if (isFullScreen()) {
4360 for (int i = 0; i != d.splitter_->count(); ++i)
4361 d.tabWorkArea(i)->setFullScreen(false);
4362 setContentsMargins(0, 0, 0, 0);
4363 setWindowState(windowState() ^ Qt::WindowFullScreen);
4366 statusBar()->show();
4369 hideDialogs("prefs", 0);
4370 for (int i = 0; i != d.splitter_->count(); ++i)
4371 d.tabWorkArea(i)->setFullScreen(true);
4372 setContentsMargins(-2, -2, -2, -2);
4374 setWindowState(windowState() ^ Qt::WindowFullScreen);
4375 if (lyxrc.full_screen_statusbar)
4376 statusBar()->hide();
4377 if (lyxrc.full_screen_menubar)
4379 if (lyxrc.full_screen_toolbars) {
4380 ToolbarMap::iterator end = d.toolbars_.end();
4381 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4386 // give dialogs like the TOC a chance to adapt
4391 Buffer const * GuiView::updateInset(Inset const * inset)
4396 Buffer const * inset_buffer = &(inset->buffer());
4398 for (int i = 0; i != d.splitter_->count(); ++i) {
4399 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4402 Buffer const * buffer = &(wa->bufferView().buffer());
4403 if (inset_buffer == buffer)
4404 wa->scheduleRedraw(true);
4406 return inset_buffer;
4410 void GuiView::restartCaret()
4412 /* When we move around, or type, it's nice to be able to see
4413 * the caret immediately after the keypress.
4415 if (d.current_work_area_)
4416 d.current_work_area_->startBlinkingCaret();
4418 // Take this occasion to update the other GUI elements.
4424 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4426 if (d.current_work_area_)
4427 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4432 // This list should be kept in sync with the list of insets in
4433 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4434 // dialog should have the same name as the inset.
4435 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4436 // docs in LyXAction.cpp.
4438 char const * const dialognames[] = {
4440 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4441 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4442 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4443 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4444 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4445 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4446 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4447 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4449 char const * const * const end_dialognames =
4450 dialognames + (sizeof(dialognames) / sizeof(char *));
4454 cmpCStr(char const * name) : name_(name) {}
4455 bool operator()(char const * other) {
4456 return strcmp(other, name_) == 0;
4463 bool isValidName(string const & name)
4465 return find_if(dialognames, end_dialognames,
4466 cmpCStr(name.c_str())) != end_dialognames;
4472 void GuiView::resetDialogs()
4474 // Make sure that no LFUN uses any GuiView.
4475 guiApp->setCurrentView(0);
4479 constructToolbars();
4480 guiApp->menus().fillMenuBar(menuBar(), this, false);
4481 d.layout_->updateContents(true);
4482 // Now update controls with current buffer.
4483 guiApp->setCurrentView(this);
4489 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4491 if (!isValidName(name))
4494 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4496 if (it != d.dialogs_.end()) {
4498 it->second->hideView();
4499 return it->second.get();
4502 Dialog * dialog = build(name);
4503 d.dialogs_[name].reset(dialog);
4504 if (lyxrc.allow_geometry_session)
4505 dialog->restoreSession();
4512 void GuiView::showDialog(string const & name, string const & sdata,
4515 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4519 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4525 const string name = fromqstr(qname);
4526 const string sdata = fromqstr(qdata);
4530 Dialog * dialog = findOrBuild(name, false);
4532 bool const visible = dialog->isVisibleView();
4533 dialog->showData(sdata);
4534 if (inset && currentBufferView())
4535 currentBufferView()->editInset(name, inset);
4536 // We only set the focus to the new dialog if it was not yet
4537 // visible in order not to change the existing previous behaviour
4539 // activateWindow is needed for floating dockviews
4540 dialog->asQWidget()->raise();
4541 dialog->asQWidget()->activateWindow();
4542 dialog->asQWidget()->setFocus();
4546 catch (ExceptionMessage const & ex) {
4554 bool GuiView::isDialogVisible(string const & name) const
4556 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4557 if (it == d.dialogs_.end())
4559 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4563 void GuiView::hideDialog(string const & name, Inset * inset)
4565 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4566 if (it == d.dialogs_.end())
4570 if (!currentBufferView())
4572 if (inset != currentBufferView()->editedInset(name))
4576 Dialog * const dialog = it->second.get();
4577 if (dialog->isVisibleView())
4579 if (currentBufferView())
4580 currentBufferView()->editInset(name, 0);
4584 void GuiView::disconnectDialog(string const & name)
4586 if (!isValidName(name))
4588 if (currentBufferView())
4589 currentBufferView()->editInset(name, 0);
4593 void GuiView::hideAll() const
4595 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4596 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4598 for(; it != end; ++it)
4599 it->second->hideView();
4603 void GuiView::updateDialogs()
4605 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4606 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4608 for(; it != end; ++it) {
4609 Dialog * dialog = it->second.get();
4611 if (dialog->needBufferOpen() && !documentBufferView())
4612 hideDialog(fromqstr(dialog->name()), 0);
4613 else if (dialog->isVisibleView())
4614 dialog->checkStatus();
4621 Dialog * createDialog(GuiView & lv, string const & name);
4623 // will be replaced by a proper factory...
4624 Dialog * createGuiAbout(GuiView & lv);
4625 Dialog * createGuiBibtex(GuiView & lv);
4626 Dialog * createGuiChanges(GuiView & lv);
4627 Dialog * createGuiCharacter(GuiView & lv);
4628 Dialog * createGuiCitation(GuiView & lv);
4629 Dialog * createGuiCompare(GuiView & lv);
4630 Dialog * createGuiCompareHistory(GuiView & lv);
4631 Dialog * createGuiDelimiter(GuiView & lv);
4632 Dialog * createGuiDocument(GuiView & lv);
4633 Dialog * createGuiErrorList(GuiView & lv);
4634 Dialog * createGuiExternal(GuiView & lv);
4635 Dialog * createGuiGraphics(GuiView & lv);
4636 Dialog * createGuiInclude(GuiView & lv);
4637 Dialog * createGuiIndex(GuiView & lv);
4638 Dialog * createGuiListings(GuiView & lv);
4639 Dialog * createGuiLog(GuiView & lv);
4640 Dialog * createGuiMathMatrix(GuiView & lv);
4641 Dialog * createGuiNote(GuiView & lv);
4642 Dialog * createGuiParagraph(GuiView & lv);
4643 Dialog * createGuiPhantom(GuiView & lv);
4644 Dialog * createGuiPreferences(GuiView & lv);
4645 Dialog * createGuiPrint(GuiView & lv);
4646 Dialog * createGuiPrintindex(GuiView & lv);
4647 Dialog * createGuiRef(GuiView & lv);
4648 Dialog * createGuiSearch(GuiView & lv);
4649 Dialog * createGuiSearchAdv(GuiView & lv);
4650 Dialog * createGuiSendTo(GuiView & lv);
4651 Dialog * createGuiShowFile(GuiView & lv);
4652 Dialog * createGuiSpellchecker(GuiView & lv);
4653 Dialog * createGuiSymbols(GuiView & lv);
4654 Dialog * createGuiTabularCreate(GuiView & lv);
4655 Dialog * createGuiTexInfo(GuiView & lv);
4656 Dialog * createGuiToc(GuiView & lv);
4657 Dialog * createGuiThesaurus(GuiView & lv);
4658 Dialog * createGuiViewSource(GuiView & lv);
4659 Dialog * createGuiWrap(GuiView & lv);
4660 Dialog * createGuiProgressView(GuiView & lv);
4664 Dialog * GuiView::build(string const & name)
4666 LASSERT(isValidName(name), return 0);
4668 Dialog * dialog = createDialog(*this, name);
4672 if (name == "aboutlyx")
4673 return createGuiAbout(*this);
4674 if (name == "bibtex")
4675 return createGuiBibtex(*this);
4676 if (name == "changes")
4677 return createGuiChanges(*this);
4678 if (name == "character")
4679 return createGuiCharacter(*this);
4680 if (name == "citation")
4681 return createGuiCitation(*this);
4682 if (name == "compare")
4683 return createGuiCompare(*this);
4684 if (name == "comparehistory")
4685 return createGuiCompareHistory(*this);
4686 if (name == "document")
4687 return createGuiDocument(*this);
4688 if (name == "errorlist")
4689 return createGuiErrorList(*this);
4690 if (name == "external")
4691 return createGuiExternal(*this);
4693 return createGuiShowFile(*this);
4694 if (name == "findreplace")
4695 return createGuiSearch(*this);
4696 if (name == "findreplaceadv")
4697 return createGuiSearchAdv(*this);
4698 if (name == "graphics")
4699 return createGuiGraphics(*this);
4700 if (name == "include")
4701 return createGuiInclude(*this);
4702 if (name == "index")
4703 return createGuiIndex(*this);
4704 if (name == "index_print")
4705 return createGuiPrintindex(*this);
4706 if (name == "listings")
4707 return createGuiListings(*this);
4709 return createGuiLog(*this);
4710 if (name == "mathdelimiter")
4711 return createGuiDelimiter(*this);
4712 if (name == "mathmatrix")
4713 return createGuiMathMatrix(*this);
4715 return createGuiNote(*this);
4716 if (name == "paragraph")
4717 return createGuiParagraph(*this);
4718 if (name == "phantom")
4719 return createGuiPhantom(*this);
4720 if (name == "prefs")
4721 return createGuiPreferences(*this);
4723 return createGuiRef(*this);
4724 if (name == "sendto")
4725 return createGuiSendTo(*this);
4726 if (name == "spellchecker")
4727 return createGuiSpellchecker(*this);
4728 if (name == "symbols")
4729 return createGuiSymbols(*this);
4730 if (name == "tabularcreate")
4731 return createGuiTabularCreate(*this);
4732 if (name == "texinfo")
4733 return createGuiTexInfo(*this);
4734 if (name == "thesaurus")
4735 return createGuiThesaurus(*this);
4737 return createGuiToc(*this);
4738 if (name == "view-source")
4739 return createGuiViewSource(*this);
4741 return createGuiWrap(*this);
4742 if (name == "progress")
4743 return createGuiProgressView(*this);
4749 SEMenu::SEMenu(QWidget * parent)
4751 QAction * action = addAction(qt_("Disable Shell Escape"));
4752 connect(action, SIGNAL(triggered()),
4753 parent, SLOT(disableShellEscape()));
4756 } // namespace frontend
4759 #include "moc_GuiView.cpp"