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_UPDATE:
1863 case LFUN_MASTER_BUFFER_VIEW:
1865 && (doc_buffer->parent() != 0
1866 || doc_buffer->hasChildren())
1867 && !d.processing_thread_watcher_.isRunning();
1870 case LFUN_BUFFER_UPDATE:
1871 case LFUN_BUFFER_VIEW: {
1872 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1876 string format = to_utf8(cmd.argument());
1877 if (cmd.argument().empty())
1878 format = doc_buffer->params().getDefaultOutputFormat();
1879 enable = doc_buffer->params().isExportable(format, true);
1883 case LFUN_BUFFER_RELOAD:
1884 enable = doc_buffer && !doc_buffer->isUnnamed()
1885 && doc_buffer->fileName().exists()
1886 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1889 case LFUN_BUFFER_CHILD_OPEN:
1890 enable = doc_buffer != 0;
1893 case LFUN_BUFFER_WRITE:
1894 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1897 //FIXME: This LFUN should be moved to GuiApplication.
1898 case LFUN_BUFFER_WRITE_ALL: {
1899 // We enable the command only if there are some modified buffers
1900 Buffer * first = theBufferList().first();
1905 // We cannot use a for loop as the buffer list is a cycle.
1907 if (!b->isClean()) {
1911 b = theBufferList().next(b);
1912 } while (b != first);
1916 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1917 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1920 case LFUN_BUFFER_EXPORT: {
1921 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1925 return doc_buffer->getStatus(cmd, flag);
1929 case LFUN_BUFFER_EXPORT_AS:
1930 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1935 case LFUN_BUFFER_WRITE_AS:
1936 enable = doc_buffer != 0;
1939 case LFUN_BUFFER_CLOSE:
1940 case LFUN_VIEW_CLOSE:
1941 enable = doc_buffer != 0;
1944 case LFUN_BUFFER_CLOSE_ALL:
1945 enable = theBufferList().last() != theBufferList().first();
1948 case LFUN_BUFFER_CHKTEX: {
1949 // hide if we have no checktex command
1950 if (lyxrc.chktex_command.empty()) {
1951 flag.setUnknown(true);
1955 if (!doc_buffer || !doc_buffer->params().isLatex()
1956 || d.processing_thread_watcher_.isRunning()) {
1957 // grey out, don't hide
1965 case LFUN_VIEW_SPLIT:
1966 if (cmd.getArg(0) == "vertical")
1967 enable = doc_buffer && (d.splitter_->count() == 1 ||
1968 d.splitter_->orientation() == Qt::Vertical);
1970 enable = doc_buffer && (d.splitter_->count() == 1 ||
1971 d.splitter_->orientation() == Qt::Horizontal);
1974 case LFUN_TAB_GROUP_CLOSE:
1975 enable = d.tabWorkAreaCount() > 1;
1978 case LFUN_DEVEL_MODE_TOGGLE:
1979 flag.setOnOff(devel_mode_);
1982 case LFUN_TOOLBAR_TOGGLE: {
1983 string const name = cmd.getArg(0);
1984 if (GuiToolbar * t = toolbar(name))
1985 flag.setOnOff(t->isVisible());
1988 docstring const msg =
1989 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
1995 case LFUN_TOOLBAR_MOVABLE: {
1996 string const name = cmd.getArg(0);
1997 // use negation since locked == !movable
1999 // toolbar name * locks all toolbars
2000 flag.setOnOff(!toolbarsMovable_);
2001 else if (GuiToolbar * t = toolbar(name))
2002 flag.setOnOff(!(t->isMovable()));
2005 docstring const msg =
2006 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2012 case LFUN_ICON_SIZE:
2013 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2016 case LFUN_DROP_LAYOUTS_CHOICE:
2020 case LFUN_UI_TOGGLE:
2021 flag.setOnOff(isFullScreen());
2024 case LFUN_DIALOG_DISCONNECT_INSET:
2027 case LFUN_DIALOG_HIDE:
2028 // FIXME: should we check if the dialog is shown?
2031 case LFUN_DIALOG_TOGGLE:
2032 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2035 case LFUN_DIALOG_SHOW: {
2036 string const name = cmd.getArg(0);
2038 enable = name == "aboutlyx"
2039 || name == "file" //FIXME: should be removed.
2041 || name == "texinfo"
2042 || name == "progress"
2043 || name == "compare";
2044 else if (name == "character" || name == "symbols"
2045 || name == "mathdelimiter" || name == "mathmatrix") {
2046 if (!buf || buf->isReadonly())
2049 Cursor const & cur = currentBufferView()->cursor();
2050 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2053 else if (name == "latexlog")
2054 enable = FileName(doc_buffer->logName()).isReadableFile();
2055 else if (name == "spellchecker")
2056 enable = theSpellChecker()
2057 && !doc_buffer->isReadonly()
2058 && !doc_buffer->text().empty();
2059 else if (name == "vclog")
2060 enable = doc_buffer->lyxvc().inUse();
2064 case LFUN_DIALOG_UPDATE: {
2065 string const name = cmd.getArg(0);
2067 enable = name == "prefs";
2071 case LFUN_COMMAND_EXECUTE:
2073 case LFUN_MENU_OPEN:
2074 // Nothing to check.
2077 case LFUN_COMPLETION_INLINE:
2078 if (!d.current_work_area_
2079 || !d.current_work_area_->completer().inlinePossible(
2080 currentBufferView()->cursor()))
2084 case LFUN_COMPLETION_POPUP:
2085 if (!d.current_work_area_
2086 || !d.current_work_area_->completer().popupPossible(
2087 currentBufferView()->cursor()))
2092 if (!d.current_work_area_
2093 || !d.current_work_area_->completer().inlinePossible(
2094 currentBufferView()->cursor()))
2098 case LFUN_COMPLETION_ACCEPT:
2099 if (!d.current_work_area_
2100 || (!d.current_work_area_->completer().popupVisible()
2101 && !d.current_work_area_->completer().inlineVisible()
2102 && !d.current_work_area_->completer().completionAvailable()))
2106 case LFUN_COMPLETION_CANCEL:
2107 if (!d.current_work_area_
2108 || (!d.current_work_area_->completer().popupVisible()
2109 && !d.current_work_area_->completer().inlineVisible()))
2113 case LFUN_BUFFER_ZOOM_OUT:
2114 case LFUN_BUFFER_ZOOM_IN: {
2115 // only diff between these two is that the default for ZOOM_OUT
2117 bool const neg_zoom =
2118 convert<int>(cmd.argument()) < 0 ||
2119 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2120 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2121 docstring const msg =
2122 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2126 enable = doc_buffer;
2130 case LFUN_BUFFER_ZOOM: {
2131 bool const less_than_min_zoom =
2132 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2133 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2134 docstring const msg =
2135 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2140 enable = doc_buffer;
2144 case LFUN_BUFFER_MOVE_NEXT:
2145 case LFUN_BUFFER_MOVE_PREVIOUS:
2146 // we do not cycle when moving
2147 case LFUN_BUFFER_NEXT:
2148 case LFUN_BUFFER_PREVIOUS:
2149 // because we cycle, it doesn't matter whether on first or last
2150 enable = (d.currentTabWorkArea()->count() > 1);
2152 case LFUN_BUFFER_SWITCH:
2153 // toggle on the current buffer, but do not toggle off
2154 // the other ones (is that a good idea?)
2156 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2157 flag.setOnOff(true);
2160 case LFUN_VC_REGISTER:
2161 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2163 case LFUN_VC_RENAME:
2164 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2167 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2169 case LFUN_VC_CHECK_IN:
2170 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2172 case LFUN_VC_CHECK_OUT:
2173 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2175 case LFUN_VC_LOCKING_TOGGLE:
2176 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2177 && doc_buffer->lyxvc().lockingToggleEnabled();
2178 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2180 case LFUN_VC_REVERT:
2181 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2182 && !doc_buffer->hasReadonlyFlag();
2184 case LFUN_VC_UNDO_LAST:
2185 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2187 case LFUN_VC_REPO_UPDATE:
2188 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2190 case LFUN_VC_COMMAND: {
2191 if (cmd.argument().empty())
2193 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2197 case LFUN_VC_COMPARE:
2198 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2201 case LFUN_SERVER_GOTO_FILE_ROW:
2202 case LFUN_LYX_ACTIVATE:
2204 case LFUN_FORWARD_SEARCH:
2205 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2208 case LFUN_FILE_INSERT_PLAINTEXT:
2209 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2210 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2213 case LFUN_SPELLING_CONTINUOUSLY:
2214 flag.setOnOff(lyxrc.spellcheck_continuously);
2222 flag.setEnabled(false);
2228 static FileName selectTemplateFile()
2230 FileDialog dlg(qt_("Select template file"));
2231 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2232 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2234 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2235 QStringList(qt_("LyX Documents (*.lyx)")));
2237 if (result.first == FileDialog::Later)
2239 if (result.second.isEmpty())
2241 return FileName(fromqstr(result.second));
2245 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2249 Buffer * newBuffer = 0;
2251 newBuffer = checkAndLoadLyXFile(filename);
2252 } catch (ExceptionMessage const & e) {
2259 message(_("Document not loaded."));
2263 setBuffer(newBuffer);
2264 newBuffer->errors("Parse");
2267 theSession().lastFiles().add(filename);
2268 theSession().writeFile();
2275 void GuiView::openDocument(string const & fname)
2277 string initpath = lyxrc.document_path;
2279 if (documentBufferView()) {
2280 string const trypath = documentBufferView()->buffer().filePath();
2281 // If directory is writeable, use this as default.
2282 if (FileName(trypath).isDirWritable())
2288 if (fname.empty()) {
2289 FileDialog dlg(qt_("Select document to open"));
2290 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2291 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2293 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2294 FileDialog::Result result =
2295 dlg.open(toqstr(initpath), filter);
2297 if (result.first == FileDialog::Later)
2300 filename = fromqstr(result.second);
2302 // check selected filename
2303 if (filename.empty()) {
2304 message(_("Canceled."));
2310 // get absolute path of file and add ".lyx" to the filename if
2312 FileName const fullname =
2313 fileSearch(string(), filename, "lyx", support::may_not_exist);
2314 if (!fullname.empty())
2315 filename = fullname.absFileName();
2317 if (!fullname.onlyPath().isDirectory()) {
2318 Alert::warning(_("Invalid filename"),
2319 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2320 from_utf8(fullname.absFileName())));
2324 // if the file doesn't exist and isn't already open (bug 6645),
2325 // let the user create one
2326 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2327 !LyXVC::file_not_found_hook(fullname)) {
2328 // the user specifically chose this name. Believe him.
2329 Buffer * const b = newFile(filename, string(), true);
2335 docstring const disp_fn = makeDisplayPath(filename);
2336 message(bformat(_("Opening document %1$s..."), disp_fn));
2339 Buffer * buf = loadDocument(fullname);
2341 str2 = bformat(_("Document %1$s opened."), disp_fn);
2342 if (buf->lyxvc().inUse())
2343 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2344 " " + _("Version control detected.");
2346 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2351 // FIXME: clean that
2352 static bool import(GuiView * lv, FileName const & filename,
2353 string const & format, ErrorList & errorList)
2355 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2357 string loader_format;
2358 vector<string> loaders = theConverters().loaders();
2359 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2360 vector<string>::const_iterator it = loaders.begin();
2361 vector<string>::const_iterator en = loaders.end();
2362 for (; it != en; ++it) {
2363 if (!theConverters().isReachable(format, *it))
2366 string const tofile =
2367 support::changeExtension(filename.absFileName(),
2368 theFormats().extension(*it));
2369 if (!theConverters().convert(0, filename, FileName(tofile),
2370 filename, format, *it, errorList))
2372 loader_format = *it;
2375 if (loader_format.empty()) {
2376 frontend::Alert::error(_("Couldn't import file"),
2377 bformat(_("No information for importing the format %1$s."),
2378 theFormats().prettyName(format)));
2382 loader_format = format;
2384 if (loader_format == "lyx") {
2385 Buffer * buf = lv->loadDocument(lyxfile);
2389 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2393 bool as_paragraphs = loader_format == "textparagraph";
2394 string filename2 = (loader_format == format) ? filename.absFileName()
2395 : support::changeExtension(filename.absFileName(),
2396 theFormats().extension(loader_format));
2397 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2399 guiApp->setCurrentView(lv);
2400 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2407 void GuiView::importDocument(string const & argument)
2410 string filename = split(argument, format, ' ');
2412 LYXERR(Debug::INFO, format << " file: " << filename);
2414 // need user interaction
2415 if (filename.empty()) {
2416 string initpath = lyxrc.document_path;
2417 if (documentBufferView()) {
2418 string const trypath = documentBufferView()->buffer().filePath();
2419 // If directory is writeable, use this as default.
2420 if (FileName(trypath).isDirWritable())
2424 docstring const text = bformat(_("Select %1$s file to import"),
2425 theFormats().prettyName(format));
2427 FileDialog dlg(toqstr(text));
2428 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2429 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2431 docstring filter = theFormats().prettyName(format);
2434 filter += from_utf8(theFormats().extensions(format));
2437 FileDialog::Result result =
2438 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2440 if (result.first == FileDialog::Later)
2443 filename = fromqstr(result.second);
2445 // check selected filename
2446 if (filename.empty())
2447 message(_("Canceled."));
2450 if (filename.empty())
2453 // get absolute path of file
2454 FileName const fullname(support::makeAbsPath(filename));
2456 // Can happen if the user entered a path into the dialog
2458 if (fullname.onlyFileName().empty()) {
2459 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2460 "Aborting import."),
2461 from_utf8(fullname.absFileName()));
2462 frontend::Alert::error(_("File name error"), msg);
2463 message(_("Canceled."));
2468 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2470 // Check if the document already is open
2471 Buffer * buf = theBufferList().getBuffer(lyxfile);
2474 if (!closeBuffer()) {
2475 message(_("Canceled."));
2480 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2482 // if the file exists already, and we didn't do
2483 // -i lyx thefile.lyx, warn
2484 if (lyxfile.exists() && fullname != lyxfile) {
2486 docstring text = bformat(_("The document %1$s already exists.\n\n"
2487 "Do you want to overwrite that document?"), displaypath);
2488 int const ret = Alert::prompt(_("Overwrite document?"),
2489 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2492 message(_("Canceled."));
2497 message(bformat(_("Importing %1$s..."), displaypath));
2498 ErrorList errorList;
2499 if (import(this, fullname, format, errorList))
2500 message(_("imported."));
2502 message(_("file not imported!"));
2504 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2508 void GuiView::newDocument(string const & filename, bool from_template)
2510 FileName initpath(lyxrc.document_path);
2511 if (documentBufferView()) {
2512 FileName const trypath(documentBufferView()->buffer().filePath());
2513 // If directory is writeable, use this as default.
2514 if (trypath.isDirWritable())
2518 string templatefile;
2519 if (from_template) {
2520 templatefile = selectTemplateFile().absFileName();
2521 if (templatefile.empty())
2526 if (filename.empty())
2527 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2529 b = newFile(filename, templatefile, true);
2534 // If no new document could be created, it is unsure
2535 // whether there is a valid BufferView.
2536 if (currentBufferView())
2537 // Ensure the cursor is correctly positioned on screen.
2538 currentBufferView()->showCursor();
2542 void GuiView::insertLyXFile(docstring const & fname)
2544 BufferView * bv = documentBufferView();
2549 FileName filename(to_utf8(fname));
2550 if (filename.empty()) {
2551 // Launch a file browser
2553 string initpath = lyxrc.document_path;
2554 string const trypath = bv->buffer().filePath();
2555 // If directory is writeable, use this as default.
2556 if (FileName(trypath).isDirWritable())
2560 FileDialog dlg(qt_("Select LyX document to insert"));
2561 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2562 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2564 FileDialog::Result result = dlg.open(toqstr(initpath),
2565 QStringList(qt_("LyX Documents (*.lyx)")));
2567 if (result.first == FileDialog::Later)
2571 filename.set(fromqstr(result.second));
2573 // check selected filename
2574 if (filename.empty()) {
2575 // emit message signal.
2576 message(_("Canceled."));
2581 bv->insertLyXFile(filename);
2582 bv->buffer().errors("Parse");
2586 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2588 FileName fname = b.fileName();
2589 FileName const oldname = fname;
2591 if (!newname.empty()) {
2593 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2595 // Switch to this Buffer.
2598 // No argument? Ask user through dialog.
2600 FileDialog dlg(qt_("Choose a filename to save document as"));
2601 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2602 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2604 if (!isLyXFileName(fname.absFileName()))
2605 fname.changeExtension(".lyx");
2607 FileDialog::Result result =
2608 dlg.save(toqstr(fname.onlyPath().absFileName()),
2609 QStringList(qt_("LyX Documents (*.lyx)")),
2610 toqstr(fname.onlyFileName()));
2612 if (result.first == FileDialog::Later)
2615 fname.set(fromqstr(result.second));
2620 if (!isLyXFileName(fname.absFileName()))
2621 fname.changeExtension(".lyx");
2624 // fname is now the new Buffer location.
2626 // if there is already a Buffer open with this name, we do not want
2627 // to have another one. (the second test makes sure we're not just
2628 // trying to overwrite ourselves, which is fine.)
2629 if (theBufferList().exists(fname) && fname != oldname
2630 && theBufferList().getBuffer(fname) != &b) {
2631 docstring const text =
2632 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2633 "Please close it before attempting to overwrite it.\n"
2634 "Do you want to choose a new filename?"),
2635 from_utf8(fname.absFileName()));
2636 int const ret = Alert::prompt(_("Chosen File Already Open"),
2637 text, 0, 1, _("&Rename"), _("&Cancel"));
2639 case 0: return renameBuffer(b, docstring(), kind);
2640 case 1: return false;
2645 bool const existsLocal = fname.exists();
2646 bool const existsInVC = LyXVC::fileInVC(fname);
2647 if (existsLocal || existsInVC) {
2648 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2649 if (kind != LV_WRITE_AS && existsInVC) {
2650 // renaming to a name that is already in VC
2652 docstring text = bformat(_("The document %1$s "
2653 "is already registered.\n\n"
2654 "Do you want to choose a new name?"),
2656 docstring const title = (kind == LV_VC_RENAME) ?
2657 _("Rename document?") : _("Copy document?");
2658 docstring const button = (kind == LV_VC_RENAME) ?
2659 _("&Rename") : _("&Copy");
2660 int const ret = Alert::prompt(title, text, 0, 1,
2661 button, _("&Cancel"));
2663 case 0: return renameBuffer(b, docstring(), kind);
2664 case 1: return false;
2669 docstring text = bformat(_("The document %1$s "
2670 "already exists.\n\n"
2671 "Do you want to overwrite that document?"),
2673 int const ret = Alert::prompt(_("Overwrite document?"),
2674 text, 0, 2, _("&Overwrite"),
2675 _("&Rename"), _("&Cancel"));
2678 case 1: return renameBuffer(b, docstring(), kind);
2679 case 2: return false;
2685 case LV_VC_RENAME: {
2686 string msg = b.lyxvc().rename(fname);
2689 message(from_utf8(msg));
2693 string msg = b.lyxvc().copy(fname);
2696 message(from_utf8(msg));
2702 // LyXVC created the file already in case of LV_VC_RENAME or
2703 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2704 // relative paths of included stuff right if we moved e.g. from
2705 // /a/b.lyx to /a/c/b.lyx.
2707 bool const saved = saveBuffer(b, fname);
2714 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2716 FileName fname = b.fileName();
2718 FileDialog dlg(qt_("Choose a filename to export the document as"));
2719 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2722 QString const anyformat = qt_("Guess from extension (*.*)");
2725 vector<Format const *> export_formats;
2726 for (Format const & f : theFormats())
2727 if (f.documentFormat())
2728 export_formats.push_back(&f);
2729 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2730 map<QString, string> fmap;
2733 for (Format const * f : export_formats) {
2734 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2735 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2737 from_ascii(f->extension())));
2738 types << loc_filter;
2739 fmap[loc_filter] = f->name();
2740 if (from_ascii(f->name()) == iformat) {
2741 filter = loc_filter;
2742 ext = f->extension();
2745 string ofname = fname.onlyFileName();
2747 ofname = support::changeExtension(ofname, ext);
2748 FileDialog::Result result =
2749 dlg.save(toqstr(fname.onlyPath().absFileName()),
2753 if (result.first != FileDialog::Chosen)
2757 fname.set(fromqstr(result.second));
2758 if (filter == anyformat)
2759 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2761 fmt_name = fmap[filter];
2762 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2763 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2765 if (fmt_name.empty() || fname.empty())
2768 // fname is now the new Buffer location.
2769 if (FileName(fname).exists()) {
2770 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2771 docstring text = bformat(_("The document %1$s already "
2772 "exists.\n\nDo you want to "
2773 "overwrite that document?"),
2775 int const ret = Alert::prompt(_("Overwrite document?"),
2776 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2779 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2780 case 2: return false;
2784 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2787 return dr.dispatched();
2791 bool GuiView::saveBuffer(Buffer & b)
2793 return saveBuffer(b, FileName());
2797 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2799 if (workArea(b) && workArea(b)->inDialogMode())
2802 if (fn.empty() && b.isUnnamed())
2803 return renameBuffer(b, docstring());
2805 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2807 theSession().lastFiles().add(b.fileName());
2808 theSession().writeFile();
2812 // Switch to this Buffer.
2815 // FIXME: we don't tell the user *WHY* the save failed !!
2816 docstring const file = makeDisplayPath(b.absFileName(), 30);
2817 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2818 "Do you want to rename the document and "
2819 "try again?"), file);
2820 int const ret = Alert::prompt(_("Rename and save?"),
2821 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2824 if (!renameBuffer(b, docstring()))
2833 return saveBuffer(b, fn);
2837 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2839 return closeWorkArea(wa, false);
2843 // We only want to close the buffer if it is not visible in other workareas
2844 // of the same view, nor in other views, and if this is not a child
2845 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2847 Buffer & buf = wa->bufferView().buffer();
2849 bool last_wa = d.countWorkAreasOf(buf) == 1
2850 && !inOtherView(buf) && !buf.parent();
2852 bool close_buffer = last_wa;
2855 if (lyxrc.close_buffer_with_last_view == "yes")
2857 else if (lyxrc.close_buffer_with_last_view == "no")
2858 close_buffer = false;
2861 if (buf.isUnnamed())
2862 file = from_utf8(buf.fileName().onlyFileName());
2864 file = buf.fileName().displayName(30);
2865 docstring const text = bformat(
2866 _("Last view on document %1$s is being closed.\n"
2867 "Would you like to close or hide the document?\n"
2869 "Hidden documents can be displayed back through\n"
2870 "the menu: View->Hidden->...\n"
2872 "To remove this question, set your preference in:\n"
2873 " Tools->Preferences->Look&Feel->UserInterface\n"
2875 int ret = Alert::prompt(_("Close or hide document?"),
2876 text, 0, 1, _("&Close"), _("&Hide"));
2877 close_buffer = (ret == 0);
2881 return closeWorkArea(wa, close_buffer);
2885 bool GuiView::closeBuffer()
2887 GuiWorkArea * wa = currentMainWorkArea();
2888 // coverity complained about this
2889 // it seems unnecessary, but perhaps is worth the check
2890 LASSERT(wa, return false);
2892 setCurrentWorkArea(wa);
2893 Buffer & buf = wa->bufferView().buffer();
2894 return closeWorkArea(wa, !buf.parent());
2898 void GuiView::writeSession() const {
2899 GuiWorkArea const * active_wa = currentMainWorkArea();
2900 for (int i = 0; i < d.splitter_->count(); ++i) {
2901 TabWorkArea * twa = d.tabWorkArea(i);
2902 for (int j = 0; j < twa->count(); ++j) {
2903 GuiWorkArea * wa = twa->workArea(j);
2904 Buffer & buf = wa->bufferView().buffer();
2905 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2911 bool GuiView::closeBufferAll()
2913 // Close the workareas in all other views
2914 QList<int> const ids = guiApp->viewIds();
2915 for (int i = 0; i != ids.size(); ++i) {
2916 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2920 // Close our own workareas
2921 if (!closeWorkAreaAll())
2924 // Now close the hidden buffers. We prevent hidden buffers from being
2925 // dirty, so we can just close them.
2926 theBufferList().closeAll();
2931 bool GuiView::closeWorkAreaAll()
2933 setCurrentWorkArea(currentMainWorkArea());
2935 // We might be in a situation that there is still a tabWorkArea, but
2936 // there are no tabs anymore. This can happen when we get here after a
2937 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2938 // many TabWorkArea's have no documents anymore.
2941 // We have to call count() each time, because it can happen that
2942 // more than one splitter will disappear in one iteration (bug 5998).
2943 while (d.splitter_->count() > empty_twa) {
2944 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2946 if (twa->count() == 0)
2949 setCurrentWorkArea(twa->currentWorkArea());
2950 if (!closeTabWorkArea(twa))
2958 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2963 Buffer & buf = wa->bufferView().buffer();
2965 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2966 Alert::warning(_("Close document"),
2967 _("Document could not be closed because it is being processed by LyX."));
2972 return closeBuffer(buf);
2974 if (!inMultiTabs(wa))
2975 if (!saveBufferIfNeeded(buf, true))
2983 bool GuiView::closeBuffer(Buffer & buf)
2985 // If we are in a close_event all children will be closed in some time,
2986 // so no need to do it here. This will ensure that the children end up
2987 // in the session file in the correct order. If we close the master
2988 // buffer, we can close or release the child buffers here too.
2989 bool success = true;
2991 ListOfBuffers clist = buf.getChildren();
2992 ListOfBuffers::const_iterator it = clist.begin();
2993 ListOfBuffers::const_iterator const bend = clist.end();
2994 for (; it != bend; ++it) {
2995 Buffer * child_buf = *it;
2996 if (theBufferList().isOthersChild(&buf, child_buf)) {
2997 child_buf->setParent(0);
3001 // FIXME: should we look in other tabworkareas?
3002 // ANSWER: I don't think so. I've tested, and if the child is
3003 // open in some other window, it closes without a problem.
3004 GuiWorkArea * child_wa = workArea(*child_buf);
3006 success = closeWorkArea(child_wa, true);
3010 // In this case the child buffer is open but hidden.
3011 // It therefore should not (MUST NOT) be dirty!
3012 LATTEST(child_buf->isClean());
3013 theBufferList().release(child_buf);
3018 // goto bookmark to update bookmark pit.
3019 // FIXME: we should update only the bookmarks related to this buffer!
3020 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3021 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3022 guiApp->gotoBookmark(i+1, false, false);
3024 if (saveBufferIfNeeded(buf, false)) {
3025 buf.removeAutosaveFile();
3026 theBufferList().release(&buf);
3030 // open all children again to avoid a crash because of dangling
3031 // pointers (bug 6603)
3037 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3039 while (twa == d.currentTabWorkArea()) {
3040 twa->setCurrentIndex(twa->count() - 1);
3042 GuiWorkArea * wa = twa->currentWorkArea();
3043 Buffer & b = wa->bufferView().buffer();
3045 // We only want to close the buffer if the same buffer is not visible
3046 // in another view, and if this is not a child and if we are closing
3047 // a view (not a tabgroup).
3048 bool const close_buffer =
3049 !inOtherView(b) && !b.parent() && closing_;
3051 if (!closeWorkArea(wa, close_buffer))
3058 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3060 if (buf.isClean() || buf.paragraphs().empty())
3063 // Switch to this Buffer.
3069 if (buf.isUnnamed()) {
3070 file = from_utf8(buf.fileName().onlyFileName());
3073 FileName filename = buf.fileName();
3075 file = filename.displayName(30);
3076 exists = filename.exists();
3079 // Bring this window to top before asking questions.
3084 if (hiding && buf.isUnnamed()) {
3085 docstring const text = bformat(_("The document %1$s has not been "
3086 "saved yet.\n\nDo you want to save "
3087 "the document?"), file);
3088 ret = Alert::prompt(_("Save new document?"),
3089 text, 0, 1, _("&Save"), _("&Cancel"));
3093 docstring const text = exists ?
3094 bformat(_("The document %1$s has unsaved changes."
3095 "\n\nDo you want to save the document or "
3096 "discard the changes?"), file) :
3097 bformat(_("The document %1$s has not been saved yet."
3098 "\n\nDo you want to save the document or "
3099 "discard it entirely?"), file);
3100 docstring const title = exists ?
3101 _("Save changed document?") : _("Save document?");
3102 ret = Alert::prompt(title, text, 0, 2,
3103 _("&Save"), _("&Discard"), _("&Cancel"));
3108 if (!saveBuffer(buf))
3112 // If we crash after this we could have no autosave file
3113 // but I guess this is really improbable (Jug).
3114 // Sometimes improbable things happen:
3115 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3116 // buf.removeAutosaveFile();
3118 // revert all changes
3129 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3131 Buffer & buf = wa->bufferView().buffer();
3133 for (int i = 0; i != d.splitter_->count(); ++i) {
3134 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3135 if (wa_ && wa_ != wa)
3138 return inOtherView(buf);
3142 bool GuiView::inOtherView(Buffer & buf)
3144 QList<int> const ids = guiApp->viewIds();
3146 for (int i = 0; i != ids.size(); ++i) {
3150 if (guiApp->view(ids[i]).workArea(buf))
3157 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3159 if (!documentBufferView())
3162 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3163 Buffer * const curbuf = &documentBufferView()->buffer();
3164 int nwa = twa->count();
3165 for (int i = 0; i < nwa; ++i) {
3166 if (&workArea(i)->bufferView().buffer() == curbuf) {
3168 if (np == NEXTBUFFER)
3169 next_index = (i == nwa - 1 ? 0 : i + 1);
3171 next_index = (i == 0 ? nwa - 1 : i - 1);
3173 twa->moveTab(i, next_index);
3175 setBuffer(&workArea(next_index)->bufferView().buffer());
3183 /// make sure the document is saved
3184 static bool ensureBufferClean(Buffer * buffer)
3186 LASSERT(buffer, return false);
3187 if (buffer->isClean() && !buffer->isUnnamed())
3190 docstring const file = buffer->fileName().displayName(30);
3193 if (!buffer->isUnnamed()) {
3194 text = bformat(_("The document %1$s has unsaved "
3195 "changes.\n\nDo you want to save "
3196 "the document?"), file);
3197 title = _("Save changed document?");
3200 text = bformat(_("The document %1$s has not been "
3201 "saved yet.\n\nDo you want to save "
3202 "the document?"), file);
3203 title = _("Save new document?");
3205 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3208 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3210 return buffer->isClean() && !buffer->isUnnamed();
3214 bool GuiView::reloadBuffer(Buffer & buf)
3216 currentBufferView()->cursor().reset();
3217 Buffer::ReadStatus status = buf.reload();
3218 return status == Buffer::ReadSuccess;
3222 void GuiView::checkExternallyModifiedBuffers()
3224 BufferList::iterator bit = theBufferList().begin();
3225 BufferList::iterator const bend = theBufferList().end();
3226 for (; bit != bend; ++bit) {
3227 Buffer * buf = *bit;
3228 if (buf->fileName().exists() && buf->isChecksumModified()) {
3229 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3230 " Reload now? Any local changes will be lost."),
3231 from_utf8(buf->absFileName()));
3232 int const ret = Alert::prompt(_("Reload externally changed document?"),
3233 text, 0, 1, _("&Reload"), _("&Cancel"));
3241 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3243 Buffer * buffer = documentBufferView()
3244 ? &(documentBufferView()->buffer()) : 0;
3246 switch (cmd.action()) {
3247 case LFUN_VC_REGISTER:
3248 if (!buffer || !ensureBufferClean(buffer))
3250 if (!buffer->lyxvc().inUse()) {
3251 if (buffer->lyxvc().registrer()) {
3252 reloadBuffer(*buffer);
3253 dr.clearMessageUpdate();
3258 case LFUN_VC_RENAME:
3259 case LFUN_VC_COPY: {
3260 if (!buffer || !ensureBufferClean(buffer))
3262 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3263 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3264 // Some changes are not yet committed.
3265 // We test here and not in getStatus(), since
3266 // this test is expensive.
3268 LyXVC::CommandResult ret =
3269 buffer->lyxvc().checkIn(log);
3271 if (ret == LyXVC::ErrorCommand ||
3272 ret == LyXVC::VCSuccess)
3273 reloadBuffer(*buffer);
3274 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3275 frontend::Alert::error(
3276 _("Revision control error."),
3277 _("Document could not be checked in."));
3281 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3282 LV_VC_RENAME : LV_VC_COPY;
3283 renameBuffer(*buffer, cmd.argument(), kind);
3288 case LFUN_VC_CHECK_IN:
3289 if (!buffer || !ensureBufferClean(buffer))
3291 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3293 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3295 // Only skip reloading if the checkin was cancelled or
3296 // an error occurred before the real checkin VCS command
3297 // was executed, since the VCS might have changed the
3298 // file even if it could not checkin successfully.
3299 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3300 reloadBuffer(*buffer);
3304 case LFUN_VC_CHECK_OUT:
3305 if (!buffer || !ensureBufferClean(buffer))
3307 if (buffer->lyxvc().inUse()) {
3308 dr.setMessage(buffer->lyxvc().checkOut());
3309 reloadBuffer(*buffer);
3313 case LFUN_VC_LOCKING_TOGGLE:
3314 LASSERT(buffer, return);
3315 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3317 if (buffer->lyxvc().inUse()) {
3318 string res = buffer->lyxvc().lockingToggle();
3320 frontend::Alert::error(_("Revision control error."),
3321 _("Error when setting the locking property."));
3324 reloadBuffer(*buffer);
3329 case LFUN_VC_REVERT:
3330 LASSERT(buffer, return);
3331 if (buffer->lyxvc().revert()) {
3332 reloadBuffer(*buffer);
3333 dr.clearMessageUpdate();
3337 case LFUN_VC_UNDO_LAST:
3338 LASSERT(buffer, return);
3339 buffer->lyxvc().undoLast();
3340 reloadBuffer(*buffer);
3341 dr.clearMessageUpdate();
3344 case LFUN_VC_REPO_UPDATE:
3345 LASSERT(buffer, return);
3346 if (ensureBufferClean(buffer)) {
3347 dr.setMessage(buffer->lyxvc().repoUpdate());
3348 checkExternallyModifiedBuffers();
3352 case LFUN_VC_COMMAND: {
3353 string flag = cmd.getArg(0);
3354 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3357 if (contains(flag, 'M')) {
3358 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3361 string path = cmd.getArg(1);
3362 if (contains(path, "$$p") && buffer)
3363 path = subst(path, "$$p", buffer->filePath());
3364 LYXERR(Debug::LYXVC, "Directory: " << path);
3366 if (!pp.isReadableDirectory()) {
3367 lyxerr << _("Directory is not accessible.") << endl;
3370 support::PathChanger p(pp);
3372 string command = cmd.getArg(2);
3373 if (command.empty())
3376 command = subst(command, "$$i", buffer->absFileName());
3377 command = subst(command, "$$p", buffer->filePath());
3379 command = subst(command, "$$m", to_utf8(message));
3380 LYXERR(Debug::LYXVC, "Command: " << command);
3382 one.startscript(Systemcall::Wait, command);
3386 if (contains(flag, 'I'))
3387 buffer->markDirty();
3388 if (contains(flag, 'R'))
3389 reloadBuffer(*buffer);
3394 case LFUN_VC_COMPARE: {
3395 if (cmd.argument().empty()) {
3396 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3400 string rev1 = cmd.getArg(0);
3405 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3408 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3409 f2 = buffer->absFileName();
3411 string rev2 = cmd.getArg(1);
3415 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3419 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3420 f1 << "\n" << f2 << "\n" );
3421 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3422 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3432 void GuiView::openChildDocument(string const & fname)
3434 LASSERT(documentBufferView(), return);
3435 Buffer & buffer = documentBufferView()->buffer();
3436 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3437 documentBufferView()->saveBookmark(false);
3439 if (theBufferList().exists(filename)) {
3440 child = theBufferList().getBuffer(filename);
3443 message(bformat(_("Opening child document %1$s..."),
3444 makeDisplayPath(filename.absFileName())));
3445 child = loadDocument(filename, false);
3447 // Set the parent name of the child document.
3448 // This makes insertion of citations and references in the child work,
3449 // when the target is in the parent or another child document.
3451 child->setParent(&buffer);
3455 bool GuiView::goToFileRow(string const & argument)
3459 size_t i = argument.find_last_of(' ');
3460 if (i != string::npos) {
3461 file_name = os::internal_path(trim(argument.substr(0, i)));
3462 istringstream is(argument.substr(i + 1));
3467 if (i == string::npos) {
3468 LYXERR0("Wrong argument: " << argument);
3472 string const abstmp = package().temp_dir().absFileName();
3473 string const realtmp = package().temp_dir().realPath();
3474 // We have to use os::path_prefix_is() here, instead of
3475 // simply prefixIs(), because the file name comes from
3476 // an external application and may need case adjustment.
3477 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3478 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3479 // Needed by inverse dvi search. If it is a file
3480 // in tmpdir, call the apropriated function.
3481 // If tmpdir is a symlink, we may have the real
3482 // path passed back, so we correct for that.
3483 if (!prefixIs(file_name, abstmp))
3484 file_name = subst(file_name, realtmp, abstmp);
3485 buf = theBufferList().getBufferFromTmp(file_name);
3487 // Must replace extension of the file to be .lyx
3488 // and get full path
3489 FileName const s = fileSearch(string(),
3490 support::changeExtension(file_name, ".lyx"), "lyx");
3491 // Either change buffer or load the file
3492 if (theBufferList().exists(s))
3493 buf = theBufferList().getBuffer(s);
3494 else if (s.exists()) {
3495 buf = loadDocument(s);
3500 _("File does not exist: %1$s"),
3501 makeDisplayPath(file_name)));
3507 _("No buffer for file: %1$s."),
3508 makeDisplayPath(file_name))
3513 bool success = documentBufferView()->setCursorFromRow(row);
3515 LYXERR(Debug::LATEX,
3516 "setCursorFromRow: invalid position for row " << row);
3517 frontend::Alert::error(_("Inverse Search Failed"),
3518 _("Invalid position requested by inverse search.\n"
3519 "You may need to update the viewed document."));
3525 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3527 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3528 menu->exec(QCursor::pos());
3533 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3535 Buffer::ExportStatus const status = func(format);
3537 // the cloning operation will have produced a clone of the entire set of
3538 // documents, starting from the master. so we must delete those.
3539 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3541 busyBuffers.remove(orig);
3546 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3548 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3549 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3553 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3555 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3556 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3560 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3562 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3563 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3567 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3568 string const & argument,
3569 Buffer const * used_buffer,
3570 docstring const & msg,
3571 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3572 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3573 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3578 string format = argument;
3580 format = used_buffer->params().getDefaultOutputFormat();
3581 processing_format = format;
3583 progress_->clearMessages();
3586 #if EXPORT_in_THREAD
3587 GuiViewPrivate::busyBuffers.insert(used_buffer);
3588 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3589 if (!cloned_buffer) {
3590 Alert::error(_("Export Error"),
3591 _("Error cloning the Buffer."));
3594 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3599 setPreviewFuture(f);
3600 last_export_format = used_buffer->params().bufferFormat();
3603 // We are asynchronous, so we don't know here anything about the success
3606 Buffer::ExportStatus status;
3608 status = (used_buffer->*syncFunc)(format, true);
3609 } else if (previewFunc) {
3610 status = (used_buffer->*previewFunc)(format);
3613 handleExportStatus(gv_, status, format);
3615 return (status == Buffer::ExportSuccess
3616 || status == Buffer::PreviewSuccess);
3620 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3622 BufferView * bv = currentBufferView();
3623 LASSERT(bv, return);
3625 // Let the current BufferView dispatch its own actions.
3626 bv->dispatch(cmd, dr);
3627 if (dr.dispatched())
3630 // Try with the document BufferView dispatch if any.
3631 BufferView * doc_bv = documentBufferView();
3632 if (doc_bv && doc_bv != bv) {
3633 doc_bv->dispatch(cmd, dr);
3634 if (dr.dispatched())
3638 // Then let the current Cursor dispatch its own actions.
3639 bv->cursor().dispatch(cmd);
3641 // update completion. We do it here and not in
3642 // processKeySym to avoid another redraw just for a
3643 // changed inline completion
3644 if (cmd.origin() == FuncRequest::KEYBOARD) {
3645 if (cmd.action() == LFUN_SELF_INSERT
3646 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3647 updateCompletion(bv->cursor(), true, true);
3648 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3649 updateCompletion(bv->cursor(), false, true);
3651 updateCompletion(bv->cursor(), false, false);
3654 dr = bv->cursor().result();
3658 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3660 BufferView * bv = currentBufferView();
3661 // By default we won't need any update.
3662 dr.screenUpdate(Update::None);
3663 // assume cmd will be dispatched
3664 dr.dispatched(true);
3666 Buffer * doc_buffer = documentBufferView()
3667 ? &(documentBufferView()->buffer()) : 0;
3669 if (cmd.origin() == FuncRequest::TOC) {
3670 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3671 // FIXME: do we need to pass a DispatchResult object here?
3672 toc->doDispatch(bv->cursor(), cmd);
3676 string const argument = to_utf8(cmd.argument());
3678 switch(cmd.action()) {
3679 case LFUN_BUFFER_CHILD_OPEN:
3680 openChildDocument(to_utf8(cmd.argument()));
3683 case LFUN_BUFFER_IMPORT:
3684 importDocument(to_utf8(cmd.argument()));
3687 case LFUN_BUFFER_EXPORT: {
3690 // GCC only sees strfwd.h when building merged
3691 if (::lyx::operator==(cmd.argument(), "custom")) {
3692 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3696 string const dest = cmd.getArg(1);
3697 FileName target_dir;
3698 if (!dest.empty() && FileName::isAbsolute(dest))
3699 target_dir = FileName(support::onlyPath(dest));
3701 target_dir = doc_buffer->fileName().onlyPath();
3703 string const format = (argument.empty() || argument == "default") ?
3704 doc_buffer->params().getDefaultOutputFormat() : argument;
3706 if ((dest.empty() && doc_buffer->isUnnamed())
3707 || !target_dir.isDirWritable()) {
3708 exportBufferAs(*doc_buffer, from_utf8(format));
3711 /* TODO/Review: Is it a problem to also export the children?
3712 See the update_unincluded flag */
3713 d.asyncBufferProcessing(format,
3716 &GuiViewPrivate::exportAndDestroy,
3719 // TODO Inform user about success
3723 case LFUN_BUFFER_EXPORT_AS: {
3724 LASSERT(doc_buffer, break);
3725 docstring f = cmd.argument();
3727 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3728 exportBufferAs(*doc_buffer, f);
3732 case LFUN_BUFFER_UPDATE: {
3733 d.asyncBufferProcessing(argument,
3736 &GuiViewPrivate::compileAndDestroy,
3741 case LFUN_BUFFER_VIEW: {
3742 d.asyncBufferProcessing(argument,
3744 _("Previewing ..."),
3745 &GuiViewPrivate::previewAndDestroy,
3750 case LFUN_MASTER_BUFFER_UPDATE: {
3751 d.asyncBufferProcessing(argument,
3752 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3754 &GuiViewPrivate::compileAndDestroy,
3759 case LFUN_MASTER_BUFFER_VIEW: {
3760 d.asyncBufferProcessing(argument,
3761 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3763 &GuiViewPrivate::previewAndDestroy,
3764 0, &Buffer::preview);
3767 case LFUN_BUFFER_SWITCH: {
3768 string const file_name = to_utf8(cmd.argument());
3769 if (!FileName::isAbsolute(file_name)) {
3771 dr.setMessage(_("Absolute filename expected."));
3775 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3778 dr.setMessage(_("Document not loaded"));
3782 // Do we open or switch to the buffer in this view ?
3783 if (workArea(*buffer)
3784 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3789 // Look for the buffer in other views
3790 QList<int> const ids = guiApp->viewIds();
3792 for (; i != ids.size(); ++i) {
3793 GuiView & gv = guiApp->view(ids[i]);
3794 if (gv.workArea(*buffer)) {
3796 gv.activateWindow();
3798 gv.setBuffer(buffer);
3803 // If necessary, open a new window as a last resort
3804 if (i == ids.size()) {
3805 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3811 case LFUN_BUFFER_NEXT:
3812 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3815 case LFUN_BUFFER_MOVE_NEXT:
3816 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3819 case LFUN_BUFFER_PREVIOUS:
3820 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3823 case LFUN_BUFFER_MOVE_PREVIOUS:
3824 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3827 case LFUN_BUFFER_CHKTEX:
3828 LASSERT(doc_buffer, break);
3829 doc_buffer->runChktex();
3832 case LFUN_COMMAND_EXECUTE: {
3833 command_execute_ = true;
3834 minibuffer_focus_ = true;
3837 case LFUN_DROP_LAYOUTS_CHOICE:
3838 d.layout_->showPopup();
3841 case LFUN_MENU_OPEN:
3842 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3843 menu->exec(QCursor::pos());
3846 case LFUN_FILE_INSERT:
3847 insertLyXFile(cmd.argument());
3850 case LFUN_FILE_INSERT_PLAINTEXT:
3851 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3852 string const fname = to_utf8(cmd.argument());
3853 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3854 dr.setMessage(_("Absolute filename expected."));
3858 FileName filename(fname);
3859 if (fname.empty()) {
3860 FileDialog dlg(qt_("Select file to insert"));
3862 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3863 QStringList(qt_("All Files (*)")));
3865 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3866 dr.setMessage(_("Canceled."));
3870 filename.set(fromqstr(result.second));
3874 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3875 bv->dispatch(new_cmd, dr);
3880 case LFUN_BUFFER_RELOAD: {
3881 LASSERT(doc_buffer, break);
3884 if (!doc_buffer->isClean()) {
3885 docstring const file =
3886 makeDisplayPath(doc_buffer->absFileName(), 20);
3887 if (doc_buffer->notifiesExternalModification()) {
3888 docstring text = _("The current version will be lost. "
3889 "Are you sure you want to load the version on disk "
3890 "of the document %1$s?");
3891 ret = Alert::prompt(_("Reload saved document?"),
3892 bformat(text, file), 1, 1,
3893 _("&Reload"), _("&Cancel"));
3895 docstring text = _("Any changes will be lost. "
3896 "Are you sure you want to revert to the saved version "
3897 "of the document %1$s?");
3898 ret = Alert::prompt(_("Revert to saved document?"),
3899 bformat(text, file), 1, 1,
3900 _("&Revert"), _("&Cancel"));
3905 doc_buffer->markClean();
3906 reloadBuffer(*doc_buffer);
3907 dr.forceBufferUpdate();
3912 case LFUN_BUFFER_WRITE:
3913 LASSERT(doc_buffer, break);
3914 saveBuffer(*doc_buffer);
3917 case LFUN_BUFFER_WRITE_AS:
3918 LASSERT(doc_buffer, break);
3919 renameBuffer(*doc_buffer, cmd.argument());
3922 case LFUN_BUFFER_WRITE_ALL: {
3923 Buffer * first = theBufferList().first();
3926 message(_("Saving all documents..."));
3927 // We cannot use a for loop as the buffer list cycles.
3930 if (!b->isClean()) {
3932 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3934 b = theBufferList().next(b);
3935 } while (b != first);
3936 dr.setMessage(_("All documents saved."));
3940 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3941 LASSERT(doc_buffer, break);
3942 doc_buffer->clearExternalModification();
3945 case LFUN_BUFFER_CLOSE:
3949 case LFUN_BUFFER_CLOSE_ALL:
3953 case LFUN_DEVEL_MODE_TOGGLE:
3954 devel_mode_ = !devel_mode_;
3956 dr.setMessage(_("Developer mode is now enabled."));
3958 dr.setMessage(_("Developer mode is now disabled."));
3961 case LFUN_TOOLBAR_TOGGLE: {
3962 string const name = cmd.getArg(0);
3963 if (GuiToolbar * t = toolbar(name))
3968 case LFUN_TOOLBAR_MOVABLE: {
3969 string const name = cmd.getArg(0);
3971 // toggle (all) toolbars movablility
3972 toolbarsMovable_ = !toolbarsMovable_;
3973 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3974 GuiToolbar * tb = toolbar(ti.name);
3975 if (tb && tb->isMovable() != toolbarsMovable_)
3976 // toggle toolbar movablity if it does not fit lock
3977 // (all) toolbars positions state silent = true, since
3978 // status bar notifications are slow
3981 if (toolbarsMovable_)
3982 dr.setMessage(_("Toolbars unlocked."));
3984 dr.setMessage(_("Toolbars locked."));
3985 } else if (GuiToolbar * t = toolbar(name)) {
3986 // toggle current toolbar movablity
3988 // update lock (all) toolbars positions
3989 updateLockToolbars();
3994 case LFUN_ICON_SIZE: {
3995 QSize size = d.iconSize(cmd.argument());
3997 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
3998 size.width(), size.height()));
4002 case LFUN_DIALOG_UPDATE: {
4003 string const name = to_utf8(cmd.argument());
4004 if (name == "prefs" || name == "document")
4005 updateDialog(name, string());
4006 else if (name == "paragraph")
4007 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4008 else if (currentBufferView()) {
4009 Inset * inset = currentBufferView()->editedInset(name);
4010 // Can only update a dialog connected to an existing inset
4012 // FIXME: get rid of this indirection; GuiView ask the inset
4013 // if he is kind enough to update itself...
4014 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4015 //FIXME: pass DispatchResult here?
4016 inset->dispatch(currentBufferView()->cursor(), fr);
4022 case LFUN_DIALOG_TOGGLE: {
4023 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4024 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4025 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4029 case LFUN_DIALOG_DISCONNECT_INSET:
4030 disconnectDialog(to_utf8(cmd.argument()));
4033 case LFUN_DIALOG_HIDE: {
4034 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4038 case LFUN_DIALOG_SHOW: {
4039 string const name = cmd.getArg(0);
4040 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
4042 if (name == "character") {
4043 data = freefont2string();
4045 showDialog("character", data);
4046 } else if (name == "latexlog") {
4047 // getStatus checks that
4048 LATTEST(doc_buffer);
4049 Buffer::LogType type;
4050 string const logfile = doc_buffer->logName(&type);
4052 case Buffer::latexlog:
4055 case Buffer::buildlog:
4059 data += Lexer::quoteString(logfile);
4060 showDialog("log", data);
4061 } else if (name == "vclog") {
4062 // getStatus checks that
4063 LATTEST(doc_buffer);
4064 string const data = "vc " +
4065 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4066 showDialog("log", data);
4067 } else if (name == "symbols") {
4068 data = bv->cursor().getEncoding()->name();
4070 showDialog("symbols", data);
4072 } else if (name == "prefs" && isFullScreen()) {
4073 lfunUiToggle("fullscreen");
4074 showDialog("prefs", data);
4076 showDialog(name, data);
4081 dr.setMessage(cmd.argument());
4084 case LFUN_UI_TOGGLE: {
4085 string arg = cmd.getArg(0);
4086 if (!lfunUiToggle(arg)) {
4087 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4088 dr.setMessage(bformat(msg, from_utf8(arg)));
4090 // Make sure the keyboard focus stays in the work area.
4095 case LFUN_VIEW_SPLIT: {
4096 LASSERT(doc_buffer, break);
4097 string const orientation = cmd.getArg(0);
4098 d.splitter_->setOrientation(orientation == "vertical"
4099 ? Qt::Vertical : Qt::Horizontal);
4100 TabWorkArea * twa = addTabWorkArea();
4101 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4102 setCurrentWorkArea(wa);
4105 case LFUN_TAB_GROUP_CLOSE:
4106 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4107 closeTabWorkArea(twa);
4108 d.current_work_area_ = 0;
4109 twa = d.currentTabWorkArea();
4110 // Switch to the next GuiWorkArea in the found TabWorkArea.
4112 // Make sure the work area is up to date.
4113 setCurrentWorkArea(twa->currentWorkArea());
4115 setCurrentWorkArea(0);
4120 case LFUN_VIEW_CLOSE:
4121 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4122 closeWorkArea(twa->currentWorkArea());
4123 d.current_work_area_ = 0;
4124 twa = d.currentTabWorkArea();
4125 // Switch to the next GuiWorkArea in the found TabWorkArea.
4127 // Make sure the work area is up to date.
4128 setCurrentWorkArea(twa->currentWorkArea());
4130 setCurrentWorkArea(0);
4135 case LFUN_COMPLETION_INLINE:
4136 if (d.current_work_area_)
4137 d.current_work_area_->completer().showInline();
4140 case LFUN_COMPLETION_POPUP:
4141 if (d.current_work_area_)
4142 d.current_work_area_->completer().showPopup();
4147 if (d.current_work_area_)
4148 d.current_work_area_->completer().tab();
4151 case LFUN_COMPLETION_CANCEL:
4152 if (d.current_work_area_) {
4153 if (d.current_work_area_->completer().popupVisible())
4154 d.current_work_area_->completer().hidePopup();
4156 d.current_work_area_->completer().hideInline();
4160 case LFUN_COMPLETION_ACCEPT:
4161 if (d.current_work_area_)
4162 d.current_work_area_->completer().activate();
4165 case LFUN_BUFFER_ZOOM_IN:
4166 case LFUN_BUFFER_ZOOM_OUT:
4167 case LFUN_BUFFER_ZOOM: {
4168 if (cmd.argument().empty()) {
4169 if (cmd.action() == LFUN_BUFFER_ZOOM)
4171 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4176 if (cmd.action() == LFUN_BUFFER_ZOOM)
4177 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4178 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4179 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4181 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4184 // Actual zoom value: default zoom + fractional extra value
4185 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4186 if (zoom < static_cast<int>(zoom_min_))
4189 lyxrc.currentZoom = zoom;
4191 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4192 lyxrc.currentZoom, lyxrc.defaultZoom));
4194 // The global QPixmapCache is used in GuiPainter to cache text
4195 // painting so we must reset it.
4196 QPixmapCache::clear();
4197 guiApp->fontLoader().update();
4198 dr.screenUpdate(Update::Force | Update::FitCursor);
4202 case LFUN_VC_REGISTER:
4203 case LFUN_VC_RENAME:
4205 case LFUN_VC_CHECK_IN:
4206 case LFUN_VC_CHECK_OUT:
4207 case LFUN_VC_REPO_UPDATE:
4208 case LFUN_VC_LOCKING_TOGGLE:
4209 case LFUN_VC_REVERT:
4210 case LFUN_VC_UNDO_LAST:
4211 case LFUN_VC_COMMAND:
4212 case LFUN_VC_COMPARE:
4213 dispatchVC(cmd, dr);
4216 case LFUN_SERVER_GOTO_FILE_ROW:
4217 if(goToFileRow(to_utf8(cmd.argument())))
4218 dr.screenUpdate(Update::Force | Update::FitCursor);
4221 case LFUN_LYX_ACTIVATE:
4225 case LFUN_FORWARD_SEARCH: {
4226 // it seems safe to assume we have a document buffer, since
4227 // getStatus wants one.
4228 LATTEST(doc_buffer);
4229 Buffer const * doc_master = doc_buffer->masterBuffer();
4230 FileName const path(doc_master->temppath());
4231 string const texname = doc_master->isChild(doc_buffer)
4232 ? DocFileName(changeExtension(
4233 doc_buffer->absFileName(),
4234 "tex")).mangledFileName()
4235 : doc_buffer->latexName();
4236 string const fulltexname =
4237 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4238 string const mastername =
4239 removeExtension(doc_master->latexName());
4240 FileName const dviname(addName(path.absFileName(),
4241 addExtension(mastername, "dvi")));
4242 FileName const pdfname(addName(path.absFileName(),
4243 addExtension(mastername, "pdf")));
4244 bool const have_dvi = dviname.exists();
4245 bool const have_pdf = pdfname.exists();
4246 if (!have_dvi && !have_pdf) {
4247 dr.setMessage(_("Please, preview the document first."));
4250 string outname = dviname.onlyFileName();
4251 string command = lyxrc.forward_search_dvi;
4252 if (!have_dvi || (have_pdf &&
4253 pdfname.lastModified() > dviname.lastModified())) {
4254 outname = pdfname.onlyFileName();
4255 command = lyxrc.forward_search_pdf;
4258 DocIterator cur = bv->cursor();
4259 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4260 LYXERR(Debug::ACTION, "Forward search: row:" << row
4262 if (row == -1 || command.empty()) {
4263 dr.setMessage(_("Couldn't proceed."));
4266 string texrow = convert<string>(row);
4268 command = subst(command, "$$n", texrow);
4269 command = subst(command, "$$f", fulltexname);
4270 command = subst(command, "$$t", texname);
4271 command = subst(command, "$$o", outname);
4273 PathChanger p(path);
4275 one.startscript(Systemcall::DontWait, command);
4279 case LFUN_SPELLING_CONTINUOUSLY:
4280 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4281 dr.screenUpdate(Update::Force);
4285 // The LFUN must be for one of BufferView, Buffer or Cursor;
4287 dispatchToBufferView(cmd, dr);
4291 // Part of automatic menu appearance feature.
4292 if (isFullScreen()) {
4293 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4297 // Need to update bv because many LFUNs here might have destroyed it
4298 bv = currentBufferView();
4300 // Clear non-empty selections
4301 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4303 Cursor & cur = bv->cursor();
4304 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4305 cur.clearSelection();
4311 bool GuiView::lfunUiToggle(string const & ui_component)
4313 if (ui_component == "scrollbar") {
4314 // hide() is of no help
4315 if (d.current_work_area_->verticalScrollBarPolicy() ==
4316 Qt::ScrollBarAlwaysOff)
4318 d.current_work_area_->setVerticalScrollBarPolicy(
4319 Qt::ScrollBarAsNeeded);
4321 d.current_work_area_->setVerticalScrollBarPolicy(
4322 Qt::ScrollBarAlwaysOff);
4323 } else if (ui_component == "statusbar") {
4324 statusBar()->setVisible(!statusBar()->isVisible());
4325 } else if (ui_component == "menubar") {
4326 menuBar()->setVisible(!menuBar()->isVisible());
4328 if (ui_component == "frame") {
4330 getContentsMargins(&l, &t, &r, &b);
4331 //are the frames in default state?
4332 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4334 setContentsMargins(-2, -2, -2, -2);
4336 setContentsMargins(0, 0, 0, 0);
4339 if (ui_component == "fullscreen") {
4347 void GuiView::toggleFullScreen()
4349 if (isFullScreen()) {
4350 for (int i = 0; i != d.splitter_->count(); ++i)
4351 d.tabWorkArea(i)->setFullScreen(false);
4352 setContentsMargins(0, 0, 0, 0);
4353 setWindowState(windowState() ^ Qt::WindowFullScreen);
4356 statusBar()->show();
4359 hideDialogs("prefs", 0);
4360 for (int i = 0; i != d.splitter_->count(); ++i)
4361 d.tabWorkArea(i)->setFullScreen(true);
4362 setContentsMargins(-2, -2, -2, -2);
4364 setWindowState(windowState() ^ Qt::WindowFullScreen);
4365 if (lyxrc.full_screen_statusbar)
4366 statusBar()->hide();
4367 if (lyxrc.full_screen_menubar)
4369 if (lyxrc.full_screen_toolbars) {
4370 ToolbarMap::iterator end = d.toolbars_.end();
4371 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4376 // give dialogs like the TOC a chance to adapt
4381 Buffer const * GuiView::updateInset(Inset const * inset)
4386 Buffer const * inset_buffer = &(inset->buffer());
4388 for (int i = 0; i != d.splitter_->count(); ++i) {
4389 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4392 Buffer const * buffer = &(wa->bufferView().buffer());
4393 if (inset_buffer == buffer)
4394 wa->scheduleRedraw(true);
4396 return inset_buffer;
4400 void GuiView::restartCaret()
4402 /* When we move around, or type, it's nice to be able to see
4403 * the caret immediately after the keypress.
4405 if (d.current_work_area_)
4406 d.current_work_area_->startBlinkingCaret();
4408 // Take this occasion to update the other GUI elements.
4414 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4416 if (d.current_work_area_)
4417 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4422 // This list should be kept in sync with the list of insets in
4423 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4424 // dialog should have the same name as the inset.
4425 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4426 // docs in LyXAction.cpp.
4428 char const * const dialognames[] = {
4430 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4431 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4432 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4433 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4434 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4435 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4436 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4437 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4439 char const * const * const end_dialognames =
4440 dialognames + (sizeof(dialognames) / sizeof(char *));
4444 cmpCStr(char const * name) : name_(name) {}
4445 bool operator()(char const * other) {
4446 return strcmp(other, name_) == 0;
4453 bool isValidName(string const & name)
4455 return find_if(dialognames, end_dialognames,
4456 cmpCStr(name.c_str())) != end_dialognames;
4462 void GuiView::resetDialogs()
4464 // Make sure that no LFUN uses any GuiView.
4465 guiApp->setCurrentView(0);
4469 constructToolbars();
4470 guiApp->menus().fillMenuBar(menuBar(), this, false);
4471 d.layout_->updateContents(true);
4472 // Now update controls with current buffer.
4473 guiApp->setCurrentView(this);
4479 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4481 if (!isValidName(name))
4484 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4486 if (it != d.dialogs_.end()) {
4488 it->second->hideView();
4489 return it->second.get();
4492 Dialog * dialog = build(name);
4493 d.dialogs_[name].reset(dialog);
4494 if (lyxrc.allow_geometry_session)
4495 dialog->restoreSession();
4502 void GuiView::showDialog(string const & name, string const & data,
4505 triggerShowDialog(toqstr(name), toqstr(data), inset);
4509 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4515 const string name = fromqstr(qname);
4516 const string data = fromqstr(qdata);
4520 Dialog * dialog = findOrBuild(name, false);
4522 bool const visible = dialog->isVisibleView();
4523 dialog->showData(data);
4524 if (inset && currentBufferView())
4525 currentBufferView()->editInset(name, inset);
4526 // We only set the focus to the new dialog if it was not yet
4527 // visible in order not to change the existing previous behaviour
4529 // activateWindow is needed for floating dockviews
4530 dialog->asQWidget()->raise();
4531 dialog->asQWidget()->activateWindow();
4532 dialog->asQWidget()->setFocus();
4536 catch (ExceptionMessage const & ex) {
4544 bool GuiView::isDialogVisible(string const & name) const
4546 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4547 if (it == d.dialogs_.end())
4549 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4553 void GuiView::hideDialog(string const & name, Inset * inset)
4555 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4556 if (it == d.dialogs_.end())
4560 if (!currentBufferView())
4562 if (inset != currentBufferView()->editedInset(name))
4566 Dialog * const dialog = it->second.get();
4567 if (dialog->isVisibleView())
4569 if (currentBufferView())
4570 currentBufferView()->editInset(name, 0);
4574 void GuiView::disconnectDialog(string const & name)
4576 if (!isValidName(name))
4578 if (currentBufferView())
4579 currentBufferView()->editInset(name, 0);
4583 void GuiView::hideAll() const
4585 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4586 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4588 for(; it != end; ++it)
4589 it->second->hideView();
4593 void GuiView::updateDialogs()
4595 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4596 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4598 for(; it != end; ++it) {
4599 Dialog * dialog = it->second.get();
4601 if (dialog->needBufferOpen() && !documentBufferView())
4602 hideDialog(fromqstr(dialog->name()), 0);
4603 else if (dialog->isVisibleView())
4604 dialog->checkStatus();
4611 Dialog * createDialog(GuiView & lv, string const & name);
4613 // will be replaced by a proper factory...
4614 Dialog * createGuiAbout(GuiView & lv);
4615 Dialog * createGuiBibtex(GuiView & lv);
4616 Dialog * createGuiChanges(GuiView & lv);
4617 Dialog * createGuiCharacter(GuiView & lv);
4618 Dialog * createGuiCitation(GuiView & lv);
4619 Dialog * createGuiCompare(GuiView & lv);
4620 Dialog * createGuiCompareHistory(GuiView & lv);
4621 Dialog * createGuiDelimiter(GuiView & lv);
4622 Dialog * createGuiDocument(GuiView & lv);
4623 Dialog * createGuiErrorList(GuiView & lv);
4624 Dialog * createGuiExternal(GuiView & lv);
4625 Dialog * createGuiGraphics(GuiView & lv);
4626 Dialog * createGuiInclude(GuiView & lv);
4627 Dialog * createGuiIndex(GuiView & lv);
4628 Dialog * createGuiListings(GuiView & lv);
4629 Dialog * createGuiLog(GuiView & lv);
4630 Dialog * createGuiMathMatrix(GuiView & lv);
4631 Dialog * createGuiNote(GuiView & lv);
4632 Dialog * createGuiParagraph(GuiView & lv);
4633 Dialog * createGuiPhantom(GuiView & lv);
4634 Dialog * createGuiPreferences(GuiView & lv);
4635 Dialog * createGuiPrint(GuiView & lv);
4636 Dialog * createGuiPrintindex(GuiView & lv);
4637 Dialog * createGuiRef(GuiView & lv);
4638 Dialog * createGuiSearch(GuiView & lv);
4639 Dialog * createGuiSearchAdv(GuiView & lv);
4640 Dialog * createGuiSendTo(GuiView & lv);
4641 Dialog * createGuiShowFile(GuiView & lv);
4642 Dialog * createGuiSpellchecker(GuiView & lv);
4643 Dialog * createGuiSymbols(GuiView & lv);
4644 Dialog * createGuiTabularCreate(GuiView & lv);
4645 Dialog * createGuiTexInfo(GuiView & lv);
4646 Dialog * createGuiToc(GuiView & lv);
4647 Dialog * createGuiThesaurus(GuiView & lv);
4648 Dialog * createGuiViewSource(GuiView & lv);
4649 Dialog * createGuiWrap(GuiView & lv);
4650 Dialog * createGuiProgressView(GuiView & lv);
4654 Dialog * GuiView::build(string const & name)
4656 LASSERT(isValidName(name), return 0);
4658 Dialog * dialog = createDialog(*this, name);
4662 if (name == "aboutlyx")
4663 return createGuiAbout(*this);
4664 if (name == "bibtex")
4665 return createGuiBibtex(*this);
4666 if (name == "changes")
4667 return createGuiChanges(*this);
4668 if (name == "character")
4669 return createGuiCharacter(*this);
4670 if (name == "citation")
4671 return createGuiCitation(*this);
4672 if (name == "compare")
4673 return createGuiCompare(*this);
4674 if (name == "comparehistory")
4675 return createGuiCompareHistory(*this);
4676 if (name == "document")
4677 return createGuiDocument(*this);
4678 if (name == "errorlist")
4679 return createGuiErrorList(*this);
4680 if (name == "external")
4681 return createGuiExternal(*this);
4683 return createGuiShowFile(*this);
4684 if (name == "findreplace")
4685 return createGuiSearch(*this);
4686 if (name == "findreplaceadv")
4687 return createGuiSearchAdv(*this);
4688 if (name == "graphics")
4689 return createGuiGraphics(*this);
4690 if (name == "include")
4691 return createGuiInclude(*this);
4692 if (name == "index")
4693 return createGuiIndex(*this);
4694 if (name == "index_print")
4695 return createGuiPrintindex(*this);
4696 if (name == "listings")
4697 return createGuiListings(*this);
4699 return createGuiLog(*this);
4700 if (name == "mathdelimiter")
4701 return createGuiDelimiter(*this);
4702 if (name == "mathmatrix")
4703 return createGuiMathMatrix(*this);
4705 return createGuiNote(*this);
4706 if (name == "paragraph")
4707 return createGuiParagraph(*this);
4708 if (name == "phantom")
4709 return createGuiPhantom(*this);
4710 if (name == "prefs")
4711 return createGuiPreferences(*this);
4713 return createGuiRef(*this);
4714 if (name == "sendto")
4715 return createGuiSendTo(*this);
4716 if (name == "spellchecker")
4717 return createGuiSpellchecker(*this);
4718 if (name == "symbols")
4719 return createGuiSymbols(*this);
4720 if (name == "tabularcreate")
4721 return createGuiTabularCreate(*this);
4722 if (name == "texinfo")
4723 return createGuiTexInfo(*this);
4724 if (name == "thesaurus")
4725 return createGuiThesaurus(*this);
4727 return createGuiToc(*this);
4728 if (name == "view-source")
4729 return createGuiViewSource(*this);
4731 return createGuiWrap(*this);
4732 if (name == "progress")
4733 return createGuiProgressView(*this);
4739 SEMenu::SEMenu(QWidget * parent)
4741 QAction * action = addAction(qt_("Disable Shell Escape"));
4742 connect(action, SIGNAL(triggered()),
4743 parent, SLOT(disableShellEscape()));
4747 } // namespace frontend
4750 #include "moc_GuiView.cpp"