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()) {
728 if (status != Buffer::ExportSuccess && status != Buffer::PreviewSuccess &&
729 status != Buffer::ExportCancel) {
730 errors(d.last_export_format);
735 void GuiView::autoSaveThreadFinished()
737 QFutureWatcher<docstring> const * watcher =
738 static_cast<QFutureWatcher<docstring> const *>(sender());
739 message(watcher->result());
744 void GuiView::saveLayout() const
747 settings.setValue("zoom_ratio", zoom_ratio_);
748 settings.setValue("devel_mode", devel_mode_);
749 settings.beginGroup("views");
750 settings.beginGroup(QString::number(id_));
751 #if defined(Q_WS_X11) || defined(QPA_XCB)
752 settings.setValue("pos", pos());
753 settings.setValue("size", size());
755 settings.setValue("geometry", saveGeometry());
757 settings.setValue("layout", saveState(0));
758 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
762 void GuiView::saveUISettings() const
766 // Save the toolbar private states
767 ToolbarMap::iterator end = d.toolbars_.end();
768 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
769 it->second->saveSession(settings);
770 // Now take care of all other dialogs
771 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
772 for (; it!= d.dialogs_.end(); ++it)
773 it->second->saveSession(settings);
777 bool GuiView::restoreLayout()
780 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
781 // Actual zoom value: default zoom + fractional offset
782 int zoom = lyxrc.defaultZoom * zoom_ratio_;
783 if (zoom < static_cast<int>(zoom_min_))
785 lyxrc.currentZoom = zoom;
786 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
787 settings.beginGroup("views");
788 settings.beginGroup(QString::number(id_));
789 QString const icon_key = "icon_size";
790 if (!settings.contains(icon_key))
793 //code below is skipped when when ~/.config/LyX is (re)created
794 setIconSize(d.iconSize(settings.value(icon_key).toString()));
796 #if defined(Q_WS_X11) || defined(QPA_XCB)
797 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
798 QSize size = settings.value("size", QSize(690, 510)).toSize();
802 // Work-around for bug #6034: the window ends up in an undetermined
803 // state when trying to restore a maximized window when it is
804 // already maximized.
805 if (!(windowState() & Qt::WindowMaximized))
806 if (!restoreGeometry(settings.value("geometry").toByteArray()))
807 setGeometry(50, 50, 690, 510);
809 // Make sure layout is correctly oriented.
810 setLayoutDirection(qApp->layoutDirection());
812 // Allow the toc and view-source dock widget to be restored if needed.
814 if ((dialog = findOrBuild("toc", true)))
815 // see bug 5082. At least setup title and enabled state.
816 // Visibility will be adjusted by restoreState below.
817 dialog->prepareView();
818 if ((dialog = findOrBuild("view-source", true)))
819 dialog->prepareView();
820 if ((dialog = findOrBuild("progress", true)))
821 dialog->prepareView();
823 if (!restoreState(settings.value("layout").toByteArray(), 0))
826 // init the toolbars that have not been restored
827 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
828 Toolbars::Infos::iterator end = guiApp->toolbars().end();
829 for (; cit != end; ++cit) {
830 GuiToolbar * tb = toolbar(cit->name);
831 if (tb && !tb->isRestored())
832 initToolbar(cit->name);
835 // update lock (all) toolbars positions
836 updateLockToolbars();
843 GuiToolbar * GuiView::toolbar(string const & name)
845 ToolbarMap::iterator it = d.toolbars_.find(name);
846 if (it != d.toolbars_.end())
849 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
854 void GuiView::updateLockToolbars()
856 toolbarsMovable_ = false;
857 for (ToolbarInfo const & info : guiApp->toolbars()) {
858 GuiToolbar * tb = toolbar(info.name);
859 if (tb && tb->isMovable())
860 toolbarsMovable_ = true;
865 void GuiView::constructToolbars()
867 ToolbarMap::iterator it = d.toolbars_.begin();
868 for (; it != d.toolbars_.end(); ++it)
872 // I don't like doing this here, but the standard toolbar
873 // destroys this object when it's destroyed itself (vfr)
874 d.layout_ = new LayoutBox(*this);
875 d.stack_widget_->addWidget(d.layout_);
876 d.layout_->move(0,0);
878 // extracts the toolbars from the backend
879 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
880 Toolbars::Infos::iterator end = guiApp->toolbars().end();
881 for (; cit != end; ++cit)
882 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
886 void GuiView::initToolbars()
888 // extracts the toolbars from the backend
889 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
890 Toolbars::Infos::iterator end = guiApp->toolbars().end();
891 for (; cit != end; ++cit)
892 initToolbar(cit->name);
896 void GuiView::initToolbar(string const & name)
898 GuiToolbar * tb = toolbar(name);
901 int const visibility = guiApp->toolbars().defaultVisibility(name);
902 bool newline = !(visibility & Toolbars::SAMEROW);
903 tb->setVisible(false);
904 tb->setVisibility(visibility);
906 if (visibility & Toolbars::TOP) {
908 addToolBarBreak(Qt::TopToolBarArea);
909 addToolBar(Qt::TopToolBarArea, tb);
912 if (visibility & Toolbars::BOTTOM) {
914 addToolBarBreak(Qt::BottomToolBarArea);
915 addToolBar(Qt::BottomToolBarArea, tb);
918 if (visibility & Toolbars::LEFT) {
920 addToolBarBreak(Qt::LeftToolBarArea);
921 addToolBar(Qt::LeftToolBarArea, tb);
924 if (visibility & Toolbars::RIGHT) {
926 addToolBarBreak(Qt::RightToolBarArea);
927 addToolBar(Qt::RightToolBarArea, tb);
930 if (visibility & Toolbars::ON)
931 tb->setVisible(true);
933 tb->setMovable(true);
937 TocModels & GuiView::tocModels()
939 return d.toc_models_;
943 void GuiView::setFocus()
945 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
946 QMainWindow::setFocus();
950 bool GuiView::hasFocus() const
952 if (currentWorkArea())
953 return currentWorkArea()->hasFocus();
954 if (currentMainWorkArea())
955 return currentMainWorkArea()->hasFocus();
956 return d.bg_widget_->hasFocus();
960 void GuiView::focusInEvent(QFocusEvent * e)
962 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
963 QMainWindow::focusInEvent(e);
964 // Make sure guiApp points to the correct view.
965 guiApp->setCurrentView(this);
966 if (currentWorkArea())
967 currentWorkArea()->setFocus();
968 else if (currentMainWorkArea())
969 currentMainWorkArea()->setFocus();
971 d.bg_widget_->setFocus();
975 void GuiView::showEvent(QShowEvent * e)
977 LYXERR(Debug::GUI, "Passed Geometry "
978 << size().height() << "x" << size().width()
979 << "+" << pos().x() << "+" << pos().y());
981 if (d.splitter_->count() == 0)
982 // No work area, switch to the background widget.
986 QMainWindow::showEvent(e);
990 bool GuiView::closeScheduled()
997 bool GuiView::prepareAllBuffersForLogout()
999 Buffer * first = theBufferList().first();
1003 // First, iterate over all buffers and ask the users if unsaved
1004 // changes should be saved.
1005 // We cannot use a for loop as the buffer list cycles.
1008 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1010 b = theBufferList().next(b);
1011 } while (b != first);
1013 // Next, save session state
1014 // When a view/window was closed before without quitting LyX, there
1015 // are already entries in the lastOpened list.
1016 theSession().lastOpened().clear();
1023 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1024 ** is responsibility of the container (e.g., dialog)
1026 void GuiView::closeEvent(QCloseEvent * close_event)
1028 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1030 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1031 Alert::warning(_("Exit LyX"),
1032 _("LyX could not be closed because documents are being processed by LyX."));
1033 close_event->setAccepted(false);
1037 // If the user pressed the x (so we didn't call closeView
1038 // programmatically), we want to clear all existing entries.
1040 theSession().lastOpened().clear();
1045 // it can happen that this event arrives without selecting the view,
1046 // e.g. when clicking the close button on a background window.
1048 if (!closeWorkAreaAll()) {
1050 close_event->ignore();
1054 // Make sure that nothing will use this to be closed View.
1055 guiApp->unregisterView(this);
1057 if (isFullScreen()) {
1058 // Switch off fullscreen before closing.
1063 // Make sure the timer time out will not trigger a statusbar update.
1064 d.statusbar_timer_.stop();
1066 // Saving fullscreen requires additional tweaks in the toolbar code.
1067 // It wouldn't also work under linux natively.
1068 if (lyxrc.allow_geometry_session) {
1073 close_event->accept();
1077 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1079 if (event->mimeData()->hasUrls())
1081 /// \todo Ask lyx-devel is this is enough:
1082 /// if (event->mimeData()->hasFormat("text/plain"))
1083 /// event->acceptProposedAction();
1087 void GuiView::dropEvent(QDropEvent * event)
1089 QList<QUrl> files = event->mimeData()->urls();
1090 if (files.isEmpty())
1093 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1094 for (int i = 0; i != files.size(); ++i) {
1095 string const file = os::internal_path(fromqstr(
1096 files.at(i).toLocalFile()));
1100 string const ext = support::getExtension(file);
1101 vector<const Format *> found_formats;
1103 // Find all formats that have the correct extension.
1104 vector<const Format *> const & import_formats
1105 = theConverters().importableFormats();
1106 vector<const Format *>::const_iterator it = import_formats.begin();
1107 for (; it != import_formats.end(); ++it)
1108 if ((*it)->hasExtension(ext))
1109 found_formats.push_back(*it);
1112 if (found_formats.size() >= 1) {
1113 if (found_formats.size() > 1) {
1114 //FIXME: show a dialog to choose the correct importable format
1115 LYXERR(Debug::FILES,
1116 "Multiple importable formats found, selecting first");
1118 string const arg = found_formats[0]->name() + " " + file;
1119 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1122 //FIXME: do we have to explicitly check whether it's a lyx file?
1123 LYXERR(Debug::FILES,
1124 "No formats found, trying to open it as a lyx file");
1125 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1127 // add the functions to the queue
1128 guiApp->addToFuncRequestQueue(cmd);
1131 // now process the collected functions. We perform the events
1132 // asynchronously. This prevents potential problems in case the
1133 // BufferView is closed within an event.
1134 guiApp->processFuncRequestQueueAsync();
1138 void GuiView::message(docstring const & str)
1140 if (ForkedProcess::iAmAChild())
1143 // call is moved to GUI-thread by GuiProgress
1144 d.progress_->appendMessage(toqstr(str));
1148 void GuiView::clearMessageText()
1150 message(docstring());
1154 void GuiView::updateStatusBarMessage(QString const & str)
1156 statusBar()->showMessage(str);
1157 d.statusbar_timer_.stop();
1158 d.statusbar_timer_.start(3000);
1162 void GuiView::clearMessage()
1164 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1165 // the hasFocus function mostly returns false, even if the focus is on
1166 // a workarea in this view.
1170 d.statusbar_timer_.stop();
1174 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1176 if (wa != d.current_work_area_
1177 || wa->bufferView().buffer().isInternal())
1179 Buffer const & buf = wa->bufferView().buffer();
1180 // Set the windows title
1181 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1182 if (buf.notifiesExternalModification()) {
1183 title = bformat(_("%1$s (modified externally)"), title);
1184 // If the external modification status has changed, then maybe the status of
1185 // buffer-save has changed too.
1189 title += from_ascii(" - LyX");
1191 setWindowTitle(toqstr(title));
1192 // Sets the path for the window: this is used by OSX to
1193 // allow a context click on the title bar showing a menu
1194 // with the path up to the file
1195 setWindowFilePath(toqstr(buf.absFileName()));
1196 // Tell Qt whether the current document is changed
1197 setWindowModified(!buf.isClean());
1199 if (buf.params().shell_escape)
1200 shell_escape_->show();
1202 shell_escape_->hide();
1204 if (buf.hasReadonlyFlag())
1209 if (buf.lyxvc().inUse()) {
1210 version_control_->show();
1211 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1213 version_control_->hide();
1217 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1219 if (d.current_work_area_)
1220 // disconnect the current work area from all slots
1221 QObject::disconnect(d.current_work_area_, 0, this, 0);
1223 disconnectBufferView();
1224 connectBufferView(wa->bufferView());
1225 connectBuffer(wa->bufferView().buffer());
1226 d.current_work_area_ = wa;
1227 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1228 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1229 QObject::connect(wa, SIGNAL(busy(bool)),
1230 this, SLOT(setBusy(bool)));
1231 // connection of a signal to a signal
1232 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1233 this, SIGNAL(bufferViewChanged()));
1234 Q_EMIT updateWindowTitle(wa);
1235 Q_EMIT bufferViewChanged();
1239 void GuiView::onBufferViewChanged()
1242 // Buffer-dependent dialogs must be updated. This is done here because
1243 // some dialogs require buffer()->text.
1248 void GuiView::on_lastWorkAreaRemoved()
1251 // We already are in a close event. Nothing more to do.
1254 if (d.splitter_->count() > 1)
1255 // We have a splitter so don't close anything.
1258 // Reset and updates the dialogs.
1259 Q_EMIT bufferViewChanged();
1264 if (lyxrc.open_buffers_in_tabs)
1265 // Nothing more to do, the window should stay open.
1268 if (guiApp->viewIds().size() > 1) {
1274 // On Mac we also close the last window because the application stay
1275 // resident in memory. On other platforms we don't close the last
1276 // window because this would quit the application.
1282 void GuiView::updateStatusBar()
1284 // let the user see the explicit message
1285 if (d.statusbar_timer_.isActive())
1292 void GuiView::showMessage()
1296 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1297 if (msg.isEmpty()) {
1298 BufferView const * bv = currentBufferView();
1300 msg = toqstr(bv->cursor().currentState(devel_mode_));
1302 msg = qt_("Welcome to LyX!");
1304 statusBar()->showMessage(msg);
1308 bool GuiView::event(QEvent * e)
1312 // Useful debug code:
1313 //case QEvent::ActivationChange:
1314 //case QEvent::WindowDeactivate:
1315 //case QEvent::Paint:
1316 //case QEvent::Enter:
1317 //case QEvent::Leave:
1318 //case QEvent::HoverEnter:
1319 //case QEvent::HoverLeave:
1320 //case QEvent::HoverMove:
1321 //case QEvent::StatusTip:
1322 //case QEvent::DragEnter:
1323 //case QEvent::DragLeave:
1324 //case QEvent::Drop:
1327 case QEvent::WindowActivate: {
1328 GuiView * old_view = guiApp->currentView();
1329 if (this == old_view) {
1331 return QMainWindow::event(e);
1333 if (old_view && old_view->currentBufferView()) {
1334 // save current selection to the selection buffer to allow
1335 // middle-button paste in this window.
1336 cap::saveSelection(old_view->currentBufferView()->cursor());
1338 guiApp->setCurrentView(this);
1339 if (d.current_work_area_)
1340 on_currentWorkAreaChanged(d.current_work_area_);
1344 return QMainWindow::event(e);
1347 case QEvent::ShortcutOverride: {
1349 if (isFullScreen() && menuBar()->isHidden()) {
1350 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1351 // FIXME: we should also try to detect special LyX shortcut such as
1352 // Alt-P and Alt-M. Right now there is a hack in
1353 // GuiWorkArea::processKeySym() that hides again the menubar for
1355 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1357 return QMainWindow::event(e);
1360 return QMainWindow::event(e);
1364 return QMainWindow::event(e);
1368 void GuiView::resetWindowTitle()
1370 setWindowTitle(qt_("LyX"));
1373 bool GuiView::focusNextPrevChild(bool /*next*/)
1380 bool GuiView::busy() const
1386 void GuiView::setBusy(bool busy)
1388 bool const busy_before = busy_ > 0;
1389 busy ? ++busy_ : --busy_;
1390 if ((busy_ > 0) == busy_before)
1391 // busy state didn't change
1395 QApplication::setOverrideCursor(Qt::WaitCursor);
1398 QApplication::restoreOverrideCursor();
1403 void GuiView::resetCommandExecute()
1405 command_execute_ = false;
1410 double GuiView::pixelRatio() const
1412 #if QT_VERSION >= 0x050000
1413 return qt_scale_factor * devicePixelRatio();
1420 GuiWorkArea * GuiView::workArea(int index)
1422 if (TabWorkArea * twa = d.currentTabWorkArea())
1423 if (index < twa->count())
1424 return twa->workArea(index);
1429 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1431 if (currentWorkArea()
1432 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1433 return currentWorkArea();
1434 if (TabWorkArea * twa = d.currentTabWorkArea())
1435 return twa->workArea(buffer);
1440 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1442 // Automatically create a TabWorkArea if there are none yet.
1443 TabWorkArea * tab_widget = d.splitter_->count()
1444 ? d.currentTabWorkArea() : addTabWorkArea();
1445 return tab_widget->addWorkArea(buffer, *this);
1449 TabWorkArea * GuiView::addTabWorkArea()
1451 TabWorkArea * twa = new TabWorkArea;
1452 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1453 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1454 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1455 this, SLOT(on_lastWorkAreaRemoved()));
1457 d.splitter_->addWidget(twa);
1458 d.stack_widget_->setCurrentWidget(d.splitter_);
1463 GuiWorkArea const * GuiView::currentWorkArea() const
1465 return d.current_work_area_;
1469 GuiWorkArea * GuiView::currentWorkArea()
1471 return d.current_work_area_;
1475 GuiWorkArea const * GuiView::currentMainWorkArea() const
1477 if (!d.currentTabWorkArea())
1479 return d.currentTabWorkArea()->currentWorkArea();
1483 GuiWorkArea * GuiView::currentMainWorkArea()
1485 if (!d.currentTabWorkArea())
1487 return d.currentTabWorkArea()->currentWorkArea();
1491 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1493 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1495 d.current_work_area_ = 0;
1497 Q_EMIT bufferViewChanged();
1501 // FIXME: I've no clue why this is here and why it accesses
1502 // theGuiApp()->currentView, which might be 0 (bug 6464).
1503 // See also 27525 (vfr).
1504 if (theGuiApp()->currentView() == this
1505 && theGuiApp()->currentView()->currentWorkArea() == wa)
1508 if (currentBufferView())
1509 cap::saveSelection(currentBufferView()->cursor());
1511 theGuiApp()->setCurrentView(this);
1512 d.current_work_area_ = wa;
1514 // We need to reset this now, because it will need to be
1515 // right if the tabWorkArea gets reset in the for loop. We
1516 // will change it back if we aren't in that case.
1517 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1518 d.current_main_work_area_ = wa;
1520 for (int i = 0; i != d.splitter_->count(); ++i) {
1521 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1522 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1523 << ", Current main wa: " << currentMainWorkArea());
1528 d.current_main_work_area_ = old_cmwa;
1530 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1531 on_currentWorkAreaChanged(wa);
1532 BufferView & bv = wa->bufferView();
1533 bv.cursor().fixIfBroken();
1535 wa->setUpdatesEnabled(true);
1536 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1540 void GuiView::removeWorkArea(GuiWorkArea * wa)
1542 LASSERT(wa, return);
1543 if (wa == d.current_work_area_) {
1545 disconnectBufferView();
1546 d.current_work_area_ = 0;
1547 d.current_main_work_area_ = 0;
1550 bool found_twa = false;
1551 for (int i = 0; i != d.splitter_->count(); ++i) {
1552 TabWorkArea * twa = d.tabWorkArea(i);
1553 if (twa->removeWorkArea(wa)) {
1554 // Found in this tab group, and deleted the GuiWorkArea.
1556 if (twa->count() != 0) {
1557 if (d.current_work_area_ == 0)
1558 // This means that we are closing the current GuiWorkArea, so
1559 // switch to the next GuiWorkArea in the found TabWorkArea.
1560 setCurrentWorkArea(twa->currentWorkArea());
1562 // No more WorkAreas in this tab group, so delete it.
1569 // It is not a tabbed work area (i.e., the search work area), so it
1570 // should be deleted by other means.
1571 LASSERT(found_twa, return);
1573 if (d.current_work_area_ == 0) {
1574 if (d.splitter_->count() != 0) {
1575 TabWorkArea * twa = d.currentTabWorkArea();
1576 setCurrentWorkArea(twa->currentWorkArea());
1578 // No more work areas, switch to the background widget.
1579 setCurrentWorkArea(0);
1585 LayoutBox * GuiView::getLayoutDialog() const
1591 void GuiView::updateLayoutList()
1594 d.layout_->updateContents(false);
1598 void GuiView::updateToolbars()
1600 ToolbarMap::iterator end = d.toolbars_.end();
1601 if (d.current_work_area_) {
1603 if (d.current_work_area_->bufferView().cursor().inMathed()
1604 && !d.current_work_area_->bufferView().cursor().inRegexped())
1605 context |= Toolbars::MATH;
1606 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1607 context |= Toolbars::TABLE;
1608 if (currentBufferView()->buffer().areChangesPresent()
1609 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1610 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1611 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1612 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1613 context |= Toolbars::REVIEW;
1614 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1615 context |= Toolbars::MATHMACROTEMPLATE;
1616 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1617 context |= Toolbars::IPA;
1618 if (command_execute_)
1619 context |= Toolbars::MINIBUFFER;
1620 if (minibuffer_focus_) {
1621 context |= Toolbars::MINIBUFFER_FOCUS;
1622 minibuffer_focus_ = false;
1625 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1626 it->second->update(context);
1628 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1629 it->second->update();
1633 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1635 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1636 LASSERT(newBuffer, return);
1638 GuiWorkArea * wa = workArea(*newBuffer);
1641 newBuffer->masterBuffer()->updateBuffer();
1643 wa = addWorkArea(*newBuffer);
1644 // scroll to the position when the BufferView was last closed
1645 if (lyxrc.use_lastfilepos) {
1646 LastFilePosSection::FilePos filepos =
1647 theSession().lastFilePos().load(newBuffer->fileName());
1648 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1651 //Disconnect the old buffer...there's no new one.
1654 connectBuffer(*newBuffer);
1655 connectBufferView(wa->bufferView());
1657 setCurrentWorkArea(wa);
1661 void GuiView::connectBuffer(Buffer & buf)
1663 buf.setGuiDelegate(this);
1667 void GuiView::disconnectBuffer()
1669 if (d.current_work_area_)
1670 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1674 void GuiView::connectBufferView(BufferView & bv)
1676 bv.setGuiDelegate(this);
1680 void GuiView::disconnectBufferView()
1682 if (d.current_work_area_)
1683 d.current_work_area_->bufferView().setGuiDelegate(0);
1687 void GuiView::errors(string const & error_type, bool from_master)
1689 BufferView const * const bv = currentBufferView();
1693 #if EXPORT_in_THREAD
1694 // We are called with from_master == false by default, so we
1695 // have to figure out whether that is the case or not.
1696 ErrorList & el = bv->buffer().errorList(error_type);
1698 el = bv->buffer().masterBuffer()->errorList(error_type);
1702 ErrorList const & el = from_master ?
1703 bv->buffer().masterBuffer()->errorList(error_type) :
1704 bv->buffer().errorList(error_type);
1710 string err = error_type;
1712 err = "from_master|" + error_type;
1713 showDialog("errorlist", err);
1717 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1719 d.toc_models_.updateItem(toqstr(type), dit);
1723 void GuiView::structureChanged()
1725 // This is called from the Buffer, which has no way to ensure that cursors
1726 // in BufferView remain valid.
1727 if (documentBufferView())
1728 documentBufferView()->cursor().sanitize();
1729 // FIXME: This is slightly expensive, though less than the tocBackend update
1730 // (#9880). This also resets the view in the Toc Widget (#6675).
1731 d.toc_models_.reset(documentBufferView());
1732 // Navigator needs more than a simple update in this case. It needs to be
1734 updateDialog("toc", "");
1738 void GuiView::updateDialog(string const & name, string const & sdata)
1740 if (!isDialogVisible(name))
1743 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1744 if (it == d.dialogs_.end())
1747 Dialog * const dialog = it->second.get();
1748 if (dialog->isVisibleView())
1749 dialog->initialiseParams(sdata);
1753 BufferView * GuiView::documentBufferView()
1755 return currentMainWorkArea()
1756 ? ¤tMainWorkArea()->bufferView()
1761 BufferView const * GuiView::documentBufferView() const
1763 return currentMainWorkArea()
1764 ? ¤tMainWorkArea()->bufferView()
1769 BufferView * GuiView::currentBufferView()
1771 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1775 BufferView const * GuiView::currentBufferView() const
1777 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1781 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1782 Buffer const * orig, Buffer * clone)
1784 bool const success = clone->autoSave();
1786 busyBuffers.remove(orig);
1788 ? _("Automatic save done.")
1789 : _("Automatic save failed!");
1793 void GuiView::autoSave()
1795 LYXERR(Debug::INFO, "Running autoSave()");
1797 Buffer * buffer = documentBufferView()
1798 ? &documentBufferView()->buffer() : 0;
1800 resetAutosaveTimers();
1804 GuiViewPrivate::busyBuffers.insert(buffer);
1805 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1806 buffer, buffer->cloneBufferOnly());
1807 d.autosave_watcher_.setFuture(f);
1808 resetAutosaveTimers();
1812 void GuiView::resetAutosaveTimers()
1815 d.autosave_timeout_.restart();
1819 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1822 Buffer * buf = currentBufferView()
1823 ? ¤tBufferView()->buffer() : 0;
1824 Buffer * doc_buffer = documentBufferView()
1825 ? &(documentBufferView()->buffer()) : 0;
1828 /* In LyX/Mac, when a dialog is open, the menus of the
1829 application can still be accessed without giving focus to
1830 the main window. In this case, we want to disable the menu
1831 entries that are buffer-related.
1832 This code must not be used on Linux and Windows, since it
1833 would disable buffer-related entries when hovering over the
1834 menu (see bug #9574).
1836 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1842 // Check whether we need a buffer
1843 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1844 // no, exit directly
1845 flag.message(from_utf8(N_("Command not allowed with"
1846 "out any document open")));
1847 flag.setEnabled(false);
1851 if (cmd.origin() == FuncRequest::TOC) {
1852 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1853 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1854 flag.setEnabled(false);
1858 switch(cmd.action()) {
1859 case LFUN_BUFFER_IMPORT:
1862 case LFUN_MASTER_BUFFER_UPDATE:
1863 case LFUN_MASTER_BUFFER_VIEW:
1865 && (doc_buffer->parent() != 0
1866 || doc_buffer->hasChildren())
1867 && !d.processing_thread_watcher_.isRunning();
1870 case LFUN_BUFFER_UPDATE:
1871 case LFUN_BUFFER_VIEW: {
1872 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1876 string format = to_utf8(cmd.argument());
1877 if (cmd.argument().empty())
1878 format = doc_buffer->params().getDefaultOutputFormat();
1879 enable = doc_buffer->params().isExportable(format, true);
1883 case LFUN_BUFFER_RELOAD:
1884 enable = doc_buffer && !doc_buffer->isUnnamed()
1885 && doc_buffer->fileName().exists()
1886 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1889 case LFUN_BUFFER_CHILD_OPEN:
1890 enable = doc_buffer != 0;
1893 case LFUN_BUFFER_WRITE:
1894 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1897 //FIXME: This LFUN should be moved to GuiApplication.
1898 case LFUN_BUFFER_WRITE_ALL: {
1899 // We enable the command only if there are some modified buffers
1900 Buffer * first = theBufferList().first();
1905 // We cannot use a for loop as the buffer list is a cycle.
1907 if (!b->isClean()) {
1911 b = theBufferList().next(b);
1912 } while (b != first);
1916 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1917 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1920 case LFUN_BUFFER_EXPORT: {
1921 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1925 return doc_buffer->getStatus(cmd, flag);
1929 case LFUN_BUFFER_EXPORT_AS:
1930 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1935 case LFUN_BUFFER_WRITE_AS:
1936 enable = doc_buffer != 0;
1939 case LFUN_EXPORT_CANCEL:
1940 enable = d.processing_thread_watcher_.isRunning();
1943 case LFUN_BUFFER_CLOSE:
1944 case LFUN_VIEW_CLOSE:
1945 enable = doc_buffer != 0;
1948 case LFUN_BUFFER_CLOSE_ALL:
1949 enable = theBufferList().last() != theBufferList().first();
1952 case LFUN_BUFFER_CHKTEX: {
1953 // hide if we have no checktex command
1954 if (lyxrc.chktex_command.empty()) {
1955 flag.setUnknown(true);
1959 if (!doc_buffer || !doc_buffer->params().isLatex()
1960 || d.processing_thread_watcher_.isRunning()) {
1961 // grey out, don't hide
1969 case LFUN_VIEW_SPLIT:
1970 if (cmd.getArg(0) == "vertical")
1971 enable = doc_buffer && (d.splitter_->count() == 1 ||
1972 d.splitter_->orientation() == Qt::Vertical);
1974 enable = doc_buffer && (d.splitter_->count() == 1 ||
1975 d.splitter_->orientation() == Qt::Horizontal);
1978 case LFUN_TAB_GROUP_CLOSE:
1979 enable = d.tabWorkAreaCount() > 1;
1982 case LFUN_DEVEL_MODE_TOGGLE:
1983 flag.setOnOff(devel_mode_);
1986 case LFUN_TOOLBAR_TOGGLE: {
1987 string const name = cmd.getArg(0);
1988 if (GuiToolbar * t = toolbar(name))
1989 flag.setOnOff(t->isVisible());
1992 docstring const msg =
1993 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1999 case LFUN_TOOLBAR_MOVABLE: {
2000 string const name = cmd.getArg(0);
2001 // use negation since locked == !movable
2003 // toolbar name * locks all toolbars
2004 flag.setOnOff(!toolbarsMovable_);
2005 else if (GuiToolbar * t = toolbar(name))
2006 flag.setOnOff(!(t->isMovable()));
2009 docstring const msg =
2010 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2016 case LFUN_ICON_SIZE:
2017 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2020 case LFUN_DROP_LAYOUTS_CHOICE:
2024 case LFUN_UI_TOGGLE:
2025 flag.setOnOff(isFullScreen());
2028 case LFUN_DIALOG_DISCONNECT_INSET:
2031 case LFUN_DIALOG_HIDE:
2032 // FIXME: should we check if the dialog is shown?
2035 case LFUN_DIALOG_TOGGLE:
2036 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2039 case LFUN_DIALOG_SHOW: {
2040 string const name = cmd.getArg(0);
2042 enable = name == "aboutlyx"
2043 || name == "file" //FIXME: should be removed.
2045 || name == "texinfo"
2046 || name == "progress"
2047 || name == "compare";
2048 else if (name == "character" || name == "symbols"
2049 || name == "mathdelimiter" || name == "mathmatrix") {
2050 if (!buf || buf->isReadonly())
2053 Cursor const & cur = currentBufferView()->cursor();
2054 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2057 else if (name == "latexlog")
2058 enable = FileName(doc_buffer->logName()).isReadableFile();
2059 else if (name == "spellchecker")
2060 enable = theSpellChecker()
2061 && !doc_buffer->isReadonly()
2062 && !doc_buffer->text().empty();
2063 else if (name == "vclog")
2064 enable = doc_buffer->lyxvc().inUse();
2068 case LFUN_DIALOG_UPDATE: {
2069 string const name = cmd.getArg(0);
2071 enable = name == "prefs";
2075 case LFUN_COMMAND_EXECUTE:
2077 case LFUN_MENU_OPEN:
2078 // Nothing to check.
2081 case LFUN_COMPLETION_INLINE:
2082 if (!d.current_work_area_
2083 || !d.current_work_area_->completer().inlinePossible(
2084 currentBufferView()->cursor()))
2088 case LFUN_COMPLETION_POPUP:
2089 if (!d.current_work_area_
2090 || !d.current_work_area_->completer().popupPossible(
2091 currentBufferView()->cursor()))
2096 if (!d.current_work_area_
2097 || !d.current_work_area_->completer().inlinePossible(
2098 currentBufferView()->cursor()))
2102 case LFUN_COMPLETION_ACCEPT:
2103 if (!d.current_work_area_
2104 || (!d.current_work_area_->completer().popupVisible()
2105 && !d.current_work_area_->completer().inlineVisible()
2106 && !d.current_work_area_->completer().completionAvailable()))
2110 case LFUN_COMPLETION_CANCEL:
2111 if (!d.current_work_area_
2112 || (!d.current_work_area_->completer().popupVisible()
2113 && !d.current_work_area_->completer().inlineVisible()))
2117 case LFUN_BUFFER_ZOOM_OUT:
2118 case LFUN_BUFFER_ZOOM_IN: {
2119 // only diff between these two is that the default for ZOOM_OUT
2121 bool const neg_zoom =
2122 convert<int>(cmd.argument()) < 0 ||
2123 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2124 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2125 docstring const msg =
2126 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2130 enable = doc_buffer;
2134 case LFUN_BUFFER_ZOOM: {
2135 bool const less_than_min_zoom =
2136 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2137 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2138 docstring const msg =
2139 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2144 enable = doc_buffer;
2148 case LFUN_BUFFER_MOVE_NEXT:
2149 case LFUN_BUFFER_MOVE_PREVIOUS:
2150 // we do not cycle when moving
2151 case LFUN_BUFFER_NEXT:
2152 case LFUN_BUFFER_PREVIOUS:
2153 // because we cycle, it doesn't matter whether on first or last
2154 enable = (d.currentTabWorkArea()->count() > 1);
2156 case LFUN_BUFFER_SWITCH:
2157 // toggle on the current buffer, but do not toggle off
2158 // the other ones (is that a good idea?)
2160 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2161 flag.setOnOff(true);
2164 case LFUN_VC_REGISTER:
2165 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2167 case LFUN_VC_RENAME:
2168 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2171 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2173 case LFUN_VC_CHECK_IN:
2174 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2176 case LFUN_VC_CHECK_OUT:
2177 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2179 case LFUN_VC_LOCKING_TOGGLE:
2180 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2181 && doc_buffer->lyxvc().lockingToggleEnabled();
2182 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2184 case LFUN_VC_REVERT:
2185 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2186 && !doc_buffer->hasReadonlyFlag();
2188 case LFUN_VC_UNDO_LAST:
2189 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2191 case LFUN_VC_REPO_UPDATE:
2192 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2194 case LFUN_VC_COMMAND: {
2195 if (cmd.argument().empty())
2197 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2201 case LFUN_VC_COMPARE:
2202 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2205 case LFUN_SERVER_GOTO_FILE_ROW:
2206 case LFUN_LYX_ACTIVATE:
2208 case LFUN_FORWARD_SEARCH:
2209 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2212 case LFUN_FILE_INSERT_PLAINTEXT:
2213 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2214 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2217 case LFUN_SPELLING_CONTINUOUSLY:
2218 flag.setOnOff(lyxrc.spellcheck_continuously);
2226 flag.setEnabled(false);
2232 static FileName selectTemplateFile()
2234 FileDialog dlg(qt_("Select template file"));
2235 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2236 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2238 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2239 QStringList(qt_("LyX Documents (*.lyx)")));
2241 if (result.first == FileDialog::Later)
2243 if (result.second.isEmpty())
2245 return FileName(fromqstr(result.second));
2249 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2253 Buffer * newBuffer = 0;
2255 newBuffer = checkAndLoadLyXFile(filename);
2256 } catch (ExceptionMessage const & e) {
2263 message(_("Document not loaded."));
2267 setBuffer(newBuffer);
2268 newBuffer->errors("Parse");
2271 theSession().lastFiles().add(filename);
2272 theSession().writeFile();
2279 void GuiView::openDocument(string const & fname)
2281 string initpath = lyxrc.document_path;
2283 if (documentBufferView()) {
2284 string const trypath = documentBufferView()->buffer().filePath();
2285 // If directory is writeable, use this as default.
2286 if (FileName(trypath).isDirWritable())
2292 if (fname.empty()) {
2293 FileDialog dlg(qt_("Select document to open"));
2294 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2295 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2297 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2298 FileDialog::Result result =
2299 dlg.open(toqstr(initpath), filter);
2301 if (result.first == FileDialog::Later)
2304 filename = fromqstr(result.second);
2306 // check selected filename
2307 if (filename.empty()) {
2308 message(_("Canceled."));
2314 // get absolute path of file and add ".lyx" to the filename if
2316 FileName const fullname =
2317 fileSearch(string(), filename, "lyx", support::may_not_exist);
2318 if (!fullname.empty())
2319 filename = fullname.absFileName();
2321 if (!fullname.onlyPath().isDirectory()) {
2322 Alert::warning(_("Invalid filename"),
2323 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2324 from_utf8(fullname.absFileName())));
2328 // if the file doesn't exist and isn't already open (bug 6645),
2329 // let the user create one
2330 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2331 !LyXVC::file_not_found_hook(fullname)) {
2332 // the user specifically chose this name. Believe him.
2333 Buffer * const b = newFile(filename, string(), true);
2339 docstring const disp_fn = makeDisplayPath(filename);
2340 message(bformat(_("Opening document %1$s..."), disp_fn));
2343 Buffer * buf = loadDocument(fullname);
2345 str2 = bformat(_("Document %1$s opened."), disp_fn);
2346 if (buf->lyxvc().inUse())
2347 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2348 " " + _("Version control detected.");
2350 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2355 // FIXME: clean that
2356 static bool import(GuiView * lv, FileName const & filename,
2357 string const & format, ErrorList & errorList)
2359 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2361 string loader_format;
2362 vector<string> loaders = theConverters().loaders();
2363 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2364 vector<string>::const_iterator it = loaders.begin();
2365 vector<string>::const_iterator en = loaders.end();
2366 for (; it != en; ++it) {
2367 if (!theConverters().isReachable(format, *it))
2370 string const tofile =
2371 support::changeExtension(filename.absFileName(),
2372 theFormats().extension(*it));
2373 if (!theConverters().convert(0, filename, FileName(tofile),
2374 filename, format, *it, errorList))
2376 loader_format = *it;
2379 if (loader_format.empty()) {
2380 frontend::Alert::error(_("Couldn't import file"),
2381 bformat(_("No information for importing the format %1$s."),
2382 theFormats().prettyName(format)));
2386 loader_format = format;
2388 if (loader_format == "lyx") {
2389 Buffer * buf = lv->loadDocument(lyxfile);
2393 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2397 bool as_paragraphs = loader_format == "textparagraph";
2398 string filename2 = (loader_format == format) ? filename.absFileName()
2399 : support::changeExtension(filename.absFileName(),
2400 theFormats().extension(loader_format));
2401 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2403 guiApp->setCurrentView(lv);
2404 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2411 void GuiView::importDocument(string const & argument)
2414 string filename = split(argument, format, ' ');
2416 LYXERR(Debug::INFO, format << " file: " << filename);
2418 // need user interaction
2419 if (filename.empty()) {
2420 string initpath = lyxrc.document_path;
2421 if (documentBufferView()) {
2422 string const trypath = documentBufferView()->buffer().filePath();
2423 // If directory is writeable, use this as default.
2424 if (FileName(trypath).isDirWritable())
2428 docstring const text = bformat(_("Select %1$s file to import"),
2429 theFormats().prettyName(format));
2431 FileDialog dlg(toqstr(text));
2432 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2433 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2435 docstring filter = theFormats().prettyName(format);
2438 filter += from_utf8(theFormats().extensions(format));
2441 FileDialog::Result result =
2442 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2444 if (result.first == FileDialog::Later)
2447 filename = fromqstr(result.second);
2449 // check selected filename
2450 if (filename.empty())
2451 message(_("Canceled."));
2454 if (filename.empty())
2457 // get absolute path of file
2458 FileName const fullname(support::makeAbsPath(filename));
2460 // Can happen if the user entered a path into the dialog
2462 if (fullname.onlyFileName().empty()) {
2463 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2464 "Aborting import."),
2465 from_utf8(fullname.absFileName()));
2466 frontend::Alert::error(_("File name error"), msg);
2467 message(_("Canceled."));
2472 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2474 // Check if the document already is open
2475 Buffer * buf = theBufferList().getBuffer(lyxfile);
2478 if (!closeBuffer()) {
2479 message(_("Canceled."));
2484 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2486 // if the file exists already, and we didn't do
2487 // -i lyx thefile.lyx, warn
2488 if (lyxfile.exists() && fullname != lyxfile) {
2490 docstring text = bformat(_("The document %1$s already exists.\n\n"
2491 "Do you want to overwrite that document?"), displaypath);
2492 int const ret = Alert::prompt(_("Overwrite document?"),
2493 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2496 message(_("Canceled."));
2501 message(bformat(_("Importing %1$s..."), displaypath));
2502 ErrorList errorList;
2503 if (import(this, fullname, format, errorList))
2504 message(_("imported."));
2506 message(_("file not imported!"));
2508 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2512 void GuiView::newDocument(string const & filename, bool from_template)
2514 FileName initpath(lyxrc.document_path);
2515 if (documentBufferView()) {
2516 FileName const trypath(documentBufferView()->buffer().filePath());
2517 // If directory is writeable, use this as default.
2518 if (trypath.isDirWritable())
2522 string templatefile;
2523 if (from_template) {
2524 templatefile = selectTemplateFile().absFileName();
2525 if (templatefile.empty())
2530 if (filename.empty())
2531 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2533 b = newFile(filename, templatefile, true);
2538 // If no new document could be created, it is unsure
2539 // whether there is a valid BufferView.
2540 if (currentBufferView())
2541 // Ensure the cursor is correctly positioned on screen.
2542 currentBufferView()->showCursor();
2546 void GuiView::insertLyXFile(docstring const & fname)
2548 BufferView * bv = documentBufferView();
2553 FileName filename(to_utf8(fname));
2554 if (filename.empty()) {
2555 // Launch a file browser
2557 string initpath = lyxrc.document_path;
2558 string const trypath = bv->buffer().filePath();
2559 // If directory is writeable, use this as default.
2560 if (FileName(trypath).isDirWritable())
2564 FileDialog dlg(qt_("Select LyX document to insert"));
2565 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2566 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2568 FileDialog::Result result = dlg.open(toqstr(initpath),
2569 QStringList(qt_("LyX Documents (*.lyx)")));
2571 if (result.first == FileDialog::Later)
2575 filename.set(fromqstr(result.second));
2577 // check selected filename
2578 if (filename.empty()) {
2579 // emit message signal.
2580 message(_("Canceled."));
2585 bv->insertLyXFile(filename);
2586 bv->buffer().errors("Parse");
2590 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2592 FileName fname = b.fileName();
2593 FileName const oldname = fname;
2595 if (!newname.empty()) {
2597 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2599 // Switch to this Buffer.
2602 // No argument? Ask user through dialog.
2604 FileDialog dlg(qt_("Choose a filename to save document as"));
2605 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2606 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2608 if (!isLyXFileName(fname.absFileName()))
2609 fname.changeExtension(".lyx");
2611 FileDialog::Result result =
2612 dlg.save(toqstr(fname.onlyPath().absFileName()),
2613 QStringList(qt_("LyX Documents (*.lyx)")),
2614 toqstr(fname.onlyFileName()));
2616 if (result.first == FileDialog::Later)
2619 fname.set(fromqstr(result.second));
2624 if (!isLyXFileName(fname.absFileName()))
2625 fname.changeExtension(".lyx");
2628 // fname is now the new Buffer location.
2630 // if there is already a Buffer open with this name, we do not want
2631 // to have another one. (the second test makes sure we're not just
2632 // trying to overwrite ourselves, which is fine.)
2633 if (theBufferList().exists(fname) && fname != oldname
2634 && theBufferList().getBuffer(fname) != &b) {
2635 docstring const text =
2636 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2637 "Please close it before attempting to overwrite it.\n"
2638 "Do you want to choose a new filename?"),
2639 from_utf8(fname.absFileName()));
2640 int const ret = Alert::prompt(_("Chosen File Already Open"),
2641 text, 0, 1, _("&Rename"), _("&Cancel"));
2643 case 0: return renameBuffer(b, docstring(), kind);
2644 case 1: return false;
2649 bool const existsLocal = fname.exists();
2650 bool const existsInVC = LyXVC::fileInVC(fname);
2651 if (existsLocal || existsInVC) {
2652 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2653 if (kind != LV_WRITE_AS && existsInVC) {
2654 // renaming to a name that is already in VC
2656 docstring text = bformat(_("The document %1$s "
2657 "is already registered.\n\n"
2658 "Do you want to choose a new name?"),
2660 docstring const title = (kind == LV_VC_RENAME) ?
2661 _("Rename document?") : _("Copy document?");
2662 docstring const button = (kind == LV_VC_RENAME) ?
2663 _("&Rename") : _("&Copy");
2664 int const ret = Alert::prompt(title, text, 0, 1,
2665 button, _("&Cancel"));
2667 case 0: return renameBuffer(b, docstring(), kind);
2668 case 1: return false;
2673 docstring text = bformat(_("The document %1$s "
2674 "already exists.\n\n"
2675 "Do you want to overwrite that document?"),
2677 int const ret = Alert::prompt(_("Overwrite document?"),
2678 text, 0, 2, _("&Overwrite"),
2679 _("&Rename"), _("&Cancel"));
2682 case 1: return renameBuffer(b, docstring(), kind);
2683 case 2: return false;
2689 case LV_VC_RENAME: {
2690 string msg = b.lyxvc().rename(fname);
2693 message(from_utf8(msg));
2697 string msg = b.lyxvc().copy(fname);
2700 message(from_utf8(msg));
2706 // LyXVC created the file already in case of LV_VC_RENAME or
2707 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2708 // relative paths of included stuff right if we moved e.g. from
2709 // /a/b.lyx to /a/c/b.lyx.
2711 bool const saved = saveBuffer(b, fname);
2718 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2720 FileName fname = b.fileName();
2722 FileDialog dlg(qt_("Choose a filename to export the document as"));
2723 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2726 QString const anyformat = qt_("Guess from extension (*.*)");
2729 vector<Format const *> export_formats;
2730 for (Format const & f : theFormats())
2731 if (f.documentFormat())
2732 export_formats.push_back(&f);
2733 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2734 map<QString, string> fmap;
2737 for (Format const * f : export_formats) {
2738 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2739 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2741 from_ascii(f->extension())));
2742 types << loc_filter;
2743 fmap[loc_filter] = f->name();
2744 if (from_ascii(f->name()) == iformat) {
2745 filter = loc_filter;
2746 ext = f->extension();
2749 string ofname = fname.onlyFileName();
2751 ofname = support::changeExtension(ofname, ext);
2752 FileDialog::Result result =
2753 dlg.save(toqstr(fname.onlyPath().absFileName()),
2757 if (result.first != FileDialog::Chosen)
2761 fname.set(fromqstr(result.second));
2762 if (filter == anyformat)
2763 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2765 fmt_name = fmap[filter];
2766 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2767 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2769 if (fmt_name.empty() || fname.empty())
2772 // fname is now the new Buffer location.
2773 if (FileName(fname).exists()) {
2774 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2775 docstring text = bformat(_("The document %1$s already "
2776 "exists.\n\nDo you want to "
2777 "overwrite that document?"),
2779 int const ret = Alert::prompt(_("Overwrite document?"),
2780 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2783 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2784 case 2: return false;
2788 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2791 return dr.dispatched();
2795 bool GuiView::saveBuffer(Buffer & b)
2797 return saveBuffer(b, FileName());
2801 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2803 if (workArea(b) && workArea(b)->inDialogMode())
2806 if (fn.empty() && b.isUnnamed())
2807 return renameBuffer(b, docstring());
2809 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2811 theSession().lastFiles().add(b.fileName());
2812 theSession().writeFile();
2816 // Switch to this Buffer.
2819 // FIXME: we don't tell the user *WHY* the save failed !!
2820 docstring const file = makeDisplayPath(b.absFileName(), 30);
2821 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2822 "Do you want to rename the document and "
2823 "try again?"), file);
2824 int const ret = Alert::prompt(_("Rename and save?"),
2825 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2828 if (!renameBuffer(b, docstring()))
2837 return saveBuffer(b, fn);
2841 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2843 return closeWorkArea(wa, false);
2847 // We only want to close the buffer if it is not visible in other workareas
2848 // of the same view, nor in other views, and if this is not a child
2849 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2851 Buffer & buf = wa->bufferView().buffer();
2853 bool last_wa = d.countWorkAreasOf(buf) == 1
2854 && !inOtherView(buf) && !buf.parent();
2856 bool close_buffer = last_wa;
2859 if (lyxrc.close_buffer_with_last_view == "yes")
2861 else if (lyxrc.close_buffer_with_last_view == "no")
2862 close_buffer = false;
2865 if (buf.isUnnamed())
2866 file = from_utf8(buf.fileName().onlyFileName());
2868 file = buf.fileName().displayName(30);
2869 docstring const text = bformat(
2870 _("Last view on document %1$s is being closed.\n"
2871 "Would you like to close or hide the document?\n"
2873 "Hidden documents can be displayed back through\n"
2874 "the menu: View->Hidden->...\n"
2876 "To remove this question, set your preference in:\n"
2877 " Tools->Preferences->Look&Feel->UserInterface\n"
2879 int ret = Alert::prompt(_("Close or hide document?"),
2880 text, 0, 1, _("&Close"), _("&Hide"));
2881 close_buffer = (ret == 0);
2885 return closeWorkArea(wa, close_buffer);
2889 bool GuiView::closeBuffer()
2891 GuiWorkArea * wa = currentMainWorkArea();
2892 // coverity complained about this
2893 // it seems unnecessary, but perhaps is worth the check
2894 LASSERT(wa, return false);
2896 setCurrentWorkArea(wa);
2897 Buffer & buf = wa->bufferView().buffer();
2898 return closeWorkArea(wa, !buf.parent());
2902 void GuiView::writeSession() const {
2903 GuiWorkArea const * active_wa = currentMainWorkArea();
2904 for (int i = 0; i < d.splitter_->count(); ++i) {
2905 TabWorkArea * twa = d.tabWorkArea(i);
2906 for (int j = 0; j < twa->count(); ++j) {
2907 GuiWorkArea * wa = twa->workArea(j);
2908 Buffer & buf = wa->bufferView().buffer();
2909 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2915 bool GuiView::closeBufferAll()
2917 // Close the workareas in all other views
2918 QList<int> const ids = guiApp->viewIds();
2919 for (int i = 0; i != ids.size(); ++i) {
2920 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2924 // Close our own workareas
2925 if (!closeWorkAreaAll())
2928 // Now close the hidden buffers. We prevent hidden buffers from being
2929 // dirty, so we can just close them.
2930 theBufferList().closeAll();
2935 bool GuiView::closeWorkAreaAll()
2937 setCurrentWorkArea(currentMainWorkArea());
2939 // We might be in a situation that there is still a tabWorkArea, but
2940 // there are no tabs anymore. This can happen when we get here after a
2941 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2942 // many TabWorkArea's have no documents anymore.
2945 // We have to call count() each time, because it can happen that
2946 // more than one splitter will disappear in one iteration (bug 5998).
2947 while (d.splitter_->count() > empty_twa) {
2948 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2950 if (twa->count() == 0)
2953 setCurrentWorkArea(twa->currentWorkArea());
2954 if (!closeTabWorkArea(twa))
2962 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2967 Buffer & buf = wa->bufferView().buffer();
2969 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2970 Alert::warning(_("Close document"),
2971 _("Document could not be closed because it is being processed by LyX."));
2976 return closeBuffer(buf);
2978 if (!inMultiTabs(wa))
2979 if (!saveBufferIfNeeded(buf, true))
2987 bool GuiView::closeBuffer(Buffer & buf)
2989 // If we are in a close_event all children will be closed in some time,
2990 // so no need to do it here. This will ensure that the children end up
2991 // in the session file in the correct order. If we close the master
2992 // buffer, we can close or release the child buffers here too.
2993 bool success = true;
2995 ListOfBuffers clist = buf.getChildren();
2996 ListOfBuffers::const_iterator it = clist.begin();
2997 ListOfBuffers::const_iterator const bend = clist.end();
2998 for (; it != bend; ++it) {
2999 Buffer * child_buf = *it;
3000 if (theBufferList().isOthersChild(&buf, child_buf)) {
3001 child_buf->setParent(0);
3005 // FIXME: should we look in other tabworkareas?
3006 // ANSWER: I don't think so. I've tested, and if the child is
3007 // open in some other window, it closes without a problem.
3008 GuiWorkArea * child_wa = workArea(*child_buf);
3010 success = closeWorkArea(child_wa, true);
3014 // In this case the child buffer is open but hidden.
3015 // It therefore should not (MUST NOT) be dirty!
3016 LATTEST(child_buf->isClean());
3017 theBufferList().release(child_buf);
3022 // goto bookmark to update bookmark pit.
3023 // FIXME: we should update only the bookmarks related to this buffer!
3024 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3025 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3026 guiApp->gotoBookmark(i+1, false, false);
3028 if (saveBufferIfNeeded(buf, false)) {
3029 buf.removeAutosaveFile();
3030 theBufferList().release(&buf);
3034 // open all children again to avoid a crash because of dangling
3035 // pointers (bug 6603)
3041 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3043 while (twa == d.currentTabWorkArea()) {
3044 twa->setCurrentIndex(twa->count() - 1);
3046 GuiWorkArea * wa = twa->currentWorkArea();
3047 Buffer & b = wa->bufferView().buffer();
3049 // We only want to close the buffer if the same buffer is not visible
3050 // in another view, and if this is not a child and if we are closing
3051 // a view (not a tabgroup).
3052 bool const close_buffer =
3053 !inOtherView(b) && !b.parent() && closing_;
3055 if (!closeWorkArea(wa, close_buffer))
3062 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3064 if (buf.isClean() || buf.paragraphs().empty())
3067 // Switch to this Buffer.
3073 if (buf.isUnnamed()) {
3074 file = from_utf8(buf.fileName().onlyFileName());
3077 FileName filename = buf.fileName();
3079 file = filename.displayName(30);
3080 exists = filename.exists();
3083 // Bring this window to top before asking questions.
3088 if (hiding && buf.isUnnamed()) {
3089 docstring const text = bformat(_("The document %1$s has not been "
3090 "saved yet.\n\nDo you want to save "
3091 "the document?"), file);
3092 ret = Alert::prompt(_("Save new document?"),
3093 text, 0, 1, _("&Save"), _("&Cancel"));
3097 docstring const text = exists ?
3098 bformat(_("The document %1$s has unsaved changes."
3099 "\n\nDo you want to save the document or "
3100 "discard the changes?"), file) :
3101 bformat(_("The document %1$s has not been saved yet."
3102 "\n\nDo you want to save the document or "
3103 "discard it entirely?"), file);
3104 docstring const title = exists ?
3105 _("Save changed document?") : _("Save document?");
3106 ret = Alert::prompt(title, text, 0, 2,
3107 _("&Save"), _("&Discard"), _("&Cancel"));
3112 if (!saveBuffer(buf))
3116 // If we crash after this we could have no autosave file
3117 // but I guess this is really improbable (Jug).
3118 // Sometimes improbable things happen:
3119 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3120 // buf.removeAutosaveFile();
3122 // revert all changes
3133 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3135 Buffer & buf = wa->bufferView().buffer();
3137 for (int i = 0; i != d.splitter_->count(); ++i) {
3138 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3139 if (wa_ && wa_ != wa)
3142 return inOtherView(buf);
3146 bool GuiView::inOtherView(Buffer & buf)
3148 QList<int> const ids = guiApp->viewIds();
3150 for (int i = 0; i != ids.size(); ++i) {
3154 if (guiApp->view(ids[i]).workArea(buf))
3161 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3163 if (!documentBufferView())
3166 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3167 Buffer * const curbuf = &documentBufferView()->buffer();
3168 int nwa = twa->count();
3169 for (int i = 0; i < nwa; ++i) {
3170 if (&workArea(i)->bufferView().buffer() == curbuf) {
3172 if (np == NEXTBUFFER)
3173 next_index = (i == nwa - 1 ? 0 : i + 1);
3175 next_index = (i == 0 ? nwa - 1 : i - 1);
3177 twa->moveTab(i, next_index);
3179 setBuffer(&workArea(next_index)->bufferView().buffer());
3187 /// make sure the document is saved
3188 static bool ensureBufferClean(Buffer * buffer)
3190 LASSERT(buffer, return false);
3191 if (buffer->isClean() && !buffer->isUnnamed())
3194 docstring const file = buffer->fileName().displayName(30);
3197 if (!buffer->isUnnamed()) {
3198 text = bformat(_("The document %1$s has unsaved "
3199 "changes.\n\nDo you want to save "
3200 "the document?"), file);
3201 title = _("Save changed document?");
3204 text = bformat(_("The document %1$s has not been "
3205 "saved yet.\n\nDo you want to save "
3206 "the document?"), file);
3207 title = _("Save new document?");
3209 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3212 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3214 return buffer->isClean() && !buffer->isUnnamed();
3218 bool GuiView::reloadBuffer(Buffer & buf)
3220 currentBufferView()->cursor().reset();
3221 Buffer::ReadStatus status = buf.reload();
3222 return status == Buffer::ReadSuccess;
3226 void GuiView::checkExternallyModifiedBuffers()
3228 BufferList::iterator bit = theBufferList().begin();
3229 BufferList::iterator const bend = theBufferList().end();
3230 for (; bit != bend; ++bit) {
3231 Buffer * buf = *bit;
3232 if (buf->fileName().exists() && buf->isChecksumModified()) {
3233 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3234 " Reload now? Any local changes will be lost."),
3235 from_utf8(buf->absFileName()));
3236 int const ret = Alert::prompt(_("Reload externally changed document?"),
3237 text, 0, 1, _("&Reload"), _("&Cancel"));
3245 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3247 Buffer * buffer = documentBufferView()
3248 ? &(documentBufferView()->buffer()) : 0;
3250 switch (cmd.action()) {
3251 case LFUN_VC_REGISTER:
3252 if (!buffer || !ensureBufferClean(buffer))
3254 if (!buffer->lyxvc().inUse()) {
3255 if (buffer->lyxvc().registrer()) {
3256 reloadBuffer(*buffer);
3257 dr.clearMessageUpdate();
3262 case LFUN_VC_RENAME:
3263 case LFUN_VC_COPY: {
3264 if (!buffer || !ensureBufferClean(buffer))
3266 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3267 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3268 // Some changes are not yet committed.
3269 // We test here and not in getStatus(), since
3270 // this test is expensive.
3272 LyXVC::CommandResult ret =
3273 buffer->lyxvc().checkIn(log);
3275 if (ret == LyXVC::ErrorCommand ||
3276 ret == LyXVC::VCSuccess)
3277 reloadBuffer(*buffer);
3278 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3279 frontend::Alert::error(
3280 _("Revision control error."),
3281 _("Document could not be checked in."));
3285 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3286 LV_VC_RENAME : LV_VC_COPY;
3287 renameBuffer(*buffer, cmd.argument(), kind);
3292 case LFUN_VC_CHECK_IN:
3293 if (!buffer || !ensureBufferClean(buffer))
3295 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3297 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3299 // Only skip reloading if the checkin was cancelled or
3300 // an error occurred before the real checkin VCS command
3301 // was executed, since the VCS might have changed the
3302 // file even if it could not checkin successfully.
3303 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3304 reloadBuffer(*buffer);
3308 case LFUN_VC_CHECK_OUT:
3309 if (!buffer || !ensureBufferClean(buffer))
3311 if (buffer->lyxvc().inUse()) {
3312 dr.setMessage(buffer->lyxvc().checkOut());
3313 reloadBuffer(*buffer);
3317 case LFUN_VC_LOCKING_TOGGLE:
3318 LASSERT(buffer, return);
3319 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3321 if (buffer->lyxvc().inUse()) {
3322 string res = buffer->lyxvc().lockingToggle();
3324 frontend::Alert::error(_("Revision control error."),
3325 _("Error when setting the locking property."));
3328 reloadBuffer(*buffer);
3333 case LFUN_VC_REVERT:
3334 LASSERT(buffer, return);
3335 if (buffer->lyxvc().revert()) {
3336 reloadBuffer(*buffer);
3337 dr.clearMessageUpdate();
3341 case LFUN_VC_UNDO_LAST:
3342 LASSERT(buffer, return);
3343 buffer->lyxvc().undoLast();
3344 reloadBuffer(*buffer);
3345 dr.clearMessageUpdate();
3348 case LFUN_VC_REPO_UPDATE:
3349 LASSERT(buffer, return);
3350 if (ensureBufferClean(buffer)) {
3351 dr.setMessage(buffer->lyxvc().repoUpdate());
3352 checkExternallyModifiedBuffers();
3356 case LFUN_VC_COMMAND: {
3357 string flag = cmd.getArg(0);
3358 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3361 if (contains(flag, 'M')) {
3362 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3365 string path = cmd.getArg(1);
3366 if (contains(path, "$$p") && buffer)
3367 path = subst(path, "$$p", buffer->filePath());
3368 LYXERR(Debug::LYXVC, "Directory: " << path);
3370 if (!pp.isReadableDirectory()) {
3371 lyxerr << _("Directory is not accessible.") << endl;
3374 support::PathChanger p(pp);
3376 string command = cmd.getArg(2);
3377 if (command.empty())
3380 command = subst(command, "$$i", buffer->absFileName());
3381 command = subst(command, "$$p", buffer->filePath());
3383 command = subst(command, "$$m", to_utf8(message));
3384 LYXERR(Debug::LYXVC, "Command: " << command);
3386 one.startscript(Systemcall::Wait, command);
3390 if (contains(flag, 'I'))
3391 buffer->markDirty();
3392 if (contains(flag, 'R'))
3393 reloadBuffer(*buffer);
3398 case LFUN_VC_COMPARE: {
3399 if (cmd.argument().empty()) {
3400 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3404 string rev1 = cmd.getArg(0);
3409 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3412 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3413 f2 = buffer->absFileName();
3415 string rev2 = cmd.getArg(1);
3419 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3423 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3424 f1 << "\n" << f2 << "\n" );
3425 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3426 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3436 void GuiView::openChildDocument(string const & fname)
3438 LASSERT(documentBufferView(), return);
3439 Buffer & buffer = documentBufferView()->buffer();
3440 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3441 documentBufferView()->saveBookmark(false);
3443 if (theBufferList().exists(filename)) {
3444 child = theBufferList().getBuffer(filename);
3447 message(bformat(_("Opening child document %1$s..."),
3448 makeDisplayPath(filename.absFileName())));
3449 child = loadDocument(filename, false);
3451 // Set the parent name of the child document.
3452 // This makes insertion of citations and references in the child work,
3453 // when the target is in the parent or another child document.
3455 child->setParent(&buffer);
3459 bool GuiView::goToFileRow(string const & argument)
3463 size_t i = argument.find_last_of(' ');
3464 if (i != string::npos) {
3465 file_name = os::internal_path(trim(argument.substr(0, i)));
3466 istringstream is(argument.substr(i + 1));
3471 if (i == string::npos) {
3472 LYXERR0("Wrong argument: " << argument);
3476 string const abstmp = package().temp_dir().absFileName();
3477 string const realtmp = package().temp_dir().realPath();
3478 // We have to use os::path_prefix_is() here, instead of
3479 // simply prefixIs(), because the file name comes from
3480 // an external application and may need case adjustment.
3481 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3482 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3483 // Needed by inverse dvi search. If it is a file
3484 // in tmpdir, call the apropriated function.
3485 // If tmpdir is a symlink, we may have the real
3486 // path passed back, so we correct for that.
3487 if (!prefixIs(file_name, abstmp))
3488 file_name = subst(file_name, realtmp, abstmp);
3489 buf = theBufferList().getBufferFromTmp(file_name);
3491 // Must replace extension of the file to be .lyx
3492 // and get full path
3493 FileName const s = fileSearch(string(),
3494 support::changeExtension(file_name, ".lyx"), "lyx");
3495 // Either change buffer or load the file
3496 if (theBufferList().exists(s))
3497 buf = theBufferList().getBuffer(s);
3498 else if (s.exists()) {
3499 buf = loadDocument(s);
3504 _("File does not exist: %1$s"),
3505 makeDisplayPath(file_name)));
3511 _("No buffer for file: %1$s."),
3512 makeDisplayPath(file_name))
3517 bool success = documentBufferView()->setCursorFromRow(row);
3519 LYXERR(Debug::LATEX,
3520 "setCursorFromRow: invalid position for row " << row);
3521 frontend::Alert::error(_("Inverse Search Failed"),
3522 _("Invalid position requested by inverse search.\n"
3523 "You may need to update the viewed document."));
3529 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3531 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3532 menu->exec(QCursor::pos());
3537 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3539 Buffer::ExportStatus const status = func(format);
3541 // the cloning operation will have produced a clone of the entire set of
3542 // documents, starting from the master. so we must delete those.
3543 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3545 busyBuffers.remove(orig);
3550 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3552 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3553 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3557 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3559 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3560 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3564 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3566 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3567 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3571 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3572 string const & argument,
3573 Buffer const * used_buffer,
3574 docstring const & msg,
3575 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3576 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3577 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3582 string format = argument;
3584 format = used_buffer->params().getDefaultOutputFormat();
3585 processing_format = format;
3587 progress_->clearMessages();
3590 #if EXPORT_in_THREAD
3591 GuiViewPrivate::busyBuffers.insert(used_buffer);
3592 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3593 if (!cloned_buffer) {
3594 Alert::error(_("Export Error"),
3595 _("Error cloning the Buffer."));
3598 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3603 setPreviewFuture(f);
3604 last_export_format = used_buffer->params().bufferFormat();
3607 // We are asynchronous, so we don't know here anything about the success
3610 Buffer::ExportStatus status;
3612 status = (used_buffer->*syncFunc)(format, true);
3613 } else if (previewFunc) {
3614 status = (used_buffer->*previewFunc)(format);
3617 handleExportStatus(gv_, status, format);
3619 return (status == Buffer::ExportSuccess
3620 || status == Buffer::PreviewSuccess);
3624 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3626 BufferView * bv = currentBufferView();
3627 LASSERT(bv, return);
3629 // Let the current BufferView dispatch its own actions.
3630 bv->dispatch(cmd, dr);
3631 if (dr.dispatched())
3634 // Try with the document BufferView dispatch if any.
3635 BufferView * doc_bv = documentBufferView();
3636 if (doc_bv && doc_bv != bv) {
3637 doc_bv->dispatch(cmd, dr);
3638 if (dr.dispatched())
3642 // Then let the current Cursor dispatch its own actions.
3643 bv->cursor().dispatch(cmd);
3645 // update completion. We do it here and not in
3646 // processKeySym to avoid another redraw just for a
3647 // changed inline completion
3648 if (cmd.origin() == FuncRequest::KEYBOARD) {
3649 if (cmd.action() == LFUN_SELF_INSERT
3650 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3651 updateCompletion(bv->cursor(), true, true);
3652 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3653 updateCompletion(bv->cursor(), false, true);
3655 updateCompletion(bv->cursor(), false, false);
3658 dr = bv->cursor().result();
3662 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3664 BufferView * bv = currentBufferView();
3665 // By default we won't need any update.
3666 dr.screenUpdate(Update::None);
3667 // assume cmd will be dispatched
3668 dr.dispatched(true);
3670 Buffer * doc_buffer = documentBufferView()
3671 ? &(documentBufferView()->buffer()) : 0;
3673 if (cmd.origin() == FuncRequest::TOC) {
3674 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3675 // FIXME: do we need to pass a DispatchResult object here?
3676 toc->doDispatch(bv->cursor(), cmd);
3680 string const argument = to_utf8(cmd.argument());
3682 switch(cmd.action()) {
3683 case LFUN_BUFFER_CHILD_OPEN:
3684 openChildDocument(to_utf8(cmd.argument()));
3687 case LFUN_BUFFER_IMPORT:
3688 importDocument(to_utf8(cmd.argument()));
3691 case LFUN_BUFFER_EXPORT: {
3694 // GCC only sees strfwd.h when building merged
3695 if (::lyx::operator==(cmd.argument(), "custom")) {
3696 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3700 string const dest = cmd.getArg(1);
3701 FileName target_dir;
3702 if (!dest.empty() && FileName::isAbsolute(dest))
3703 target_dir = FileName(support::onlyPath(dest));
3705 target_dir = doc_buffer->fileName().onlyPath();
3707 string const format = (argument.empty() || argument == "default") ?
3708 doc_buffer->params().getDefaultOutputFormat() : argument;
3710 if ((dest.empty() && doc_buffer->isUnnamed())
3711 || !target_dir.isDirWritable()) {
3712 exportBufferAs(*doc_buffer, from_utf8(format));
3715 /* TODO/Review: Is it a problem to also export the children?
3716 See the update_unincluded flag */
3717 d.asyncBufferProcessing(format,
3720 &GuiViewPrivate::exportAndDestroy,
3723 // TODO Inform user about success
3727 case LFUN_BUFFER_EXPORT_AS: {
3728 LASSERT(doc_buffer, break);
3729 docstring f = cmd.argument();
3731 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3732 exportBufferAs(*doc_buffer, f);
3736 case LFUN_BUFFER_UPDATE: {
3737 d.asyncBufferProcessing(argument,
3740 &GuiViewPrivate::compileAndDestroy,
3745 case LFUN_BUFFER_VIEW: {
3746 d.asyncBufferProcessing(argument,
3748 _("Previewing ..."),
3749 &GuiViewPrivate::previewAndDestroy,
3754 case LFUN_MASTER_BUFFER_UPDATE: {
3755 d.asyncBufferProcessing(argument,
3756 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3758 &GuiViewPrivate::compileAndDestroy,
3763 case LFUN_MASTER_BUFFER_VIEW: {
3764 d.asyncBufferProcessing(argument,
3765 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3767 &GuiViewPrivate::previewAndDestroy,
3768 0, &Buffer::preview);
3771 case LFUN_EXPORT_CANCEL: {
3772 Systemcall::killscript();
3775 case LFUN_BUFFER_SWITCH: {
3776 string const file_name = to_utf8(cmd.argument());
3777 if (!FileName::isAbsolute(file_name)) {
3779 dr.setMessage(_("Absolute filename expected."));
3783 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3786 dr.setMessage(_("Document not loaded"));
3790 // Do we open or switch to the buffer in this view ?
3791 if (workArea(*buffer)
3792 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3797 // Look for the buffer in other views
3798 QList<int> const ids = guiApp->viewIds();
3800 for (; i != ids.size(); ++i) {
3801 GuiView & gv = guiApp->view(ids[i]);
3802 if (gv.workArea(*buffer)) {
3804 gv.activateWindow();
3806 gv.setBuffer(buffer);
3811 // If necessary, open a new window as a last resort
3812 if (i == ids.size()) {
3813 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3819 case LFUN_BUFFER_NEXT:
3820 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3823 case LFUN_BUFFER_MOVE_NEXT:
3824 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3827 case LFUN_BUFFER_PREVIOUS:
3828 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3831 case LFUN_BUFFER_MOVE_PREVIOUS:
3832 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3835 case LFUN_BUFFER_CHKTEX:
3836 LASSERT(doc_buffer, break);
3837 doc_buffer->runChktex();
3840 case LFUN_COMMAND_EXECUTE: {
3841 command_execute_ = true;
3842 minibuffer_focus_ = true;
3845 case LFUN_DROP_LAYOUTS_CHOICE:
3846 d.layout_->showPopup();
3849 case LFUN_MENU_OPEN:
3850 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3851 menu->exec(QCursor::pos());
3854 case LFUN_FILE_INSERT:
3855 insertLyXFile(cmd.argument());
3858 case LFUN_FILE_INSERT_PLAINTEXT:
3859 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3860 string const fname = to_utf8(cmd.argument());
3861 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3862 dr.setMessage(_("Absolute filename expected."));
3866 FileName filename(fname);
3867 if (fname.empty()) {
3868 FileDialog dlg(qt_("Select file to insert"));
3870 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3871 QStringList(qt_("All Files (*)")));
3873 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3874 dr.setMessage(_("Canceled."));
3878 filename.set(fromqstr(result.second));
3882 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3883 bv->dispatch(new_cmd, dr);
3888 case LFUN_BUFFER_RELOAD: {
3889 LASSERT(doc_buffer, break);
3892 bool drop = (cmd.argument()=="dump");
3895 if (!drop && !doc_buffer->isClean()) {
3896 docstring const file =
3897 makeDisplayPath(doc_buffer->absFileName(), 20);
3898 if (doc_buffer->notifiesExternalModification()) {
3899 docstring text = _("The current version will be lost. "
3900 "Are you sure you want to load the version on disk "
3901 "of the document %1$s?");
3902 ret = Alert::prompt(_("Reload saved document?"),
3903 bformat(text, file), 1, 1,
3904 _("&Reload"), _("&Cancel"));
3906 docstring text = _("Any changes will be lost. "
3907 "Are you sure you want to revert to the saved version "
3908 "of the document %1$s?");
3909 ret = Alert::prompt(_("Revert to saved document?"),
3910 bformat(text, file), 1, 1,
3911 _("&Revert"), _("&Cancel"));
3916 doc_buffer->markClean();
3917 reloadBuffer(*doc_buffer);
3918 dr.forceBufferUpdate();
3923 case LFUN_BUFFER_WRITE:
3924 LASSERT(doc_buffer, break);
3925 saveBuffer(*doc_buffer);
3928 case LFUN_BUFFER_WRITE_AS:
3929 LASSERT(doc_buffer, break);
3930 renameBuffer(*doc_buffer, cmd.argument());
3933 case LFUN_BUFFER_WRITE_ALL: {
3934 Buffer * first = theBufferList().first();
3937 message(_("Saving all documents..."));
3938 // We cannot use a for loop as the buffer list cycles.
3941 if (!b->isClean()) {
3943 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3945 b = theBufferList().next(b);
3946 } while (b != first);
3947 dr.setMessage(_("All documents saved."));
3951 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3952 LASSERT(doc_buffer, break);
3953 doc_buffer->clearExternalModification();
3956 case LFUN_BUFFER_CLOSE:
3960 case LFUN_BUFFER_CLOSE_ALL:
3964 case LFUN_DEVEL_MODE_TOGGLE:
3965 devel_mode_ = !devel_mode_;
3967 dr.setMessage(_("Developer mode is now enabled."));
3969 dr.setMessage(_("Developer mode is now disabled."));
3972 case LFUN_TOOLBAR_TOGGLE: {
3973 string const name = cmd.getArg(0);
3974 if (GuiToolbar * t = toolbar(name))
3979 case LFUN_TOOLBAR_MOVABLE: {
3980 string const name = cmd.getArg(0);
3982 // toggle (all) toolbars movablility
3983 toolbarsMovable_ = !toolbarsMovable_;
3984 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3985 GuiToolbar * tb = toolbar(ti.name);
3986 if (tb && tb->isMovable() != toolbarsMovable_)
3987 // toggle toolbar movablity if it does not fit lock
3988 // (all) toolbars positions state silent = true, since
3989 // status bar notifications are slow
3992 if (toolbarsMovable_)
3993 dr.setMessage(_("Toolbars unlocked."));
3995 dr.setMessage(_("Toolbars locked."));
3996 } else if (GuiToolbar * t = toolbar(name)) {
3997 // toggle current toolbar movablity
3999 // update lock (all) toolbars positions
4000 updateLockToolbars();
4005 case LFUN_ICON_SIZE: {
4006 QSize size = d.iconSize(cmd.argument());
4008 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4009 size.width(), size.height()));
4013 case LFUN_DIALOG_UPDATE: {
4014 string const name = to_utf8(cmd.argument());
4015 if (name == "prefs" || name == "document")
4016 updateDialog(name, string());
4017 else if (name == "paragraph")
4018 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4019 else if (currentBufferView()) {
4020 Inset * inset = currentBufferView()->editedInset(name);
4021 // Can only update a dialog connected to an existing inset
4023 // FIXME: get rid of this indirection; GuiView ask the inset
4024 // if he is kind enough to update itself...
4025 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4026 //FIXME: pass DispatchResult here?
4027 inset->dispatch(currentBufferView()->cursor(), fr);
4033 case LFUN_DIALOG_TOGGLE: {
4034 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4035 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4036 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4040 case LFUN_DIALOG_DISCONNECT_INSET:
4041 disconnectDialog(to_utf8(cmd.argument()));
4044 case LFUN_DIALOG_HIDE: {
4045 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4049 case LFUN_DIALOG_SHOW: {
4050 string const name = cmd.getArg(0);
4051 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4053 if (name == "character") {
4054 sdata = freefont2string();
4056 showDialog("character", sdata);
4057 } else if (name == "latexlog") {
4058 // gettatus checks that
4059 LATTEST(doc_buffer);
4060 Buffer::LogType type;
4061 string const logfile = doc_buffer->logName(&type);
4063 case Buffer::latexlog:
4066 case Buffer::buildlog:
4067 sdata = "literate ";
4070 sdata += Lexer::quoteString(logfile);
4071 showDialog("log", sdata);
4072 } else if (name == "vclog") {
4073 // getStatus checks that
4074 LATTEST(doc_buffer);
4075 string const sdata2 = "vc " +
4076 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4077 showDialog("log", sdata2);
4078 } else if (name == "symbols") {
4079 sdata = bv->cursor().getEncoding()->name();
4081 showDialog("symbols", sdata);
4083 } else if (name == "prefs" && isFullScreen()) {
4084 lfunUiToggle("fullscreen");
4085 showDialog("prefs", sdata);
4087 showDialog(name, sdata);
4092 dr.setMessage(cmd.argument());
4095 case LFUN_UI_TOGGLE: {
4096 string arg = cmd.getArg(0);
4097 if (!lfunUiToggle(arg)) {
4098 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4099 dr.setMessage(bformat(msg, from_utf8(arg)));
4101 // Make sure the keyboard focus stays in the work area.
4106 case LFUN_VIEW_SPLIT: {
4107 LASSERT(doc_buffer, break);
4108 string const orientation = cmd.getArg(0);
4109 d.splitter_->setOrientation(orientation == "vertical"
4110 ? Qt::Vertical : Qt::Horizontal);
4111 TabWorkArea * twa = addTabWorkArea();
4112 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4113 setCurrentWorkArea(wa);
4116 case LFUN_TAB_GROUP_CLOSE:
4117 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4118 closeTabWorkArea(twa);
4119 d.current_work_area_ = 0;
4120 twa = d.currentTabWorkArea();
4121 // Switch to the next GuiWorkArea in the found TabWorkArea.
4123 // Make sure the work area is up to date.
4124 setCurrentWorkArea(twa->currentWorkArea());
4126 setCurrentWorkArea(0);
4131 case LFUN_VIEW_CLOSE:
4132 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4133 closeWorkArea(twa->currentWorkArea());
4134 d.current_work_area_ = 0;
4135 twa = d.currentTabWorkArea();
4136 // Switch to the next GuiWorkArea in the found TabWorkArea.
4138 // Make sure the work area is up to date.
4139 setCurrentWorkArea(twa->currentWorkArea());
4141 setCurrentWorkArea(0);
4146 case LFUN_COMPLETION_INLINE:
4147 if (d.current_work_area_)
4148 d.current_work_area_->completer().showInline();
4151 case LFUN_COMPLETION_POPUP:
4152 if (d.current_work_area_)
4153 d.current_work_area_->completer().showPopup();
4158 if (d.current_work_area_)
4159 d.current_work_area_->completer().tab();
4162 case LFUN_COMPLETION_CANCEL:
4163 if (d.current_work_area_) {
4164 if (d.current_work_area_->completer().popupVisible())
4165 d.current_work_area_->completer().hidePopup();
4167 d.current_work_area_->completer().hideInline();
4171 case LFUN_COMPLETION_ACCEPT:
4172 if (d.current_work_area_)
4173 d.current_work_area_->completer().activate();
4176 case LFUN_BUFFER_ZOOM_IN:
4177 case LFUN_BUFFER_ZOOM_OUT:
4178 case LFUN_BUFFER_ZOOM: {
4179 if (cmd.argument().empty()) {
4180 if (cmd.action() == LFUN_BUFFER_ZOOM)
4182 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4187 if (cmd.action() == LFUN_BUFFER_ZOOM)
4188 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4189 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4190 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4192 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4195 // Actual zoom value: default zoom + fractional extra value
4196 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4197 if (zoom < static_cast<int>(zoom_min_))
4200 lyxrc.currentZoom = zoom;
4202 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4203 lyxrc.currentZoom, lyxrc.defaultZoom));
4205 // The global QPixmapCache is used in GuiPainter to cache text
4206 // painting so we must reset it.
4207 QPixmapCache::clear();
4208 guiApp->fontLoader().update();
4209 dr.screenUpdate(Update::Force | Update::FitCursor);
4213 case LFUN_VC_REGISTER:
4214 case LFUN_VC_RENAME:
4216 case LFUN_VC_CHECK_IN:
4217 case LFUN_VC_CHECK_OUT:
4218 case LFUN_VC_REPO_UPDATE:
4219 case LFUN_VC_LOCKING_TOGGLE:
4220 case LFUN_VC_REVERT:
4221 case LFUN_VC_UNDO_LAST:
4222 case LFUN_VC_COMMAND:
4223 case LFUN_VC_COMPARE:
4224 dispatchVC(cmd, dr);
4227 case LFUN_SERVER_GOTO_FILE_ROW:
4228 if(goToFileRow(to_utf8(cmd.argument())))
4229 dr.screenUpdate(Update::Force | Update::FitCursor);
4232 case LFUN_LYX_ACTIVATE:
4236 case LFUN_FORWARD_SEARCH: {
4237 // it seems safe to assume we have a document buffer, since
4238 // getStatus wants one.
4239 LATTEST(doc_buffer);
4240 Buffer const * doc_master = doc_buffer->masterBuffer();
4241 FileName const path(doc_master->temppath());
4242 string const texname = doc_master->isChild(doc_buffer)
4243 ? DocFileName(changeExtension(
4244 doc_buffer->absFileName(),
4245 "tex")).mangledFileName()
4246 : doc_buffer->latexName();
4247 string const fulltexname =
4248 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4249 string const mastername =
4250 removeExtension(doc_master->latexName());
4251 FileName const dviname(addName(path.absFileName(),
4252 addExtension(mastername, "dvi")));
4253 FileName const pdfname(addName(path.absFileName(),
4254 addExtension(mastername, "pdf")));
4255 bool const have_dvi = dviname.exists();
4256 bool const have_pdf = pdfname.exists();
4257 if (!have_dvi && !have_pdf) {
4258 dr.setMessage(_("Please, preview the document first."));
4261 string outname = dviname.onlyFileName();
4262 string command = lyxrc.forward_search_dvi;
4263 if (!have_dvi || (have_pdf &&
4264 pdfname.lastModified() > dviname.lastModified())) {
4265 outname = pdfname.onlyFileName();
4266 command = lyxrc.forward_search_pdf;
4269 DocIterator cur = bv->cursor();
4270 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4271 LYXERR(Debug::ACTION, "Forward search: row:" << row
4273 if (row == -1 || command.empty()) {
4274 dr.setMessage(_("Couldn't proceed."));
4277 string texrow = convert<string>(row);
4279 command = subst(command, "$$n", texrow);
4280 command = subst(command, "$$f", fulltexname);
4281 command = subst(command, "$$t", texname);
4282 command = subst(command, "$$o", outname);
4284 PathChanger p(path);
4286 one.startscript(Systemcall::DontWait, command);
4290 case LFUN_SPELLING_CONTINUOUSLY:
4291 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4292 dr.screenUpdate(Update::Force);
4296 // The LFUN must be for one of BufferView, Buffer or Cursor;
4298 dispatchToBufferView(cmd, dr);
4302 // Part of automatic menu appearance feature.
4303 if (isFullScreen()) {
4304 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4308 // Need to update bv because many LFUNs here might have destroyed it
4309 bv = currentBufferView();
4311 // Clear non-empty selections
4312 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4314 Cursor & cur = bv->cursor();
4315 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4316 cur.clearSelection();
4322 bool GuiView::lfunUiToggle(string const & ui_component)
4324 if (ui_component == "scrollbar") {
4325 // hide() is of no help
4326 if (d.current_work_area_->verticalScrollBarPolicy() ==
4327 Qt::ScrollBarAlwaysOff)
4329 d.current_work_area_->setVerticalScrollBarPolicy(
4330 Qt::ScrollBarAsNeeded);
4332 d.current_work_area_->setVerticalScrollBarPolicy(
4333 Qt::ScrollBarAlwaysOff);
4334 } else if (ui_component == "statusbar") {
4335 statusBar()->setVisible(!statusBar()->isVisible());
4336 } else if (ui_component == "menubar") {
4337 menuBar()->setVisible(!menuBar()->isVisible());
4339 if (ui_component == "frame") {
4341 getContentsMargins(&l, &t, &r, &b);
4342 //are the frames in default state?
4343 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4345 setContentsMargins(-2, -2, -2, -2);
4347 setContentsMargins(0, 0, 0, 0);
4350 if (ui_component == "fullscreen") {
4358 void GuiView::toggleFullScreen()
4360 if (isFullScreen()) {
4361 for (int i = 0; i != d.splitter_->count(); ++i)
4362 d.tabWorkArea(i)->setFullScreen(false);
4363 setContentsMargins(0, 0, 0, 0);
4364 setWindowState(windowState() ^ Qt::WindowFullScreen);
4367 statusBar()->show();
4370 hideDialogs("prefs", 0);
4371 for (int i = 0; i != d.splitter_->count(); ++i)
4372 d.tabWorkArea(i)->setFullScreen(true);
4373 setContentsMargins(-2, -2, -2, -2);
4375 setWindowState(windowState() ^ Qt::WindowFullScreen);
4376 if (lyxrc.full_screen_statusbar)
4377 statusBar()->hide();
4378 if (lyxrc.full_screen_menubar)
4380 if (lyxrc.full_screen_toolbars) {
4381 ToolbarMap::iterator end = d.toolbars_.end();
4382 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4387 // give dialogs like the TOC a chance to adapt
4392 Buffer const * GuiView::updateInset(Inset const * inset)
4397 Buffer const * inset_buffer = &(inset->buffer());
4399 for (int i = 0; i != d.splitter_->count(); ++i) {
4400 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4403 Buffer const * buffer = &(wa->bufferView().buffer());
4404 if (inset_buffer == buffer)
4405 wa->scheduleRedraw(true);
4407 return inset_buffer;
4411 void GuiView::restartCaret()
4413 /* When we move around, or type, it's nice to be able to see
4414 * the caret immediately after the keypress.
4416 if (d.current_work_area_)
4417 d.current_work_area_->startBlinkingCaret();
4419 // Take this occasion to update the other GUI elements.
4425 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4427 if (d.current_work_area_)
4428 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4433 // This list should be kept in sync with the list of insets in
4434 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4435 // dialog should have the same name as the inset.
4436 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4437 // docs in LyXAction.cpp.
4439 char const * const dialognames[] = {
4441 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4442 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4443 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4444 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4445 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4446 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4447 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4448 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4450 char const * const * const end_dialognames =
4451 dialognames + (sizeof(dialognames) / sizeof(char *));
4455 cmpCStr(char const * name) : name_(name) {}
4456 bool operator()(char const * other) {
4457 return strcmp(other, name_) == 0;
4464 bool isValidName(string const & name)
4466 return find_if(dialognames, end_dialognames,
4467 cmpCStr(name.c_str())) != end_dialognames;
4473 void GuiView::resetDialogs()
4475 // Make sure that no LFUN uses any GuiView.
4476 guiApp->setCurrentView(0);
4480 constructToolbars();
4481 guiApp->menus().fillMenuBar(menuBar(), this, false);
4482 d.layout_->updateContents(true);
4483 // Now update controls with current buffer.
4484 guiApp->setCurrentView(this);
4490 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4492 if (!isValidName(name))
4495 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4497 if (it != d.dialogs_.end()) {
4499 it->second->hideView();
4500 return it->second.get();
4503 Dialog * dialog = build(name);
4504 d.dialogs_[name].reset(dialog);
4505 if (lyxrc.allow_geometry_session)
4506 dialog->restoreSession();
4513 void GuiView::showDialog(string const & name, string const & sdata,
4516 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4520 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4526 const string name = fromqstr(qname);
4527 const string sdata = fromqstr(qdata);
4531 Dialog * dialog = findOrBuild(name, false);
4533 bool const visible = dialog->isVisibleView();
4534 dialog->showData(sdata);
4535 if (inset && currentBufferView())
4536 currentBufferView()->editInset(name, inset);
4537 // We only set the focus to the new dialog if it was not yet
4538 // visible in order not to change the existing previous behaviour
4540 // activateWindow is needed for floating dockviews
4541 dialog->asQWidget()->raise();
4542 dialog->asQWidget()->activateWindow();
4543 dialog->asQWidget()->setFocus();
4547 catch (ExceptionMessage const & ex) {
4555 bool GuiView::isDialogVisible(string const & name) const
4557 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4558 if (it == d.dialogs_.end())
4560 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4564 void GuiView::hideDialog(string const & name, Inset * inset)
4566 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4567 if (it == d.dialogs_.end())
4571 if (!currentBufferView())
4573 if (inset != currentBufferView()->editedInset(name))
4577 Dialog * const dialog = it->second.get();
4578 if (dialog->isVisibleView())
4580 if (currentBufferView())
4581 currentBufferView()->editInset(name, 0);
4585 void GuiView::disconnectDialog(string const & name)
4587 if (!isValidName(name))
4589 if (currentBufferView())
4590 currentBufferView()->editInset(name, 0);
4594 void GuiView::hideAll() const
4596 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4597 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4599 for(; it != end; ++it)
4600 it->second->hideView();
4604 void GuiView::updateDialogs()
4606 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4607 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4609 for(; it != end; ++it) {
4610 Dialog * dialog = it->second.get();
4612 if (dialog->needBufferOpen() && !documentBufferView())
4613 hideDialog(fromqstr(dialog->name()), 0);
4614 else if (dialog->isVisibleView())
4615 dialog->checkStatus();
4622 Dialog * createDialog(GuiView & lv, string const & name);
4624 // will be replaced by a proper factory...
4625 Dialog * createGuiAbout(GuiView & lv);
4626 Dialog * createGuiBibtex(GuiView & lv);
4627 Dialog * createGuiChanges(GuiView & lv);
4628 Dialog * createGuiCharacter(GuiView & lv);
4629 Dialog * createGuiCitation(GuiView & lv);
4630 Dialog * createGuiCompare(GuiView & lv);
4631 Dialog * createGuiCompareHistory(GuiView & lv);
4632 Dialog * createGuiDelimiter(GuiView & lv);
4633 Dialog * createGuiDocument(GuiView & lv);
4634 Dialog * createGuiErrorList(GuiView & lv);
4635 Dialog * createGuiExternal(GuiView & lv);
4636 Dialog * createGuiGraphics(GuiView & lv);
4637 Dialog * createGuiInclude(GuiView & lv);
4638 Dialog * createGuiIndex(GuiView & lv);
4639 Dialog * createGuiListings(GuiView & lv);
4640 Dialog * createGuiLog(GuiView & lv);
4641 Dialog * createGuiMathMatrix(GuiView & lv);
4642 Dialog * createGuiNote(GuiView & lv);
4643 Dialog * createGuiParagraph(GuiView & lv);
4644 Dialog * createGuiPhantom(GuiView & lv);
4645 Dialog * createGuiPreferences(GuiView & lv);
4646 Dialog * createGuiPrint(GuiView & lv);
4647 Dialog * createGuiPrintindex(GuiView & lv);
4648 Dialog * createGuiRef(GuiView & lv);
4649 Dialog * createGuiSearch(GuiView & lv);
4650 Dialog * createGuiSearchAdv(GuiView & lv);
4651 Dialog * createGuiSendTo(GuiView & lv);
4652 Dialog * createGuiShowFile(GuiView & lv);
4653 Dialog * createGuiSpellchecker(GuiView & lv);
4654 Dialog * createGuiSymbols(GuiView & lv);
4655 Dialog * createGuiTabularCreate(GuiView & lv);
4656 Dialog * createGuiTexInfo(GuiView & lv);
4657 Dialog * createGuiToc(GuiView & lv);
4658 Dialog * createGuiThesaurus(GuiView & lv);
4659 Dialog * createGuiViewSource(GuiView & lv);
4660 Dialog * createGuiWrap(GuiView & lv);
4661 Dialog * createGuiProgressView(GuiView & lv);
4665 Dialog * GuiView::build(string const & name)
4667 LASSERT(isValidName(name), return 0);
4669 Dialog * dialog = createDialog(*this, name);
4673 if (name == "aboutlyx")
4674 return createGuiAbout(*this);
4675 if (name == "bibtex")
4676 return createGuiBibtex(*this);
4677 if (name == "changes")
4678 return createGuiChanges(*this);
4679 if (name == "character")
4680 return createGuiCharacter(*this);
4681 if (name == "citation")
4682 return createGuiCitation(*this);
4683 if (name == "compare")
4684 return createGuiCompare(*this);
4685 if (name == "comparehistory")
4686 return createGuiCompareHistory(*this);
4687 if (name == "document")
4688 return createGuiDocument(*this);
4689 if (name == "errorlist")
4690 return createGuiErrorList(*this);
4691 if (name == "external")
4692 return createGuiExternal(*this);
4694 return createGuiShowFile(*this);
4695 if (name == "findreplace")
4696 return createGuiSearch(*this);
4697 if (name == "findreplaceadv")
4698 return createGuiSearchAdv(*this);
4699 if (name == "graphics")
4700 return createGuiGraphics(*this);
4701 if (name == "include")
4702 return createGuiInclude(*this);
4703 if (name == "index")
4704 return createGuiIndex(*this);
4705 if (name == "index_print")
4706 return createGuiPrintindex(*this);
4707 if (name == "listings")
4708 return createGuiListings(*this);
4710 return createGuiLog(*this);
4711 if (name == "mathdelimiter")
4712 return createGuiDelimiter(*this);
4713 if (name == "mathmatrix")
4714 return createGuiMathMatrix(*this);
4716 return createGuiNote(*this);
4717 if (name == "paragraph")
4718 return createGuiParagraph(*this);
4719 if (name == "phantom")
4720 return createGuiPhantom(*this);
4721 if (name == "prefs")
4722 return createGuiPreferences(*this);
4724 return createGuiRef(*this);
4725 if (name == "sendto")
4726 return createGuiSendTo(*this);
4727 if (name == "spellchecker")
4728 return createGuiSpellchecker(*this);
4729 if (name == "symbols")
4730 return createGuiSymbols(*this);
4731 if (name == "tabularcreate")
4732 return createGuiTabularCreate(*this);
4733 if (name == "texinfo")
4734 return createGuiTexInfo(*this);
4735 if (name == "thesaurus")
4736 return createGuiThesaurus(*this);
4738 return createGuiToc(*this);
4739 if (name == "view-source")
4740 return createGuiViewSource(*this);
4742 return createGuiWrap(*this);
4743 if (name == "progress")
4744 return createGuiProgressView(*this);
4750 SEMenu::SEMenu(QWidget * parent)
4752 QAction * action = addAction(qt_("Disable Shell Escape"));
4753 connect(action, SIGNAL(triggered()),
4754 parent, SLOT(disableShellEscape()));
4757 } // namespace frontend
4760 #include "moc_GuiView.cpp"