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 data = error_type;
1712 data = "from_master|" + error_type;
1713 showDialog("errorlist", data);
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 & data)
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(data);
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_EXPORT:
1864 && (doc_buffer->parent() != 0
1865 || doc_buffer->hasChildren())
1866 && !d.processing_thread_watcher_.isRunning()
1867 // this launches a dialog, which would be in the wrong Buffer
1868 && !(::lyx::operator==(cmd.argument(), "custom"));
1871 case LFUN_MASTER_BUFFER_UPDATE:
1872 case LFUN_MASTER_BUFFER_VIEW:
1874 && (doc_buffer->parent() != 0
1875 || doc_buffer->hasChildren())
1876 && !d.processing_thread_watcher_.isRunning();
1879 case LFUN_BUFFER_UPDATE:
1880 case LFUN_BUFFER_VIEW: {
1881 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1885 string format = to_utf8(cmd.argument());
1886 if (cmd.argument().empty())
1887 format = doc_buffer->params().getDefaultOutputFormat();
1888 enable = doc_buffer->params().isExportable(format, true);
1892 case LFUN_BUFFER_RELOAD:
1893 enable = doc_buffer && !doc_buffer->isUnnamed()
1894 && doc_buffer->fileName().exists()
1895 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1898 case LFUN_BUFFER_CHILD_OPEN:
1899 enable = doc_buffer != 0;
1902 case LFUN_BUFFER_WRITE:
1903 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1906 //FIXME: This LFUN should be moved to GuiApplication.
1907 case LFUN_BUFFER_WRITE_ALL: {
1908 // We enable the command only if there are some modified buffers
1909 Buffer * first = theBufferList().first();
1914 // We cannot use a for loop as the buffer list is a cycle.
1916 if (!b->isClean()) {
1920 b = theBufferList().next(b);
1921 } while (b != first);
1925 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1926 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1929 case LFUN_BUFFER_EXPORT: {
1930 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1934 return doc_buffer->getStatus(cmd, flag);
1938 case LFUN_BUFFER_EXPORT_AS:
1939 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1944 case LFUN_BUFFER_WRITE_AS:
1945 enable = doc_buffer != 0;
1948 case LFUN_BUFFER_CLOSE:
1949 case LFUN_VIEW_CLOSE:
1950 enable = doc_buffer != 0;
1953 case LFUN_BUFFER_CLOSE_ALL:
1954 enable = theBufferList().last() != theBufferList().first();
1957 case LFUN_BUFFER_CHKTEX: {
1958 // hide if we have no checktex command
1959 if (lyxrc.chktex_command.empty()) {
1960 flag.setUnknown(true);
1964 if (!doc_buffer || !doc_buffer->params().isLatex()
1965 || d.processing_thread_watcher_.isRunning()) {
1966 // grey out, don't hide
1974 case LFUN_VIEW_SPLIT:
1975 if (cmd.getArg(0) == "vertical")
1976 enable = doc_buffer && (d.splitter_->count() == 1 ||
1977 d.splitter_->orientation() == Qt::Vertical);
1979 enable = doc_buffer && (d.splitter_->count() == 1 ||
1980 d.splitter_->orientation() == Qt::Horizontal);
1983 case LFUN_TAB_GROUP_CLOSE:
1984 enable = d.tabWorkAreaCount() > 1;
1987 case LFUN_DEVEL_MODE_TOGGLE:
1988 flag.setOnOff(devel_mode_);
1991 case LFUN_TOOLBAR_TOGGLE: {
1992 string const name = cmd.getArg(0);
1993 if (GuiToolbar * t = toolbar(name))
1994 flag.setOnOff(t->isVisible());
1997 docstring const msg =
1998 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2004 case LFUN_TOOLBAR_MOVABLE: {
2005 string const name = cmd.getArg(0);
2006 // use negation since locked == !movable
2008 // toolbar name * locks all toolbars
2009 flag.setOnOff(!toolbarsMovable_);
2010 else if (GuiToolbar * t = toolbar(name))
2011 flag.setOnOff(!(t->isMovable()));
2014 docstring const msg =
2015 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2021 case LFUN_ICON_SIZE:
2022 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2025 case LFUN_DROP_LAYOUTS_CHOICE:
2029 case LFUN_UI_TOGGLE:
2030 flag.setOnOff(isFullScreen());
2033 case LFUN_DIALOG_DISCONNECT_INSET:
2036 case LFUN_DIALOG_HIDE:
2037 // FIXME: should we check if the dialog is shown?
2040 case LFUN_DIALOG_TOGGLE:
2041 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2044 case LFUN_DIALOG_SHOW: {
2045 string const name = cmd.getArg(0);
2047 enable = name == "aboutlyx"
2048 || name == "file" //FIXME: should be removed.
2050 || name == "texinfo"
2051 || name == "progress"
2052 || name == "compare";
2053 else if (name == "character" || name == "symbols"
2054 || name == "mathdelimiter" || name == "mathmatrix") {
2055 if (!buf || buf->isReadonly())
2058 Cursor const & cur = currentBufferView()->cursor();
2059 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2062 else if (name == "latexlog")
2063 enable = FileName(doc_buffer->logName()).isReadableFile();
2064 else if (name == "spellchecker")
2065 enable = theSpellChecker()
2066 && !doc_buffer->isReadonly()
2067 && !doc_buffer->text().empty();
2068 else if (name == "vclog")
2069 enable = doc_buffer->lyxvc().inUse();
2073 case LFUN_DIALOG_UPDATE: {
2074 string const name = cmd.getArg(0);
2076 enable = name == "prefs";
2080 case LFUN_COMMAND_EXECUTE:
2082 case LFUN_MENU_OPEN:
2083 // Nothing to check.
2086 case LFUN_COMPLETION_INLINE:
2087 if (!d.current_work_area_
2088 || !d.current_work_area_->completer().inlinePossible(
2089 currentBufferView()->cursor()))
2093 case LFUN_COMPLETION_POPUP:
2094 if (!d.current_work_area_
2095 || !d.current_work_area_->completer().popupPossible(
2096 currentBufferView()->cursor()))
2101 if (!d.current_work_area_
2102 || !d.current_work_area_->completer().inlinePossible(
2103 currentBufferView()->cursor()))
2107 case LFUN_COMPLETION_ACCEPT:
2108 if (!d.current_work_area_
2109 || (!d.current_work_area_->completer().popupVisible()
2110 && !d.current_work_area_->completer().inlineVisible()
2111 && !d.current_work_area_->completer().completionAvailable()))
2115 case LFUN_COMPLETION_CANCEL:
2116 if (!d.current_work_area_
2117 || (!d.current_work_area_->completer().popupVisible()
2118 && !d.current_work_area_->completer().inlineVisible()))
2122 case LFUN_BUFFER_ZOOM_OUT:
2123 case LFUN_BUFFER_ZOOM_IN: {
2124 // only diff between these two is that the default for ZOOM_OUT
2126 bool const neg_zoom =
2127 convert<int>(cmd.argument()) < 0 ||
2128 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2129 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2130 docstring const msg =
2131 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2135 enable = doc_buffer;
2139 case LFUN_BUFFER_ZOOM: {
2140 bool const less_than_min_zoom =
2141 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2142 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2143 docstring const msg =
2144 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2149 enable = doc_buffer;
2153 case LFUN_BUFFER_MOVE_NEXT:
2154 case LFUN_BUFFER_MOVE_PREVIOUS:
2155 // we do not cycle when moving
2156 case LFUN_BUFFER_NEXT:
2157 case LFUN_BUFFER_PREVIOUS:
2158 // because we cycle, it doesn't matter whether on first or last
2159 enable = (d.currentTabWorkArea()->count() > 1);
2161 case LFUN_BUFFER_SWITCH:
2162 // toggle on the current buffer, but do not toggle off
2163 // the other ones (is that a good idea?)
2165 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2166 flag.setOnOff(true);
2169 case LFUN_VC_REGISTER:
2170 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2172 case LFUN_VC_RENAME:
2173 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2176 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2178 case LFUN_VC_CHECK_IN:
2179 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2181 case LFUN_VC_CHECK_OUT:
2182 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2184 case LFUN_VC_LOCKING_TOGGLE:
2185 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2186 && doc_buffer->lyxvc().lockingToggleEnabled();
2187 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2189 case LFUN_VC_REVERT:
2190 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2191 && !doc_buffer->hasReadonlyFlag();
2193 case LFUN_VC_UNDO_LAST:
2194 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2196 case LFUN_VC_REPO_UPDATE:
2197 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2199 case LFUN_VC_COMMAND: {
2200 if (cmd.argument().empty())
2202 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2206 case LFUN_VC_COMPARE:
2207 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2210 case LFUN_SERVER_GOTO_FILE_ROW:
2211 case LFUN_LYX_ACTIVATE:
2213 case LFUN_FORWARD_SEARCH:
2214 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2217 case LFUN_FILE_INSERT_PLAINTEXT:
2218 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2219 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2222 case LFUN_SPELLING_CONTINUOUSLY:
2223 flag.setOnOff(lyxrc.spellcheck_continuously);
2231 flag.setEnabled(false);
2237 static FileName selectTemplateFile()
2239 FileDialog dlg(qt_("Select template file"));
2240 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2241 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2243 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2244 QStringList(qt_("LyX Documents (*.lyx)")));
2246 if (result.first == FileDialog::Later)
2248 if (result.second.isEmpty())
2250 return FileName(fromqstr(result.second));
2254 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2258 Buffer * newBuffer = 0;
2260 newBuffer = checkAndLoadLyXFile(filename);
2261 } catch (ExceptionMessage const & e) {
2268 message(_("Document not loaded."));
2272 setBuffer(newBuffer);
2273 newBuffer->errors("Parse");
2276 theSession().lastFiles().add(filename);
2277 theSession().writeFile();
2284 void GuiView::openDocument(string const & fname)
2286 string initpath = lyxrc.document_path;
2288 if (documentBufferView()) {
2289 string const trypath = documentBufferView()->buffer().filePath();
2290 // If directory is writeable, use this as default.
2291 if (FileName(trypath).isDirWritable())
2297 if (fname.empty()) {
2298 FileDialog dlg(qt_("Select document to open"));
2299 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2300 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2302 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2303 FileDialog::Result result =
2304 dlg.open(toqstr(initpath), filter);
2306 if (result.first == FileDialog::Later)
2309 filename = fromqstr(result.second);
2311 // check selected filename
2312 if (filename.empty()) {
2313 message(_("Canceled."));
2319 // get absolute path of file and add ".lyx" to the filename if
2321 FileName const fullname =
2322 fileSearch(string(), filename, "lyx", support::may_not_exist);
2323 if (!fullname.empty())
2324 filename = fullname.absFileName();
2326 if (!fullname.onlyPath().isDirectory()) {
2327 Alert::warning(_("Invalid filename"),
2328 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2329 from_utf8(fullname.absFileName())));
2333 // if the file doesn't exist and isn't already open (bug 6645),
2334 // let the user create one
2335 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2336 !LyXVC::file_not_found_hook(fullname)) {
2337 // the user specifically chose this name. Believe him.
2338 Buffer * const b = newFile(filename, string(), true);
2344 docstring const disp_fn = makeDisplayPath(filename);
2345 message(bformat(_("Opening document %1$s..."), disp_fn));
2348 Buffer * buf = loadDocument(fullname);
2350 str2 = bformat(_("Document %1$s opened."), disp_fn);
2351 if (buf->lyxvc().inUse())
2352 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2353 " " + _("Version control detected.");
2355 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2360 // FIXME: clean that
2361 static bool import(GuiView * lv, FileName const & filename,
2362 string const & format, ErrorList & errorList)
2364 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2366 string loader_format;
2367 vector<string> loaders = theConverters().loaders();
2368 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2369 vector<string>::const_iterator it = loaders.begin();
2370 vector<string>::const_iterator en = loaders.end();
2371 for (; it != en; ++it) {
2372 if (!theConverters().isReachable(format, *it))
2375 string const tofile =
2376 support::changeExtension(filename.absFileName(),
2377 theFormats().extension(*it));
2378 if (!theConverters().convert(0, filename, FileName(tofile),
2379 filename, format, *it, errorList))
2381 loader_format = *it;
2384 if (loader_format.empty()) {
2385 frontend::Alert::error(_("Couldn't import file"),
2386 bformat(_("No information for importing the format %1$s."),
2387 theFormats().prettyName(format)));
2391 loader_format = format;
2393 if (loader_format == "lyx") {
2394 Buffer * buf = lv->loadDocument(lyxfile);
2398 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2402 bool as_paragraphs = loader_format == "textparagraph";
2403 string filename2 = (loader_format == format) ? filename.absFileName()
2404 : support::changeExtension(filename.absFileName(),
2405 theFormats().extension(loader_format));
2406 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2408 guiApp->setCurrentView(lv);
2409 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2416 void GuiView::importDocument(string const & argument)
2419 string filename = split(argument, format, ' ');
2421 LYXERR(Debug::INFO, format << " file: " << filename);
2423 // need user interaction
2424 if (filename.empty()) {
2425 string initpath = lyxrc.document_path;
2426 if (documentBufferView()) {
2427 string const trypath = documentBufferView()->buffer().filePath();
2428 // If directory is writeable, use this as default.
2429 if (FileName(trypath).isDirWritable())
2433 docstring const text = bformat(_("Select %1$s file to import"),
2434 theFormats().prettyName(format));
2436 FileDialog dlg(toqstr(text));
2437 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2438 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2440 docstring filter = theFormats().prettyName(format);
2443 filter += from_utf8(theFormats().extensions(format));
2446 FileDialog::Result result =
2447 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2449 if (result.first == FileDialog::Later)
2452 filename = fromqstr(result.second);
2454 // check selected filename
2455 if (filename.empty())
2456 message(_("Canceled."));
2459 if (filename.empty())
2462 // get absolute path of file
2463 FileName const fullname(support::makeAbsPath(filename));
2465 // Can happen if the user entered a path into the dialog
2467 if (fullname.onlyFileName().empty()) {
2468 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2469 "Aborting import."),
2470 from_utf8(fullname.absFileName()));
2471 frontend::Alert::error(_("File name error"), msg);
2472 message(_("Canceled."));
2477 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2479 // Check if the document already is open
2480 Buffer * buf = theBufferList().getBuffer(lyxfile);
2483 if (!closeBuffer()) {
2484 message(_("Canceled."));
2489 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2491 // if the file exists already, and we didn't do
2492 // -i lyx thefile.lyx, warn
2493 if (lyxfile.exists() && fullname != lyxfile) {
2495 docstring text = bformat(_("The document %1$s already exists.\n\n"
2496 "Do you want to overwrite that document?"), displaypath);
2497 int const ret = Alert::prompt(_("Overwrite document?"),
2498 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2501 message(_("Canceled."));
2506 message(bformat(_("Importing %1$s..."), displaypath));
2507 ErrorList errorList;
2508 if (import(this, fullname, format, errorList))
2509 message(_("imported."));
2511 message(_("file not imported!"));
2513 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2517 void GuiView::newDocument(string const & filename, bool from_template)
2519 FileName initpath(lyxrc.document_path);
2520 if (documentBufferView()) {
2521 FileName const trypath(documentBufferView()->buffer().filePath());
2522 // If directory is writeable, use this as default.
2523 if (trypath.isDirWritable())
2527 string templatefile;
2528 if (from_template) {
2529 templatefile = selectTemplateFile().absFileName();
2530 if (templatefile.empty())
2535 if (filename.empty())
2536 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2538 b = newFile(filename, templatefile, true);
2543 // If no new document could be created, it is unsure
2544 // whether there is a valid BufferView.
2545 if (currentBufferView())
2546 // Ensure the cursor is correctly positioned on screen.
2547 currentBufferView()->showCursor();
2551 void GuiView::insertLyXFile(docstring const & fname)
2553 BufferView * bv = documentBufferView();
2558 FileName filename(to_utf8(fname));
2559 if (filename.empty()) {
2560 // Launch a file browser
2562 string initpath = lyxrc.document_path;
2563 string const trypath = bv->buffer().filePath();
2564 // If directory is writeable, use this as default.
2565 if (FileName(trypath).isDirWritable())
2569 FileDialog dlg(qt_("Select LyX document to insert"));
2570 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2571 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2573 FileDialog::Result result = dlg.open(toqstr(initpath),
2574 QStringList(qt_("LyX Documents (*.lyx)")));
2576 if (result.first == FileDialog::Later)
2580 filename.set(fromqstr(result.second));
2582 // check selected filename
2583 if (filename.empty()) {
2584 // emit message signal.
2585 message(_("Canceled."));
2590 bv->insertLyXFile(filename);
2591 bv->buffer().errors("Parse");
2595 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2597 FileName fname = b.fileName();
2598 FileName const oldname = fname;
2600 if (!newname.empty()) {
2602 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2604 // Switch to this Buffer.
2607 // No argument? Ask user through dialog.
2609 FileDialog dlg(qt_("Choose a filename to save document as"));
2610 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2611 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2613 if (!isLyXFileName(fname.absFileName()))
2614 fname.changeExtension(".lyx");
2616 FileDialog::Result result =
2617 dlg.save(toqstr(fname.onlyPath().absFileName()),
2618 QStringList(qt_("LyX Documents (*.lyx)")),
2619 toqstr(fname.onlyFileName()));
2621 if (result.first == FileDialog::Later)
2624 fname.set(fromqstr(result.second));
2629 if (!isLyXFileName(fname.absFileName()))
2630 fname.changeExtension(".lyx");
2633 // fname is now the new Buffer location.
2635 // if there is already a Buffer open with this name, we do not want
2636 // to have another one. (the second test makes sure we're not just
2637 // trying to overwrite ourselves, which is fine.)
2638 if (theBufferList().exists(fname) && fname != oldname
2639 && theBufferList().getBuffer(fname) != &b) {
2640 docstring const text =
2641 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2642 "Please close it before attempting to overwrite it.\n"
2643 "Do you want to choose a new filename?"),
2644 from_utf8(fname.absFileName()));
2645 int const ret = Alert::prompt(_("Chosen File Already Open"),
2646 text, 0, 1, _("&Rename"), _("&Cancel"));
2648 case 0: return renameBuffer(b, docstring(), kind);
2649 case 1: return false;
2654 bool const existsLocal = fname.exists();
2655 bool const existsInVC = LyXVC::fileInVC(fname);
2656 if (existsLocal || existsInVC) {
2657 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2658 if (kind != LV_WRITE_AS && existsInVC) {
2659 // renaming to a name that is already in VC
2661 docstring text = bformat(_("The document %1$s "
2662 "is already registered.\n\n"
2663 "Do you want to choose a new name?"),
2665 docstring const title = (kind == LV_VC_RENAME) ?
2666 _("Rename document?") : _("Copy document?");
2667 docstring const button = (kind == LV_VC_RENAME) ?
2668 _("&Rename") : _("&Copy");
2669 int const ret = Alert::prompt(title, text, 0, 1,
2670 button, _("&Cancel"));
2672 case 0: return renameBuffer(b, docstring(), kind);
2673 case 1: return false;
2678 docstring text = bformat(_("The document %1$s "
2679 "already exists.\n\n"
2680 "Do you want to overwrite that document?"),
2682 int const ret = Alert::prompt(_("Overwrite document?"),
2683 text, 0, 2, _("&Overwrite"),
2684 _("&Rename"), _("&Cancel"));
2687 case 1: return renameBuffer(b, docstring(), kind);
2688 case 2: return false;
2694 case LV_VC_RENAME: {
2695 string msg = b.lyxvc().rename(fname);
2698 message(from_utf8(msg));
2702 string msg = b.lyxvc().copy(fname);
2705 message(from_utf8(msg));
2711 // LyXVC created the file already in case of LV_VC_RENAME or
2712 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2713 // relative paths of included stuff right if we moved e.g. from
2714 // /a/b.lyx to /a/c/b.lyx.
2716 bool const saved = saveBuffer(b, fname);
2723 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2725 FileName fname = b.fileName();
2727 FileDialog dlg(qt_("Choose a filename to export the document as"));
2728 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2731 QString const anyformat = qt_("Guess from extension (*.*)");
2734 vector<Format const *> export_formats;
2735 for (Format const & f : theFormats())
2736 if (f.documentFormat())
2737 export_formats.push_back(&f);
2738 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2739 map<QString, string> fmap;
2742 for (Format const * f : export_formats) {
2743 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2744 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2746 from_ascii(f->extension())));
2747 types << loc_filter;
2748 fmap[loc_filter] = f->name();
2749 if (from_ascii(f->name()) == iformat) {
2750 filter = loc_filter;
2751 ext = f->extension();
2754 string ofname = fname.onlyFileName();
2756 ofname = support::changeExtension(ofname, ext);
2757 FileDialog::Result result =
2758 dlg.save(toqstr(fname.onlyPath().absFileName()),
2762 if (result.first != FileDialog::Chosen)
2766 fname.set(fromqstr(result.second));
2767 if (filter == anyformat)
2768 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2770 fmt_name = fmap[filter];
2771 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2772 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2774 if (fmt_name.empty() || fname.empty())
2777 // fname is now the new Buffer location.
2778 if (FileName(fname).exists()) {
2779 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2780 docstring text = bformat(_("The document %1$s already "
2781 "exists.\n\nDo you want to "
2782 "overwrite that document?"),
2784 int const ret = Alert::prompt(_("Overwrite document?"),
2785 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2788 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2789 case 2: return false;
2793 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2796 return dr.dispatched();
2800 bool GuiView::saveBuffer(Buffer & b)
2802 return saveBuffer(b, FileName());
2806 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2808 if (workArea(b) && workArea(b)->inDialogMode())
2811 if (fn.empty() && b.isUnnamed())
2812 return renameBuffer(b, docstring());
2814 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2816 theSession().lastFiles().add(b.fileName());
2817 theSession().writeFile();
2821 // Switch to this Buffer.
2824 // FIXME: we don't tell the user *WHY* the save failed !!
2825 docstring const file = makeDisplayPath(b.absFileName(), 30);
2826 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2827 "Do you want to rename the document and "
2828 "try again?"), file);
2829 int const ret = Alert::prompt(_("Rename and save?"),
2830 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2833 if (!renameBuffer(b, docstring()))
2842 return saveBuffer(b, fn);
2846 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2848 return closeWorkArea(wa, false);
2852 // We only want to close the buffer if it is not visible in other workareas
2853 // of the same view, nor in other views, and if this is not a child
2854 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2856 Buffer & buf = wa->bufferView().buffer();
2858 bool last_wa = d.countWorkAreasOf(buf) == 1
2859 && !inOtherView(buf) && !buf.parent();
2861 bool close_buffer = last_wa;
2864 if (lyxrc.close_buffer_with_last_view == "yes")
2866 else if (lyxrc.close_buffer_with_last_view == "no")
2867 close_buffer = false;
2870 if (buf.isUnnamed())
2871 file = from_utf8(buf.fileName().onlyFileName());
2873 file = buf.fileName().displayName(30);
2874 docstring const text = bformat(
2875 _("Last view on document %1$s is being closed.\n"
2876 "Would you like to close or hide the document?\n"
2878 "Hidden documents can be displayed back through\n"
2879 "the menu: View->Hidden->...\n"
2881 "To remove this question, set your preference in:\n"
2882 " Tools->Preferences->Look&Feel->UserInterface\n"
2884 int ret = Alert::prompt(_("Close or hide document?"),
2885 text, 0, 1, _("&Close"), _("&Hide"));
2886 close_buffer = (ret == 0);
2890 return closeWorkArea(wa, close_buffer);
2894 bool GuiView::closeBuffer()
2896 GuiWorkArea * wa = currentMainWorkArea();
2897 // coverity complained about this
2898 // it seems unnecessary, but perhaps is worth the check
2899 LASSERT(wa, return false);
2901 setCurrentWorkArea(wa);
2902 Buffer & buf = wa->bufferView().buffer();
2903 return closeWorkArea(wa, !buf.parent());
2907 void GuiView::writeSession() const {
2908 GuiWorkArea const * active_wa = currentMainWorkArea();
2909 for (int i = 0; i < d.splitter_->count(); ++i) {
2910 TabWorkArea * twa = d.tabWorkArea(i);
2911 for (int j = 0; j < twa->count(); ++j) {
2912 GuiWorkArea * wa = twa->workArea(j);
2913 Buffer & buf = wa->bufferView().buffer();
2914 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2920 bool GuiView::closeBufferAll()
2922 // Close the workareas in all other views
2923 QList<int> const ids = guiApp->viewIds();
2924 for (int i = 0; i != ids.size(); ++i) {
2925 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2929 // Close our own workareas
2930 if (!closeWorkAreaAll())
2933 // Now close the hidden buffers. We prevent hidden buffers from being
2934 // dirty, so we can just close them.
2935 theBufferList().closeAll();
2940 bool GuiView::closeWorkAreaAll()
2942 setCurrentWorkArea(currentMainWorkArea());
2944 // We might be in a situation that there is still a tabWorkArea, but
2945 // there are no tabs anymore. This can happen when we get here after a
2946 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2947 // many TabWorkArea's have no documents anymore.
2950 // We have to call count() each time, because it can happen that
2951 // more than one splitter will disappear in one iteration (bug 5998).
2952 while (d.splitter_->count() > empty_twa) {
2953 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2955 if (twa->count() == 0)
2958 setCurrentWorkArea(twa->currentWorkArea());
2959 if (!closeTabWorkArea(twa))
2967 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2972 Buffer & buf = wa->bufferView().buffer();
2974 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2975 Alert::warning(_("Close document"),
2976 _("Document could not be closed because it is being processed by LyX."));
2981 return closeBuffer(buf);
2983 if (!inMultiTabs(wa))
2984 if (!saveBufferIfNeeded(buf, true))
2992 bool GuiView::closeBuffer(Buffer & buf)
2994 // If we are in a close_event all children will be closed in some time,
2995 // so no need to do it here. This will ensure that the children end up
2996 // in the session file in the correct order. If we close the master
2997 // buffer, we can close or release the child buffers here too.
2998 bool success = true;
3000 ListOfBuffers clist = buf.getChildren();
3001 ListOfBuffers::const_iterator it = clist.begin();
3002 ListOfBuffers::const_iterator const bend = clist.end();
3003 for (; it != bend; ++it) {
3004 Buffer * child_buf = *it;
3005 if (theBufferList().isOthersChild(&buf, child_buf)) {
3006 child_buf->setParent(0);
3010 // FIXME: should we look in other tabworkareas?
3011 // ANSWER: I don't think so. I've tested, and if the child is
3012 // open in some other window, it closes without a problem.
3013 GuiWorkArea * child_wa = workArea(*child_buf);
3015 success = closeWorkArea(child_wa, true);
3019 // In this case the child buffer is open but hidden.
3020 // It therefore should not (MUST NOT) be dirty!
3021 LATTEST(child_buf->isClean());
3022 theBufferList().release(child_buf);
3027 // goto bookmark to update bookmark pit.
3028 // FIXME: we should update only the bookmarks related to this buffer!
3029 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3030 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3031 guiApp->gotoBookmark(i+1, false, false);
3033 if (saveBufferIfNeeded(buf, false)) {
3034 buf.removeAutosaveFile();
3035 theBufferList().release(&buf);
3039 // open all children again to avoid a crash because of dangling
3040 // pointers (bug 6603)
3046 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3048 while (twa == d.currentTabWorkArea()) {
3049 twa->setCurrentIndex(twa->count() - 1);
3051 GuiWorkArea * wa = twa->currentWorkArea();
3052 Buffer & b = wa->bufferView().buffer();
3054 // We only want to close the buffer if the same buffer is not visible
3055 // in another view, and if this is not a child and if we are closing
3056 // a view (not a tabgroup).
3057 bool const close_buffer =
3058 !inOtherView(b) && !b.parent() && closing_;
3060 if (!closeWorkArea(wa, close_buffer))
3067 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3069 if (buf.isClean() || buf.paragraphs().empty())
3072 // Switch to this Buffer.
3078 if (buf.isUnnamed()) {
3079 file = from_utf8(buf.fileName().onlyFileName());
3082 FileName filename = buf.fileName();
3084 file = filename.displayName(30);
3085 exists = filename.exists();
3088 // Bring this window to top before asking questions.
3093 if (hiding && buf.isUnnamed()) {
3094 docstring const text = bformat(_("The document %1$s has not been "
3095 "saved yet.\n\nDo you want to save "
3096 "the document?"), file);
3097 ret = Alert::prompt(_("Save new document?"),
3098 text, 0, 1, _("&Save"), _("&Cancel"));
3102 docstring const text = exists ?
3103 bformat(_("The document %1$s has unsaved changes."
3104 "\n\nDo you want to save the document or "
3105 "discard the changes?"), file) :
3106 bformat(_("The document %1$s has not been saved yet."
3107 "\n\nDo you want to save the document or "
3108 "discard it entirely?"), file);
3109 docstring const title = exists ?
3110 _("Save changed document?") : _("Save document?");
3111 ret = Alert::prompt(title, text, 0, 2,
3112 _("&Save"), _("&Discard"), _("&Cancel"));
3117 if (!saveBuffer(buf))
3121 // If we crash after this we could have no autosave file
3122 // but I guess this is really improbable (Jug).
3123 // Sometimes improbable things happen:
3124 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3125 // buf.removeAutosaveFile();
3127 // revert all changes
3138 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3140 Buffer & buf = wa->bufferView().buffer();
3142 for (int i = 0; i != d.splitter_->count(); ++i) {
3143 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3144 if (wa_ && wa_ != wa)
3147 return inOtherView(buf);
3151 bool GuiView::inOtherView(Buffer & buf)
3153 QList<int> const ids = guiApp->viewIds();
3155 for (int i = 0; i != ids.size(); ++i) {
3159 if (guiApp->view(ids[i]).workArea(buf))
3166 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3168 if (!documentBufferView())
3171 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3172 Buffer * const curbuf = &documentBufferView()->buffer();
3173 int nwa = twa->count();
3174 for (int i = 0; i < nwa; ++i) {
3175 if (&workArea(i)->bufferView().buffer() == curbuf) {
3177 if (np == NEXTBUFFER)
3178 next_index = (i == nwa - 1 ? 0 : i + 1);
3180 next_index = (i == 0 ? nwa - 1 : i - 1);
3182 twa->moveTab(i, next_index);
3184 setBuffer(&workArea(next_index)->bufferView().buffer());
3192 /// make sure the document is saved
3193 static bool ensureBufferClean(Buffer * buffer)
3195 LASSERT(buffer, return false);
3196 if (buffer->isClean() && !buffer->isUnnamed())
3199 docstring const file = buffer->fileName().displayName(30);
3202 if (!buffer->isUnnamed()) {
3203 text = bformat(_("The document %1$s has unsaved "
3204 "changes.\n\nDo you want to save "
3205 "the document?"), file);
3206 title = _("Save changed document?");
3209 text = bformat(_("The document %1$s has not been "
3210 "saved yet.\n\nDo you want to save "
3211 "the document?"), file);
3212 title = _("Save new document?");
3214 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3217 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3219 return buffer->isClean() && !buffer->isUnnamed();
3223 bool GuiView::reloadBuffer(Buffer & buf)
3225 currentBufferView()->cursor().reset();
3226 Buffer::ReadStatus status = buf.reload();
3227 return status == Buffer::ReadSuccess;
3231 void GuiView::checkExternallyModifiedBuffers()
3233 BufferList::iterator bit = theBufferList().begin();
3234 BufferList::iterator const bend = theBufferList().end();
3235 for (; bit != bend; ++bit) {
3236 Buffer * buf = *bit;
3237 if (buf->fileName().exists() && buf->isChecksumModified()) {
3238 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3239 " Reload now? Any local changes will be lost."),
3240 from_utf8(buf->absFileName()));
3241 int const ret = Alert::prompt(_("Reload externally changed document?"),
3242 text, 0, 1, _("&Reload"), _("&Cancel"));
3250 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3252 Buffer * buffer = documentBufferView()
3253 ? &(documentBufferView()->buffer()) : 0;
3255 switch (cmd.action()) {
3256 case LFUN_VC_REGISTER:
3257 if (!buffer || !ensureBufferClean(buffer))
3259 if (!buffer->lyxvc().inUse()) {
3260 if (buffer->lyxvc().registrer()) {
3261 reloadBuffer(*buffer);
3262 dr.clearMessageUpdate();
3267 case LFUN_VC_RENAME:
3268 case LFUN_VC_COPY: {
3269 if (!buffer || !ensureBufferClean(buffer))
3271 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3272 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3273 // Some changes are not yet committed.
3274 // We test here and not in getStatus(), since
3275 // this test is expensive.
3277 LyXVC::CommandResult ret =
3278 buffer->lyxvc().checkIn(log);
3280 if (ret == LyXVC::ErrorCommand ||
3281 ret == LyXVC::VCSuccess)
3282 reloadBuffer(*buffer);
3283 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3284 frontend::Alert::error(
3285 _("Revision control error."),
3286 _("Document could not be checked in."));
3290 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3291 LV_VC_RENAME : LV_VC_COPY;
3292 renameBuffer(*buffer, cmd.argument(), kind);
3297 case LFUN_VC_CHECK_IN:
3298 if (!buffer || !ensureBufferClean(buffer))
3300 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3302 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3304 // Only skip reloading if the checkin was cancelled or
3305 // an error occurred before the real checkin VCS command
3306 // was executed, since the VCS might have changed the
3307 // file even if it could not checkin successfully.
3308 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3309 reloadBuffer(*buffer);
3313 case LFUN_VC_CHECK_OUT:
3314 if (!buffer || !ensureBufferClean(buffer))
3316 if (buffer->lyxvc().inUse()) {
3317 dr.setMessage(buffer->lyxvc().checkOut());
3318 reloadBuffer(*buffer);
3322 case LFUN_VC_LOCKING_TOGGLE:
3323 LASSERT(buffer, return);
3324 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3326 if (buffer->lyxvc().inUse()) {
3327 string res = buffer->lyxvc().lockingToggle();
3329 frontend::Alert::error(_("Revision control error."),
3330 _("Error when setting the locking property."));
3333 reloadBuffer(*buffer);
3338 case LFUN_VC_REVERT:
3339 LASSERT(buffer, return);
3340 if (buffer->lyxvc().revert()) {
3341 reloadBuffer(*buffer);
3342 dr.clearMessageUpdate();
3346 case LFUN_VC_UNDO_LAST:
3347 LASSERT(buffer, return);
3348 buffer->lyxvc().undoLast();
3349 reloadBuffer(*buffer);
3350 dr.clearMessageUpdate();
3353 case LFUN_VC_REPO_UPDATE:
3354 LASSERT(buffer, return);
3355 if (ensureBufferClean(buffer)) {
3356 dr.setMessage(buffer->lyxvc().repoUpdate());
3357 checkExternallyModifiedBuffers();
3361 case LFUN_VC_COMMAND: {
3362 string flag = cmd.getArg(0);
3363 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3366 if (contains(flag, 'M')) {
3367 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3370 string path = cmd.getArg(1);
3371 if (contains(path, "$$p") && buffer)
3372 path = subst(path, "$$p", buffer->filePath());
3373 LYXERR(Debug::LYXVC, "Directory: " << path);
3375 if (!pp.isReadableDirectory()) {
3376 lyxerr << _("Directory is not accessible.") << endl;
3379 support::PathChanger p(pp);
3381 string command = cmd.getArg(2);
3382 if (command.empty())
3385 command = subst(command, "$$i", buffer->absFileName());
3386 command = subst(command, "$$p", buffer->filePath());
3388 command = subst(command, "$$m", to_utf8(message));
3389 LYXERR(Debug::LYXVC, "Command: " << command);
3391 one.startscript(Systemcall::Wait, command);
3395 if (contains(flag, 'I'))
3396 buffer->markDirty();
3397 if (contains(flag, 'R'))
3398 reloadBuffer(*buffer);
3403 case LFUN_VC_COMPARE: {
3404 if (cmd.argument().empty()) {
3405 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3409 string rev1 = cmd.getArg(0);
3414 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3417 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3418 f2 = buffer->absFileName();
3420 string rev2 = cmd.getArg(1);
3424 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3428 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3429 f1 << "\n" << f2 << "\n" );
3430 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3431 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3441 void GuiView::openChildDocument(string const & fname)
3443 LASSERT(documentBufferView(), return);
3444 Buffer & buffer = documentBufferView()->buffer();
3445 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3446 documentBufferView()->saveBookmark(false);
3448 if (theBufferList().exists(filename)) {
3449 child = theBufferList().getBuffer(filename);
3452 message(bformat(_("Opening child document %1$s..."),
3453 makeDisplayPath(filename.absFileName())));
3454 child = loadDocument(filename, false);
3456 // Set the parent name of the child document.
3457 // This makes insertion of citations and references in the child work,
3458 // when the target is in the parent or another child document.
3460 child->setParent(&buffer);
3464 bool GuiView::goToFileRow(string const & argument)
3468 size_t i = argument.find_last_of(' ');
3469 if (i != string::npos) {
3470 file_name = os::internal_path(trim(argument.substr(0, i)));
3471 istringstream is(argument.substr(i + 1));
3476 if (i == string::npos) {
3477 LYXERR0("Wrong argument: " << argument);
3481 string const abstmp = package().temp_dir().absFileName();
3482 string const realtmp = package().temp_dir().realPath();
3483 // We have to use os::path_prefix_is() here, instead of
3484 // simply prefixIs(), because the file name comes from
3485 // an external application and may need case adjustment.
3486 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3487 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3488 // Needed by inverse dvi search. If it is a file
3489 // in tmpdir, call the apropriated function.
3490 // If tmpdir is a symlink, we may have the real
3491 // path passed back, so we correct for that.
3492 if (!prefixIs(file_name, abstmp))
3493 file_name = subst(file_name, realtmp, abstmp);
3494 buf = theBufferList().getBufferFromTmp(file_name);
3496 // Must replace extension of the file to be .lyx
3497 // and get full path
3498 FileName const s = fileSearch(string(),
3499 support::changeExtension(file_name, ".lyx"), "lyx");
3500 // Either change buffer or load the file
3501 if (theBufferList().exists(s))
3502 buf = theBufferList().getBuffer(s);
3503 else if (s.exists()) {
3504 buf = loadDocument(s);
3509 _("File does not exist: %1$s"),
3510 makeDisplayPath(file_name)));
3516 _("No buffer for file: %1$s."),
3517 makeDisplayPath(file_name))
3522 bool success = documentBufferView()->setCursorFromRow(row);
3524 LYXERR(Debug::LATEX,
3525 "setCursorFromRow: invalid position for row " << row);
3526 frontend::Alert::error(_("Inverse Search Failed"),
3527 _("Invalid position requested by inverse search.\n"
3528 "You may need to update the viewed document."));
3534 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3536 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3537 menu->exec(QCursor::pos());
3542 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3544 Buffer::ExportStatus const status = func(format);
3546 // the cloning operation will have produced a clone of the entire set of
3547 // documents, starting from the master. so we must delete those.
3548 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3550 busyBuffers.remove(orig);
3555 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3557 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3558 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3562 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3564 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3565 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3569 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3571 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3572 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3576 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3577 string const & argument,
3578 Buffer const * used_buffer,
3579 docstring const & msg,
3580 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3581 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3582 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3587 string format = argument;
3589 format = used_buffer->params().getDefaultOutputFormat();
3590 processing_format = format;
3592 progress_->clearMessages();
3595 #if EXPORT_in_THREAD
3596 GuiViewPrivate::busyBuffers.insert(used_buffer);
3597 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3598 if (!cloned_buffer) {
3599 Alert::error(_("Export Error"),
3600 _("Error cloning the Buffer."));
3603 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3608 setPreviewFuture(f);
3609 last_export_format = used_buffer->params().bufferFormat();
3612 // We are asynchronous, so we don't know here anything about the success
3615 Buffer::ExportStatus status;
3617 status = (used_buffer->*syncFunc)(format, true);
3618 } else if (previewFunc) {
3619 status = (used_buffer->*previewFunc)(format);
3622 handleExportStatus(gv_, status, format);
3624 return (status == Buffer::ExportSuccess
3625 || status == Buffer::PreviewSuccess);
3629 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3631 BufferView * bv = currentBufferView();
3632 LASSERT(bv, return);
3634 // Let the current BufferView dispatch its own actions.
3635 bv->dispatch(cmd, dr);
3636 if (dr.dispatched())
3639 // Try with the document BufferView dispatch if any.
3640 BufferView * doc_bv = documentBufferView();
3641 if (doc_bv && doc_bv != bv) {
3642 doc_bv->dispatch(cmd, dr);
3643 if (dr.dispatched())
3647 // Then let the current Cursor dispatch its own actions.
3648 bv->cursor().dispatch(cmd);
3650 // update completion. We do it here and not in
3651 // processKeySym to avoid another redraw just for a
3652 // changed inline completion
3653 if (cmd.origin() == FuncRequest::KEYBOARD) {
3654 if (cmd.action() == LFUN_SELF_INSERT
3655 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3656 updateCompletion(bv->cursor(), true, true);
3657 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3658 updateCompletion(bv->cursor(), false, true);
3660 updateCompletion(bv->cursor(), false, false);
3663 dr = bv->cursor().result();
3667 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3669 BufferView * bv = currentBufferView();
3670 // By default we won't need any update.
3671 dr.screenUpdate(Update::None);
3672 // assume cmd will be dispatched
3673 dr.dispatched(true);
3675 Buffer * doc_buffer = documentBufferView()
3676 ? &(documentBufferView()->buffer()) : 0;
3678 if (cmd.origin() == FuncRequest::TOC) {
3679 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3680 // FIXME: do we need to pass a DispatchResult object here?
3681 toc->doDispatch(bv->cursor(), cmd);
3685 string const argument = to_utf8(cmd.argument());
3687 switch(cmd.action()) {
3688 case LFUN_BUFFER_CHILD_OPEN:
3689 openChildDocument(to_utf8(cmd.argument()));
3692 case LFUN_BUFFER_IMPORT:
3693 importDocument(to_utf8(cmd.argument()));
3696 case LFUN_MASTER_BUFFER_EXPORT:
3698 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3700 case LFUN_BUFFER_EXPORT: {
3703 // GCC only sees strfwd.h when building merged
3704 if (::lyx::operator==(cmd.argument(), "custom")) {
3705 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3706 // so the following test should not be needed.
3707 // In principle, we could try to switch to such a view...
3708 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3709 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3713 string const dest = cmd.getArg(1);
3714 FileName target_dir;
3715 if (!dest.empty() && FileName::isAbsolute(dest))
3716 target_dir = FileName(support::onlyPath(dest));
3718 target_dir = doc_buffer->fileName().onlyPath();
3720 string const format = (argument.empty() || argument == "default") ?
3721 doc_buffer->params().getDefaultOutputFormat() : argument;
3723 if ((dest.empty() && doc_buffer->isUnnamed())
3724 || !target_dir.isDirWritable()) {
3725 exportBufferAs(*doc_buffer, from_utf8(format));
3728 /* TODO/Review: Is it a problem to also export the children?
3729 See the update_unincluded flag */
3730 d.asyncBufferProcessing(format,
3733 &GuiViewPrivate::exportAndDestroy,
3736 // TODO Inform user about success
3740 case LFUN_BUFFER_EXPORT_AS: {
3741 LASSERT(doc_buffer, break);
3742 docstring f = cmd.argument();
3744 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3745 exportBufferAs(*doc_buffer, f);
3749 case LFUN_BUFFER_UPDATE: {
3750 d.asyncBufferProcessing(argument,
3753 &GuiViewPrivate::compileAndDestroy,
3758 case LFUN_BUFFER_VIEW: {
3759 d.asyncBufferProcessing(argument,
3761 _("Previewing ..."),
3762 &GuiViewPrivate::previewAndDestroy,
3767 case LFUN_MASTER_BUFFER_UPDATE: {
3768 d.asyncBufferProcessing(argument,
3769 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3771 &GuiViewPrivate::compileAndDestroy,
3776 case LFUN_MASTER_BUFFER_VIEW: {
3777 d.asyncBufferProcessing(argument,
3778 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3780 &GuiViewPrivate::previewAndDestroy,
3781 0, &Buffer::preview);
3784 case LFUN_BUFFER_SWITCH: {
3785 string const file_name = to_utf8(cmd.argument());
3786 if (!FileName::isAbsolute(file_name)) {
3788 dr.setMessage(_("Absolute filename expected."));
3792 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3795 dr.setMessage(_("Document not loaded"));
3799 // Do we open or switch to the buffer in this view ?
3800 if (workArea(*buffer)
3801 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3806 // Look for the buffer in other views
3807 QList<int> const ids = guiApp->viewIds();
3809 for (; i != ids.size(); ++i) {
3810 GuiView & gv = guiApp->view(ids[i]);
3811 if (gv.workArea(*buffer)) {
3813 gv.activateWindow();
3815 gv.setBuffer(buffer);
3820 // If necessary, open a new window as a last resort
3821 if (i == ids.size()) {
3822 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3828 case LFUN_BUFFER_NEXT:
3829 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3832 case LFUN_BUFFER_MOVE_NEXT:
3833 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3836 case LFUN_BUFFER_PREVIOUS:
3837 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3840 case LFUN_BUFFER_MOVE_PREVIOUS:
3841 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3844 case LFUN_BUFFER_CHKTEX:
3845 LASSERT(doc_buffer, break);
3846 doc_buffer->runChktex();
3849 case LFUN_COMMAND_EXECUTE: {
3850 command_execute_ = true;
3851 minibuffer_focus_ = true;
3854 case LFUN_DROP_LAYOUTS_CHOICE:
3855 d.layout_->showPopup();
3858 case LFUN_MENU_OPEN:
3859 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3860 menu->exec(QCursor::pos());
3863 case LFUN_FILE_INSERT:
3864 insertLyXFile(cmd.argument());
3867 case LFUN_FILE_INSERT_PLAINTEXT:
3868 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3869 string const fname = to_utf8(cmd.argument());
3870 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3871 dr.setMessage(_("Absolute filename expected."));
3875 FileName filename(fname);
3876 if (fname.empty()) {
3877 FileDialog dlg(qt_("Select file to insert"));
3879 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3880 QStringList(qt_("All Files (*)")));
3882 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3883 dr.setMessage(_("Canceled."));
3887 filename.set(fromqstr(result.second));
3891 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3892 bv->dispatch(new_cmd, dr);
3897 case LFUN_BUFFER_RELOAD: {
3898 LASSERT(doc_buffer, break);
3901 if (!doc_buffer->isClean()) {
3902 docstring const file =
3903 makeDisplayPath(doc_buffer->absFileName(), 20);
3904 if (doc_buffer->notifiesExternalModification()) {
3905 docstring text = _("The current version will be lost. "
3906 "Are you sure you want to load the version on disk "
3907 "of the document %1$s?");
3908 ret = Alert::prompt(_("Reload saved document?"),
3909 bformat(text, file), 1, 1,
3910 _("&Reload"), _("&Cancel"));
3912 docstring text = _("Any changes will be lost. "
3913 "Are you sure you want to revert to the saved version "
3914 "of the document %1$s?");
3915 ret = Alert::prompt(_("Revert to saved document?"),
3916 bformat(text, file), 1, 1,
3917 _("&Revert"), _("&Cancel"));
3922 doc_buffer->markClean();
3923 reloadBuffer(*doc_buffer);
3924 dr.forceBufferUpdate();
3929 case LFUN_BUFFER_WRITE:
3930 LASSERT(doc_buffer, break);
3931 saveBuffer(*doc_buffer);
3934 case LFUN_BUFFER_WRITE_AS:
3935 LASSERT(doc_buffer, break);
3936 renameBuffer(*doc_buffer, cmd.argument());
3939 case LFUN_BUFFER_WRITE_ALL: {
3940 Buffer * first = theBufferList().first();
3943 message(_("Saving all documents..."));
3944 // We cannot use a for loop as the buffer list cycles.
3947 if (!b->isClean()) {
3949 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3951 b = theBufferList().next(b);
3952 } while (b != first);
3953 dr.setMessage(_("All documents saved."));
3957 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3958 LASSERT(doc_buffer, break);
3959 doc_buffer->clearExternalModification();
3962 case LFUN_BUFFER_CLOSE:
3966 case LFUN_BUFFER_CLOSE_ALL:
3970 case LFUN_DEVEL_MODE_TOGGLE:
3971 devel_mode_ = !devel_mode_;
3973 dr.setMessage(_("Developer mode is now enabled."));
3975 dr.setMessage(_("Developer mode is now disabled."));
3978 case LFUN_TOOLBAR_TOGGLE: {
3979 string const name = cmd.getArg(0);
3980 if (GuiToolbar * t = toolbar(name))
3985 case LFUN_TOOLBAR_MOVABLE: {
3986 string const name = cmd.getArg(0);
3988 // toggle (all) toolbars movablility
3989 toolbarsMovable_ = !toolbarsMovable_;
3990 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3991 GuiToolbar * tb = toolbar(ti.name);
3992 if (tb && tb->isMovable() != toolbarsMovable_)
3993 // toggle toolbar movablity if it does not fit lock
3994 // (all) toolbars positions state silent = true, since
3995 // status bar notifications are slow
3998 if (toolbarsMovable_)
3999 dr.setMessage(_("Toolbars unlocked."));
4001 dr.setMessage(_("Toolbars locked."));
4002 } else if (GuiToolbar * t = toolbar(name)) {
4003 // toggle current toolbar movablity
4005 // update lock (all) toolbars positions
4006 updateLockToolbars();
4011 case LFUN_ICON_SIZE: {
4012 QSize size = d.iconSize(cmd.argument());
4014 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4015 size.width(), size.height()));
4019 case LFUN_DIALOG_UPDATE: {
4020 string const name = to_utf8(cmd.argument());
4021 if (name == "prefs" || name == "document")
4022 updateDialog(name, string());
4023 else if (name == "paragraph")
4024 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4025 else if (currentBufferView()) {
4026 Inset * inset = currentBufferView()->editedInset(name);
4027 // Can only update a dialog connected to an existing inset
4029 // FIXME: get rid of this indirection; GuiView ask the inset
4030 // if he is kind enough to update itself...
4031 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4032 //FIXME: pass DispatchResult here?
4033 inset->dispatch(currentBufferView()->cursor(), fr);
4039 case LFUN_DIALOG_TOGGLE: {
4040 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4041 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4042 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4046 case LFUN_DIALOG_DISCONNECT_INSET:
4047 disconnectDialog(to_utf8(cmd.argument()));
4050 case LFUN_DIALOG_HIDE: {
4051 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4055 case LFUN_DIALOG_SHOW: {
4056 string const name = cmd.getArg(0);
4057 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
4059 if (name == "character") {
4060 data = freefont2string();
4062 showDialog("character", data);
4063 } else if (name == "latexlog") {
4064 // getStatus checks that
4065 LATTEST(doc_buffer);
4066 Buffer::LogType type;
4067 string const logfile = doc_buffer->logName(&type);
4069 case Buffer::latexlog:
4072 case Buffer::buildlog:
4076 data += Lexer::quoteString(logfile);
4077 showDialog("log", data);
4078 } else if (name == "vclog") {
4079 // getStatus checks that
4080 LATTEST(doc_buffer);
4081 string const data = "vc " +
4082 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4083 showDialog("log", data);
4084 } else if (name == "symbols") {
4085 data = bv->cursor().getEncoding()->name();
4087 showDialog("symbols", data);
4089 } else if (name == "prefs" && isFullScreen()) {
4090 lfunUiToggle("fullscreen");
4091 showDialog("prefs", data);
4093 showDialog(name, data);
4098 dr.setMessage(cmd.argument());
4101 case LFUN_UI_TOGGLE: {
4102 string arg = cmd.getArg(0);
4103 if (!lfunUiToggle(arg)) {
4104 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4105 dr.setMessage(bformat(msg, from_utf8(arg)));
4107 // Make sure the keyboard focus stays in the work area.
4112 case LFUN_VIEW_SPLIT: {
4113 LASSERT(doc_buffer, break);
4114 string const orientation = cmd.getArg(0);
4115 d.splitter_->setOrientation(orientation == "vertical"
4116 ? Qt::Vertical : Qt::Horizontal);
4117 TabWorkArea * twa = addTabWorkArea();
4118 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4119 setCurrentWorkArea(wa);
4122 case LFUN_TAB_GROUP_CLOSE:
4123 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4124 closeTabWorkArea(twa);
4125 d.current_work_area_ = 0;
4126 twa = d.currentTabWorkArea();
4127 // Switch to the next GuiWorkArea in the found TabWorkArea.
4129 // Make sure the work area is up to date.
4130 setCurrentWorkArea(twa->currentWorkArea());
4132 setCurrentWorkArea(0);
4137 case LFUN_VIEW_CLOSE:
4138 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4139 closeWorkArea(twa->currentWorkArea());
4140 d.current_work_area_ = 0;
4141 twa = d.currentTabWorkArea();
4142 // Switch to the next GuiWorkArea in the found TabWorkArea.
4144 // Make sure the work area is up to date.
4145 setCurrentWorkArea(twa->currentWorkArea());
4147 setCurrentWorkArea(0);
4152 case LFUN_COMPLETION_INLINE:
4153 if (d.current_work_area_)
4154 d.current_work_area_->completer().showInline();
4157 case LFUN_COMPLETION_POPUP:
4158 if (d.current_work_area_)
4159 d.current_work_area_->completer().showPopup();
4164 if (d.current_work_area_)
4165 d.current_work_area_->completer().tab();
4168 case LFUN_COMPLETION_CANCEL:
4169 if (d.current_work_area_) {
4170 if (d.current_work_area_->completer().popupVisible())
4171 d.current_work_area_->completer().hidePopup();
4173 d.current_work_area_->completer().hideInline();
4177 case LFUN_COMPLETION_ACCEPT:
4178 if (d.current_work_area_)
4179 d.current_work_area_->completer().activate();
4182 case LFUN_BUFFER_ZOOM_IN:
4183 case LFUN_BUFFER_ZOOM_OUT:
4184 case LFUN_BUFFER_ZOOM: {
4185 if (cmd.argument().empty()) {
4186 if (cmd.action() == LFUN_BUFFER_ZOOM)
4188 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4193 if (cmd.action() == LFUN_BUFFER_ZOOM)
4194 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4195 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4196 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4198 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4201 // Actual zoom value: default zoom + fractional extra value
4202 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4203 if (zoom < static_cast<int>(zoom_min_))
4206 lyxrc.currentZoom = zoom;
4208 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4209 lyxrc.currentZoom, lyxrc.defaultZoom));
4211 // The global QPixmapCache is used in GuiPainter to cache text
4212 // painting so we must reset it.
4213 QPixmapCache::clear();
4214 guiApp->fontLoader().update();
4215 dr.screenUpdate(Update::Force | Update::FitCursor);
4219 case LFUN_VC_REGISTER:
4220 case LFUN_VC_RENAME:
4222 case LFUN_VC_CHECK_IN:
4223 case LFUN_VC_CHECK_OUT:
4224 case LFUN_VC_REPO_UPDATE:
4225 case LFUN_VC_LOCKING_TOGGLE:
4226 case LFUN_VC_REVERT:
4227 case LFUN_VC_UNDO_LAST:
4228 case LFUN_VC_COMMAND:
4229 case LFUN_VC_COMPARE:
4230 dispatchVC(cmd, dr);
4233 case LFUN_SERVER_GOTO_FILE_ROW:
4234 if(goToFileRow(to_utf8(cmd.argument())))
4235 dr.screenUpdate(Update::Force | Update::FitCursor);
4238 case LFUN_LYX_ACTIVATE:
4242 case LFUN_FORWARD_SEARCH: {
4243 // it seems safe to assume we have a document buffer, since
4244 // getStatus wants one.
4245 LATTEST(doc_buffer);
4246 Buffer const * doc_master = doc_buffer->masterBuffer();
4247 FileName const path(doc_master->temppath());
4248 string const texname = doc_master->isChild(doc_buffer)
4249 ? DocFileName(changeExtension(
4250 doc_buffer->absFileName(),
4251 "tex")).mangledFileName()
4252 : doc_buffer->latexName();
4253 string const fulltexname =
4254 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4255 string const mastername =
4256 removeExtension(doc_master->latexName());
4257 FileName const dviname(addName(path.absFileName(),
4258 addExtension(mastername, "dvi")));
4259 FileName const pdfname(addName(path.absFileName(),
4260 addExtension(mastername, "pdf")));
4261 bool const have_dvi = dviname.exists();
4262 bool const have_pdf = pdfname.exists();
4263 if (!have_dvi && !have_pdf) {
4264 dr.setMessage(_("Please, preview the document first."));
4267 string outname = dviname.onlyFileName();
4268 string command = lyxrc.forward_search_dvi;
4269 if (!have_dvi || (have_pdf &&
4270 pdfname.lastModified() > dviname.lastModified())) {
4271 outname = pdfname.onlyFileName();
4272 command = lyxrc.forward_search_pdf;
4275 DocIterator cur = bv->cursor();
4276 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4277 LYXERR(Debug::ACTION, "Forward search: row:" << row
4279 if (row == -1 || command.empty()) {
4280 dr.setMessage(_("Couldn't proceed."));
4283 string texrow = convert<string>(row);
4285 command = subst(command, "$$n", texrow);
4286 command = subst(command, "$$f", fulltexname);
4287 command = subst(command, "$$t", texname);
4288 command = subst(command, "$$o", outname);
4290 PathChanger p(path);
4292 one.startscript(Systemcall::DontWait, command);
4296 case LFUN_SPELLING_CONTINUOUSLY:
4297 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4298 dr.screenUpdate(Update::Force);
4302 // The LFUN must be for one of BufferView, Buffer or Cursor;
4304 dispatchToBufferView(cmd, dr);
4308 // Part of automatic menu appearance feature.
4309 if (isFullScreen()) {
4310 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4314 // Need to update bv because many LFUNs here might have destroyed it
4315 bv = currentBufferView();
4317 // Clear non-empty selections
4318 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4320 Cursor & cur = bv->cursor();
4321 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4322 cur.clearSelection();
4328 bool GuiView::lfunUiToggle(string const & ui_component)
4330 if (ui_component == "scrollbar") {
4331 // hide() is of no help
4332 if (d.current_work_area_->verticalScrollBarPolicy() ==
4333 Qt::ScrollBarAlwaysOff)
4335 d.current_work_area_->setVerticalScrollBarPolicy(
4336 Qt::ScrollBarAsNeeded);
4338 d.current_work_area_->setVerticalScrollBarPolicy(
4339 Qt::ScrollBarAlwaysOff);
4340 } else if (ui_component == "statusbar") {
4341 statusBar()->setVisible(!statusBar()->isVisible());
4342 } else if (ui_component == "menubar") {
4343 menuBar()->setVisible(!menuBar()->isVisible());
4345 if (ui_component == "frame") {
4347 getContentsMargins(&l, &t, &r, &b);
4348 //are the frames in default state?
4349 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4351 setContentsMargins(-2, -2, -2, -2);
4353 setContentsMargins(0, 0, 0, 0);
4356 if (ui_component == "fullscreen") {
4364 void GuiView::toggleFullScreen()
4366 if (isFullScreen()) {
4367 for (int i = 0; i != d.splitter_->count(); ++i)
4368 d.tabWorkArea(i)->setFullScreen(false);
4369 setContentsMargins(0, 0, 0, 0);
4370 setWindowState(windowState() ^ Qt::WindowFullScreen);
4373 statusBar()->show();
4376 hideDialogs("prefs", 0);
4377 for (int i = 0; i != d.splitter_->count(); ++i)
4378 d.tabWorkArea(i)->setFullScreen(true);
4379 setContentsMargins(-2, -2, -2, -2);
4381 setWindowState(windowState() ^ Qt::WindowFullScreen);
4382 if (lyxrc.full_screen_statusbar)
4383 statusBar()->hide();
4384 if (lyxrc.full_screen_menubar)
4386 if (lyxrc.full_screen_toolbars) {
4387 ToolbarMap::iterator end = d.toolbars_.end();
4388 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4393 // give dialogs like the TOC a chance to adapt
4398 Buffer const * GuiView::updateInset(Inset const * inset)
4403 Buffer const * inset_buffer = &(inset->buffer());
4405 for (int i = 0; i != d.splitter_->count(); ++i) {
4406 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4409 Buffer const * buffer = &(wa->bufferView().buffer());
4410 if (inset_buffer == buffer)
4411 wa->scheduleRedraw(true);
4413 return inset_buffer;
4417 void GuiView::restartCaret()
4419 /* When we move around, or type, it's nice to be able to see
4420 * the caret immediately after the keypress.
4422 if (d.current_work_area_)
4423 d.current_work_area_->startBlinkingCaret();
4425 // Take this occasion to update the other GUI elements.
4431 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4433 if (d.current_work_area_)
4434 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4439 // This list should be kept in sync with the list of insets in
4440 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4441 // dialog should have the same name as the inset.
4442 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4443 // docs in LyXAction.cpp.
4445 char const * const dialognames[] = {
4447 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4448 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4449 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4450 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4451 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4452 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4453 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4454 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4456 char const * const * const end_dialognames =
4457 dialognames + (sizeof(dialognames) / sizeof(char *));
4461 cmpCStr(char const * name) : name_(name) {}
4462 bool operator()(char const * other) {
4463 return strcmp(other, name_) == 0;
4470 bool isValidName(string const & name)
4472 return find_if(dialognames, end_dialognames,
4473 cmpCStr(name.c_str())) != end_dialognames;
4479 void GuiView::resetDialogs()
4481 // Make sure that no LFUN uses any GuiView.
4482 guiApp->setCurrentView(0);
4486 constructToolbars();
4487 guiApp->menus().fillMenuBar(menuBar(), this, false);
4488 d.layout_->updateContents(true);
4489 // Now update controls with current buffer.
4490 guiApp->setCurrentView(this);
4496 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4498 if (!isValidName(name))
4501 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4503 if (it != d.dialogs_.end()) {
4505 it->second->hideView();
4506 return it->second.get();
4509 Dialog * dialog = build(name);
4510 d.dialogs_[name].reset(dialog);
4511 if (lyxrc.allow_geometry_session)
4512 dialog->restoreSession();
4519 void GuiView::showDialog(string const & name, string const & data,
4522 triggerShowDialog(toqstr(name), toqstr(data), inset);
4526 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4532 const string name = fromqstr(qname);
4533 const string data = fromqstr(qdata);
4537 Dialog * dialog = findOrBuild(name, false);
4539 bool const visible = dialog->isVisibleView();
4540 dialog->showData(data);
4541 if (inset && currentBufferView())
4542 currentBufferView()->editInset(name, inset);
4543 // We only set the focus to the new dialog if it was not yet
4544 // visible in order not to change the existing previous behaviour
4546 // activateWindow is needed for floating dockviews
4547 dialog->asQWidget()->raise();
4548 dialog->asQWidget()->activateWindow();
4549 dialog->asQWidget()->setFocus();
4553 catch (ExceptionMessage const & ex) {
4561 bool GuiView::isDialogVisible(string const & name) const
4563 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4564 if (it == d.dialogs_.end())
4566 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4570 void GuiView::hideDialog(string const & name, Inset * inset)
4572 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4573 if (it == d.dialogs_.end())
4577 if (!currentBufferView())
4579 if (inset != currentBufferView()->editedInset(name))
4583 Dialog * const dialog = it->second.get();
4584 if (dialog->isVisibleView())
4586 if (currentBufferView())
4587 currentBufferView()->editInset(name, 0);
4591 void GuiView::disconnectDialog(string const & name)
4593 if (!isValidName(name))
4595 if (currentBufferView())
4596 currentBufferView()->editInset(name, 0);
4600 void GuiView::hideAll() const
4602 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4603 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4605 for(; it != end; ++it)
4606 it->second->hideView();
4610 void GuiView::updateDialogs()
4612 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4613 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4615 for(; it != end; ++it) {
4616 Dialog * dialog = it->second.get();
4618 if (dialog->needBufferOpen() && !documentBufferView())
4619 hideDialog(fromqstr(dialog->name()), 0);
4620 else if (dialog->isVisibleView())
4621 dialog->checkStatus();
4628 Dialog * createDialog(GuiView & lv, string const & name);
4630 // will be replaced by a proper factory...
4631 Dialog * createGuiAbout(GuiView & lv);
4632 Dialog * createGuiBibtex(GuiView & lv);
4633 Dialog * createGuiChanges(GuiView & lv);
4634 Dialog * createGuiCharacter(GuiView & lv);
4635 Dialog * createGuiCitation(GuiView & lv);
4636 Dialog * createGuiCompare(GuiView & lv);
4637 Dialog * createGuiCompareHistory(GuiView & lv);
4638 Dialog * createGuiDelimiter(GuiView & lv);
4639 Dialog * createGuiDocument(GuiView & lv);
4640 Dialog * createGuiErrorList(GuiView & lv);
4641 Dialog * createGuiExternal(GuiView & lv);
4642 Dialog * createGuiGraphics(GuiView & lv);
4643 Dialog * createGuiInclude(GuiView & lv);
4644 Dialog * createGuiIndex(GuiView & lv);
4645 Dialog * createGuiListings(GuiView & lv);
4646 Dialog * createGuiLog(GuiView & lv);
4647 Dialog * createGuiMathMatrix(GuiView & lv);
4648 Dialog * createGuiNote(GuiView & lv);
4649 Dialog * createGuiParagraph(GuiView & lv);
4650 Dialog * createGuiPhantom(GuiView & lv);
4651 Dialog * createGuiPreferences(GuiView & lv);
4652 Dialog * createGuiPrint(GuiView & lv);
4653 Dialog * createGuiPrintindex(GuiView & lv);
4654 Dialog * createGuiRef(GuiView & lv);
4655 Dialog * createGuiSearch(GuiView & lv);
4656 Dialog * createGuiSearchAdv(GuiView & lv);
4657 Dialog * createGuiSendTo(GuiView & lv);
4658 Dialog * createGuiShowFile(GuiView & lv);
4659 Dialog * createGuiSpellchecker(GuiView & lv);
4660 Dialog * createGuiSymbols(GuiView & lv);
4661 Dialog * createGuiTabularCreate(GuiView & lv);
4662 Dialog * createGuiTexInfo(GuiView & lv);
4663 Dialog * createGuiToc(GuiView & lv);
4664 Dialog * createGuiThesaurus(GuiView & lv);
4665 Dialog * createGuiViewSource(GuiView & lv);
4666 Dialog * createGuiWrap(GuiView & lv);
4667 Dialog * createGuiProgressView(GuiView & lv);
4671 Dialog * GuiView::build(string const & name)
4673 LASSERT(isValidName(name), return 0);
4675 Dialog * dialog = createDialog(*this, name);
4679 if (name == "aboutlyx")
4680 return createGuiAbout(*this);
4681 if (name == "bibtex")
4682 return createGuiBibtex(*this);
4683 if (name == "changes")
4684 return createGuiChanges(*this);
4685 if (name == "character")
4686 return createGuiCharacter(*this);
4687 if (name == "citation")
4688 return createGuiCitation(*this);
4689 if (name == "compare")
4690 return createGuiCompare(*this);
4691 if (name == "comparehistory")
4692 return createGuiCompareHistory(*this);
4693 if (name == "document")
4694 return createGuiDocument(*this);
4695 if (name == "errorlist")
4696 return createGuiErrorList(*this);
4697 if (name == "external")
4698 return createGuiExternal(*this);
4700 return createGuiShowFile(*this);
4701 if (name == "findreplace")
4702 return createGuiSearch(*this);
4703 if (name == "findreplaceadv")
4704 return createGuiSearchAdv(*this);
4705 if (name == "graphics")
4706 return createGuiGraphics(*this);
4707 if (name == "include")
4708 return createGuiInclude(*this);
4709 if (name == "index")
4710 return createGuiIndex(*this);
4711 if (name == "index_print")
4712 return createGuiPrintindex(*this);
4713 if (name == "listings")
4714 return createGuiListings(*this);
4716 return createGuiLog(*this);
4717 if (name == "mathdelimiter")
4718 return createGuiDelimiter(*this);
4719 if (name == "mathmatrix")
4720 return createGuiMathMatrix(*this);
4722 return createGuiNote(*this);
4723 if (name == "paragraph")
4724 return createGuiParagraph(*this);
4725 if (name == "phantom")
4726 return createGuiPhantom(*this);
4727 if (name == "prefs")
4728 return createGuiPreferences(*this);
4730 return createGuiRef(*this);
4731 if (name == "sendto")
4732 return createGuiSendTo(*this);
4733 if (name == "spellchecker")
4734 return createGuiSpellchecker(*this);
4735 if (name == "symbols")
4736 return createGuiSymbols(*this);
4737 if (name == "tabularcreate")
4738 return createGuiTabularCreate(*this);
4739 if (name == "texinfo")
4740 return createGuiTexInfo(*this);
4741 if (name == "thesaurus")
4742 return createGuiThesaurus(*this);
4744 return createGuiToc(*this);
4745 if (name == "view-source")
4746 return createGuiViewSource(*this);
4748 return createGuiWrap(*this);
4749 if (name == "progress")
4750 return createGuiProgressView(*this);
4756 SEMenu::SEMenu(QWidget * parent)
4758 QAction * action = addAction(qt_("Disable Shell Escape"));
4759 connect(action, SIGNAL(triggered()),
4760 parent, SLOT(disableShellEscape()));
4764 } // namespace frontend
4767 #include "moc_GuiView.cpp"