3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
56 #include "LyXAction.h"
60 #include "Paragraph.h"
61 #include "SpellChecker.h"
64 #include "TextClass.h"
69 #include "support/convert.h"
70 #include "support/debug.h"
71 #include "support/ExceptionMessage.h"
72 #include "support/FileName.h"
73 #include "support/filetools.h"
74 #include "support/gettext.h"
75 #include "support/filetools.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
90 #include <QDesktopWidget>
91 #include <QDragEnterEvent>
94 #include <QFutureWatcher>
103 #include <QPixmapCache>
105 #include <QPushButton>
106 #include <QScrollBar>
108 #include <QShowEvent>
110 #include <QStackedWidget>
111 #include <QStatusBar>
112 #include <QSvgRenderer>
113 #include <QtConcurrentRun>
121 // sync with GuiAlert.cpp
122 #define EXPORT_in_THREAD 1
125 #include "support/bind.h"
129 #ifdef HAVE_SYS_TIME_H
130 # include <sys/time.h>
138 using namespace lyx::support;
142 using support::addExtension;
143 using support::changeExtension;
144 using support::removeExtension;
150 class BackgroundWidget : public QWidget
153 BackgroundWidget(int width, int height)
154 : width_(width), height_(height)
156 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
157 if (!lyxrc.show_banner)
159 /// The text to be written on top of the pixmap
160 QString const text = lyx_version ?
161 qt_("version ") + lyx_version : qt_("unknown version");
162 #if QT_VERSION >= 0x050000
163 QString imagedir = "images/";
164 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
165 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
166 if (svgRenderer.isValid()) {
167 splash_ = QPixmap(splashSize());
168 QPainter painter(&splash_);
169 svgRenderer.render(&painter);
170 splash_.setDevicePixelRatio(pixelRatio());
172 splash_ = getPixmap("images/", "banner", "png");
175 splash_ = getPixmap("images/", "banner", "svgz,png");
178 QPainter pain(&splash_);
179 pain.setPen(QColor(0, 0, 0));
180 qreal const fsize = fontSize();
181 QPointF const position = textPosition();
183 "widget pixel ratio: " << pixelRatio() <<
184 " splash pixel ratio: " << splashPixelRatio() <<
185 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
187 // The font used to display the version info
188 font.setStyleHint(QFont::SansSerif);
189 font.setWeight(QFont::Bold);
190 font.setPointSizeF(fsize);
192 pain.drawText(position, text);
193 setFocusPolicy(Qt::StrongFocus);
196 void paintEvent(QPaintEvent *)
198 int const w = width_;
199 int const h = height_;
200 int const x = (width() - w) / 2;
201 int const y = (height() - h) / 2;
203 "widget pixel ratio: " << pixelRatio() <<
204 " splash pixel ratio: " << splashPixelRatio() <<
205 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
207 pain.drawPixmap(x, y, w, h, splash_);
210 void keyPressEvent(QKeyEvent * ev)
213 setKeySymbol(&sym, ev);
215 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
227 /// Current ratio between physical pixels and device-independent pixels
228 double pixelRatio() const {
229 #if QT_VERSION >= 0x050000
230 return qt_scale_factor * devicePixelRatio();
236 qreal fontSize() const {
237 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
240 QPointF textPosition() const {
241 return QPointF(width_/2 - 18, height_/2 + 45);
244 QSize splashSize() const {
246 static_cast<unsigned int>(width_ * pixelRatio()),
247 static_cast<unsigned int>(height_ * pixelRatio()));
250 /// Ratio between physical pixels and device-independent pixels of splash image
251 double splashPixelRatio() const {
252 #if QT_VERSION >= 0x050000
253 return splash_.devicePixelRatio();
261 /// Toolbar store providing access to individual toolbars by name.
262 typedef map<string, GuiToolbar *> ToolbarMap;
264 typedef shared_ptr<Dialog> DialogPtr;
269 class GuiView::GuiViewPrivate
272 GuiViewPrivate(GuiViewPrivate const &);
273 void operator=(GuiViewPrivate const &);
275 GuiViewPrivate(GuiView * gv)
276 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
277 layout_(0), autosave_timeout_(5000),
280 // hardcode here the platform specific icon size
281 smallIconSize = 16; // scaling problems
282 normalIconSize = 20; // ok, default if iconsize.png is missing
283 bigIconSize = 26; // better for some math icons
284 hugeIconSize = 32; // better for hires displays
287 // if it exists, use width of iconsize.png as normal size
288 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
289 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
291 QImage image(toqstr(fn.absFileName()));
292 if (image.width() < int(smallIconSize))
293 normalIconSize = smallIconSize;
294 else if (image.width() > int(giantIconSize))
295 normalIconSize = giantIconSize;
297 normalIconSize = image.width();
300 splitter_ = new QSplitter;
301 bg_widget_ = new BackgroundWidget(400, 250);
302 stack_widget_ = new QStackedWidget;
303 stack_widget_->addWidget(bg_widget_);
304 stack_widget_->addWidget(splitter_);
307 // TODO cleanup, remove the singleton, handle multiple Windows?
308 progress_ = ProgressInterface::instance();
309 if (!dynamic_cast<GuiProgress*>(progress_)) {
310 progress_ = new GuiProgress; // TODO who deletes it
311 ProgressInterface::setInstance(progress_);
314 dynamic_cast<GuiProgress*>(progress_),
315 SIGNAL(updateStatusBarMessage(QString const&)),
316 gv, SLOT(updateStatusBarMessage(QString const&)));
318 dynamic_cast<GuiProgress*>(progress_),
319 SIGNAL(clearMessageText()),
320 gv, SLOT(clearMessageText()));
327 delete stack_widget_;
332 stack_widget_->setCurrentWidget(bg_widget_);
333 bg_widget_->setUpdatesEnabled(true);
334 bg_widget_->setFocus();
337 int tabWorkAreaCount()
339 return splitter_->count();
342 TabWorkArea * tabWorkArea(int i)
344 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
347 TabWorkArea * currentTabWorkArea()
349 int areas = tabWorkAreaCount();
351 // The first TabWorkArea is always the first one, if any.
352 return tabWorkArea(0);
354 for (int i = 0; i != areas; ++i) {
355 TabWorkArea * twa = tabWorkArea(i);
356 if (current_main_work_area_ == twa->currentWorkArea())
360 // None has the focus so we just take the first one.
361 return tabWorkArea(0);
364 int countWorkAreasOf(Buffer & buf)
366 int areas = tabWorkAreaCount();
368 for (int i = 0; i != areas; ++i) {
369 TabWorkArea * twa = tabWorkArea(i);
370 if (twa->workArea(buf))
376 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
378 if (processing_thread_watcher_.isRunning()) {
379 // we prefer to cancel this preview in order to keep a snappy
383 processing_thread_watcher_.setFuture(f);
386 QSize iconSize(docstring const & icon_size)
389 if (icon_size == "small")
390 size = smallIconSize;
391 else if (icon_size == "normal")
392 size = normalIconSize;
393 else if (icon_size == "big")
395 else if (icon_size == "huge")
397 else if (icon_size == "giant")
398 size = giantIconSize;
400 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
402 if (size < smallIconSize)
403 size = smallIconSize;
405 return QSize(size, size);
408 QSize iconSize(QString const & icon_size)
410 return iconSize(qstring_to_ucs4(icon_size));
413 string & iconSize(QSize const & qsize)
415 LATTEST(qsize.width() == qsize.height());
417 static string icon_size;
419 unsigned int size = qsize.width();
421 if (size < smallIconSize)
422 size = smallIconSize;
424 if (size == smallIconSize)
426 else if (size == normalIconSize)
427 icon_size = "normal";
428 else if (size == bigIconSize)
430 else if (size == hugeIconSize)
432 else if (size == giantIconSize)
435 icon_size = convert<string>(size);
442 GuiWorkArea * current_work_area_;
443 GuiWorkArea * current_main_work_area_;
444 QSplitter * splitter_;
445 QStackedWidget * stack_widget_;
446 BackgroundWidget * bg_widget_;
448 ToolbarMap toolbars_;
449 ProgressInterface* progress_;
450 /// The main layout box.
452 * \warning Don't Delete! The layout box is actually owned by
453 * whichever toolbar contains it. All the GuiView class needs is a
454 * means of accessing it.
456 * FIXME: replace that with a proper model so that we are not limited
457 * to only one dialog.
462 map<string, DialogPtr> dialogs_;
464 unsigned int smallIconSize;
465 unsigned int normalIconSize;
466 unsigned int bigIconSize;
467 unsigned int hugeIconSize;
468 unsigned int giantIconSize;
470 QTimer statusbar_timer_;
471 /// auto-saving of buffers
472 Timeout autosave_timeout_;
473 /// flag against a race condition due to multiclicks, see bug #1119
477 TocModels toc_models_;
480 QFutureWatcher<docstring> autosave_watcher_;
481 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
483 string last_export_format;
484 string processing_format;
486 static QSet<Buffer const *> busyBuffers;
487 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
488 Buffer * buffer, string const & format);
489 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
490 Buffer * buffer, string const & format);
491 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
492 Buffer * buffer, string const & format);
493 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
496 static Buffer::ExportStatus runAndDestroy(const T& func,
497 Buffer const * orig, Buffer * buffer, string const & format);
499 // TODO syncFunc/previewFunc: use bind
500 bool asyncBufferProcessing(string const & argument,
501 Buffer const * used_buffer,
502 docstring const & msg,
503 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
504 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
505 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const);
507 QVector<GuiWorkArea*> guiWorkAreas();
510 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
513 GuiView::GuiView(int id)
514 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
515 command_execute_(false), minibuffer_focus_(false), devel_mode_(false)
517 connect(this, SIGNAL(bufferViewChanged()),
518 this, SLOT(onBufferViewChanged()));
520 // GuiToolbars *must* be initialised before the menu bar.
521 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
524 // set ourself as the current view. This is needed for the menu bar
525 // filling, at least for the static special menu item on Mac. Otherwise
526 // they are greyed out.
527 guiApp->setCurrentView(this);
529 // Fill up the menu bar.
530 guiApp->menus().fillMenuBar(menuBar(), this, true);
532 setCentralWidget(d.stack_widget_);
534 // Start autosave timer
535 if (lyxrc.autosave) {
536 // The connection is closed when this is destroyed.
537 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
538 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
539 d.autosave_timeout_.start();
541 connect(&d.statusbar_timer_, SIGNAL(timeout()),
542 this, SLOT(clearMessage()));
544 // We don't want to keep the window in memory if it is closed.
545 setAttribute(Qt::WA_DeleteOnClose, true);
547 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
548 // QIcon::fromTheme was introduced in Qt 4.6
549 #if (QT_VERSION >= 0x040600)
550 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
551 // since the icon is provided in the application bundle. We use a themed
552 // version when available and use the bundled one as fallback.
553 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
555 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
561 // use tabbed dock area for multiple docks
562 // (such as "source" and "messages")
563 setDockOptions(QMainWindow::ForceTabbedDocks);
566 setAcceptDrops(true);
568 // add busy indicator to statusbar
569 QLabel * busylabel = new QLabel(statusBar());
570 statusBar()->addPermanentWidget(busylabel);
571 search_mode mode = theGuiApp()->imageSearchMode();
572 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
573 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
574 busylabel->setMovie(busyanim);
578 connect(&d.processing_thread_watcher_, SIGNAL(started()),
579 busylabel, SLOT(show()));
580 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
581 busylabel, SLOT(hide()));
583 QFontMetrics const fm(statusBar()->fontMetrics());
584 int const iconheight = max(int(d.normalIconSize), fm.height());
585 QSize const iconsize(iconheight, iconheight);
587 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
588 shell_escape_ = new QLabel(statusBar());
589 shell_escape_->setPixmap(shellescape);
590 shell_escape_->setScaledContents(true);
591 shell_escape_->setAlignment(Qt::AlignCenter);
592 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
593 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
594 "external commands for this document. "
595 "Right click to change."));
596 SEMenu * menu = new SEMenu(this);
597 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
598 menu, SLOT(showMenu(QPoint)));
599 shell_escape_->hide();
600 statusBar()->addPermanentWidget(shell_escape_);
602 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
603 read_only_ = new QLabel(statusBar());
604 read_only_->setPixmap(readonly);
605 read_only_->setScaledContents(true);
606 read_only_->setAlignment(Qt::AlignCenter);
608 statusBar()->addPermanentWidget(read_only_);
610 version_control_ = new QLabel(statusBar());
611 version_control_->setAlignment(Qt::AlignCenter);
612 version_control_->setFrameStyle(QFrame::StyledPanel);
613 version_control_->hide();
614 statusBar()->addPermanentWidget(version_control_);
616 statusBar()->setSizeGripEnabled(true);
619 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
620 SLOT(autoSaveThreadFinished()));
622 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
623 SLOT(processingThreadStarted()));
624 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
625 SLOT(processingThreadFinished()));
627 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
628 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
630 // set custom application bars context menu, e.g. tool bar and menu bar
631 setContextMenuPolicy(Qt::CustomContextMenu);
632 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
633 SLOT(toolBarPopup(const QPoint &)));
635 // Forbid too small unresizable window because it can happen
636 // with some window manager under X11.
637 setMinimumSize(300, 200);
639 if (lyxrc.allow_geometry_session) {
640 // Now take care of session management.
645 // no session handling, default to a sane size.
646 setGeometry(50, 50, 690, 510);
649 // clear session data if any.
651 settings.remove("views");
661 void GuiView::disableShellEscape()
663 BufferView * bv = documentBufferView();
666 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
667 bv->buffer().params().shell_escape = false;
668 bv->processUpdateFlags(Update::Force);
672 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
674 QVector<GuiWorkArea*> areas;
675 for (int i = 0; i < tabWorkAreaCount(); i++) {
676 TabWorkArea* ta = tabWorkArea(i);
677 for (int u = 0; u < ta->count(); u++) {
678 areas << ta->workArea(u);
684 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
685 string const & format)
687 docstring const fmt = theFormats().prettyName(format);
690 case Buffer::ExportSuccess:
691 msg = bformat(_("Successful export to format: %1$s"), fmt);
693 case Buffer::ExportCancel:
694 msg = _("Document export cancelled.");
696 case Buffer::ExportError:
697 case Buffer::ExportNoPathToFormat:
698 case Buffer::ExportTexPathHasSpaces:
699 case Buffer::ExportConverterError:
700 msg = bformat(_("Error while exporting format: %1$s"), fmt);
702 case Buffer::PreviewSuccess:
703 msg = bformat(_("Successful preview of format: %1$s"), fmt);
705 case Buffer::PreviewError:
706 msg = bformat(_("Error while previewing format: %1$s"), fmt);
708 case Buffer::ExportKilled:
709 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
716 void GuiView::processingThreadStarted()
721 void GuiView::processingThreadFinished()
723 QFutureWatcher<Buffer::ExportStatus> const * watcher =
724 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
726 Buffer::ExportStatus const status = watcher->result();
727 handleExportStatus(this, status, d.processing_format);
730 BufferView const * const bv = currentBufferView();
731 if (bv && !bv->buffer().errorList("Export").empty()) {
736 bool const error = (status != Buffer::ExportSuccess &&
737 status != Buffer::PreviewSuccess &&
738 status != Buffer::ExportCancel);
740 ErrorList & el = bv->buffer().errorList(d.last_export_format);
741 // at this point, we do not know if buffer-view or
742 // master-buffer-view was called. If there was an export error,
743 // and the current buffer's error log is empty, we guess that
744 // it must be master-buffer-view that was called so we set
746 errors(d.last_export_format, el.empty());
751 void GuiView::autoSaveThreadFinished()
753 QFutureWatcher<docstring> const * watcher =
754 static_cast<QFutureWatcher<docstring> const *>(sender());
755 message(watcher->result());
760 void GuiView::saveLayout() const
763 settings.setValue("zoom_ratio", zoom_ratio_);
764 settings.setValue("devel_mode", devel_mode_);
765 settings.beginGroup("views");
766 settings.beginGroup(QString::number(id_));
767 #if defined(Q_WS_X11) || defined(QPA_XCB)
768 settings.setValue("pos", pos());
769 settings.setValue("size", size());
771 settings.setValue("geometry", saveGeometry());
773 settings.setValue("layout", saveState(0));
774 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
778 void GuiView::saveUISettings() const
782 // Save the toolbar private states
783 ToolbarMap::iterator end = d.toolbars_.end();
784 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
785 it->second->saveSession(settings);
786 // Now take care of all other dialogs
787 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
788 for (; it!= d.dialogs_.end(); ++it)
789 it->second->saveSession(settings);
793 bool GuiView::restoreLayout()
796 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
797 // Actual zoom value: default zoom + fractional offset
798 int zoom = lyxrc.defaultZoom * zoom_ratio_;
799 if (zoom < static_cast<int>(zoom_min_))
801 lyxrc.currentZoom = zoom;
802 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
803 settings.beginGroup("views");
804 settings.beginGroup(QString::number(id_));
805 QString const icon_key = "icon_size";
806 if (!settings.contains(icon_key))
809 //code below is skipped when when ~/.config/LyX is (re)created
810 setIconSize(d.iconSize(settings.value(icon_key).toString()));
812 #if defined(Q_WS_X11) || defined(QPA_XCB)
813 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
814 QSize size = settings.value("size", QSize(690, 510)).toSize();
818 // Work-around for bug #6034: the window ends up in an undetermined
819 // state when trying to restore a maximized window when it is
820 // already maximized.
821 if (!(windowState() & Qt::WindowMaximized))
822 if (!restoreGeometry(settings.value("geometry").toByteArray()))
823 setGeometry(50, 50, 690, 510);
825 // Make sure layout is correctly oriented.
826 setLayoutDirection(qApp->layoutDirection());
828 // Allow the toc and view-source dock widget to be restored if needed.
830 if ((dialog = findOrBuild("toc", true)))
831 // see bug 5082. At least setup title and enabled state.
832 // Visibility will be adjusted by restoreState below.
833 dialog->prepareView();
834 if ((dialog = findOrBuild("view-source", true)))
835 dialog->prepareView();
836 if ((dialog = findOrBuild("progress", true)))
837 dialog->prepareView();
839 if (!restoreState(settings.value("layout").toByteArray(), 0))
842 // init the toolbars that have not been restored
843 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
844 Toolbars::Infos::iterator end = guiApp->toolbars().end();
845 for (; cit != end; ++cit) {
846 GuiToolbar * tb = toolbar(cit->name);
847 if (tb && !tb->isRestored())
848 initToolbar(cit->name);
851 // update lock (all) toolbars positions
852 updateLockToolbars();
859 GuiToolbar * GuiView::toolbar(string const & name)
861 ToolbarMap::iterator it = d.toolbars_.find(name);
862 if (it != d.toolbars_.end())
865 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
870 void GuiView::updateLockToolbars()
872 toolbarsMovable_ = false;
873 for (ToolbarInfo const & info : guiApp->toolbars()) {
874 GuiToolbar * tb = toolbar(info.name);
875 if (tb && tb->isMovable())
876 toolbarsMovable_ = true;
881 void GuiView::constructToolbars()
883 ToolbarMap::iterator it = d.toolbars_.begin();
884 for (; it != d.toolbars_.end(); ++it)
888 // I don't like doing this here, but the standard toolbar
889 // destroys this object when it's destroyed itself (vfr)
890 d.layout_ = new LayoutBox(*this);
891 d.stack_widget_->addWidget(d.layout_);
892 d.layout_->move(0,0);
894 // extracts the toolbars from the backend
895 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
896 Toolbars::Infos::iterator end = guiApp->toolbars().end();
897 for (; cit != end; ++cit)
898 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
902 void GuiView::initToolbars()
904 // extracts the toolbars from the backend
905 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
906 Toolbars::Infos::iterator end = guiApp->toolbars().end();
907 for (; cit != end; ++cit)
908 initToolbar(cit->name);
912 void GuiView::initToolbar(string const & name)
914 GuiToolbar * tb = toolbar(name);
917 int const visibility = guiApp->toolbars().defaultVisibility(name);
918 bool newline = !(visibility & Toolbars::SAMEROW);
919 tb->setVisible(false);
920 tb->setVisibility(visibility);
922 if (visibility & Toolbars::TOP) {
924 addToolBarBreak(Qt::TopToolBarArea);
925 addToolBar(Qt::TopToolBarArea, tb);
928 if (visibility & Toolbars::BOTTOM) {
930 addToolBarBreak(Qt::BottomToolBarArea);
931 addToolBar(Qt::BottomToolBarArea, tb);
934 if (visibility & Toolbars::LEFT) {
936 addToolBarBreak(Qt::LeftToolBarArea);
937 addToolBar(Qt::LeftToolBarArea, tb);
940 if (visibility & Toolbars::RIGHT) {
942 addToolBarBreak(Qt::RightToolBarArea);
943 addToolBar(Qt::RightToolBarArea, tb);
946 if (visibility & Toolbars::ON)
947 tb->setVisible(true);
949 tb->setMovable(true);
953 TocModels & GuiView::tocModels()
955 return d.toc_models_;
959 void GuiView::setFocus()
961 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
962 QMainWindow::setFocus();
966 bool GuiView::hasFocus() const
968 if (currentWorkArea())
969 return currentWorkArea()->hasFocus();
970 if (currentMainWorkArea())
971 return currentMainWorkArea()->hasFocus();
972 return d.bg_widget_->hasFocus();
976 void GuiView::focusInEvent(QFocusEvent * e)
978 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
979 QMainWindow::focusInEvent(e);
980 // Make sure guiApp points to the correct view.
981 guiApp->setCurrentView(this);
982 if (currentWorkArea())
983 currentWorkArea()->setFocus();
984 else if (currentMainWorkArea())
985 currentMainWorkArea()->setFocus();
987 d.bg_widget_->setFocus();
991 void GuiView::showEvent(QShowEvent * e)
993 LYXERR(Debug::GUI, "Passed Geometry "
994 << size().height() << "x" << size().width()
995 << "+" << pos().x() << "+" << pos().y());
997 if (d.splitter_->count() == 0)
998 // No work area, switch to the background widget.
1002 QMainWindow::showEvent(e);
1006 bool GuiView::closeScheduled()
1013 bool GuiView::prepareAllBuffersForLogout()
1015 Buffer * first = theBufferList().first();
1019 // First, iterate over all buffers and ask the users if unsaved
1020 // changes should be saved.
1021 // We cannot use a for loop as the buffer list cycles.
1024 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1026 b = theBufferList().next(b);
1027 } while (b != first);
1029 // Next, save session state
1030 // When a view/window was closed before without quitting LyX, there
1031 // are already entries in the lastOpened list.
1032 theSession().lastOpened().clear();
1039 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1040 ** is responsibility of the container (e.g., dialog)
1042 void GuiView::closeEvent(QCloseEvent * close_event)
1044 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1046 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1047 Alert::warning(_("Exit LyX"),
1048 _("LyX could not be closed because documents are being processed by LyX."));
1049 close_event->setAccepted(false);
1053 // If the user pressed the x (so we didn't call closeView
1054 // programmatically), we want to clear all existing entries.
1056 theSession().lastOpened().clear();
1061 // it can happen that this event arrives without selecting the view,
1062 // e.g. when clicking the close button on a background window.
1064 if (!closeWorkAreaAll()) {
1066 close_event->ignore();
1070 // Make sure that nothing will use this to be closed View.
1071 guiApp->unregisterView(this);
1073 if (isFullScreen()) {
1074 // Switch off fullscreen before closing.
1079 // Make sure the timer time out will not trigger a statusbar update.
1080 d.statusbar_timer_.stop();
1082 // Saving fullscreen requires additional tweaks in the toolbar code.
1083 // It wouldn't also work under linux natively.
1084 if (lyxrc.allow_geometry_session) {
1089 close_event->accept();
1093 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1095 if (event->mimeData()->hasUrls())
1097 /// \todo Ask lyx-devel is this is enough:
1098 /// if (event->mimeData()->hasFormat("text/plain"))
1099 /// event->acceptProposedAction();
1103 void GuiView::dropEvent(QDropEvent * event)
1105 QList<QUrl> files = event->mimeData()->urls();
1106 if (files.isEmpty())
1109 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1110 for (int i = 0; i != files.size(); ++i) {
1111 string const file = os::internal_path(fromqstr(
1112 files.at(i).toLocalFile()));
1116 string const ext = support::getExtension(file);
1117 vector<const Format *> found_formats;
1119 // Find all formats that have the correct extension.
1120 vector<const Format *> const & import_formats
1121 = theConverters().importableFormats();
1122 vector<const Format *>::const_iterator it = import_formats.begin();
1123 for (; it != import_formats.end(); ++it)
1124 if ((*it)->hasExtension(ext))
1125 found_formats.push_back(*it);
1128 if (found_formats.size() >= 1) {
1129 if (found_formats.size() > 1) {
1130 //FIXME: show a dialog to choose the correct importable format
1131 LYXERR(Debug::FILES,
1132 "Multiple importable formats found, selecting first");
1134 string const arg = found_formats[0]->name() + " " + file;
1135 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1138 //FIXME: do we have to explicitly check whether it's a lyx file?
1139 LYXERR(Debug::FILES,
1140 "No formats found, trying to open it as a lyx file");
1141 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1143 // add the functions to the queue
1144 guiApp->addToFuncRequestQueue(cmd);
1147 // now process the collected functions. We perform the events
1148 // asynchronously. This prevents potential problems in case the
1149 // BufferView is closed within an event.
1150 guiApp->processFuncRequestQueueAsync();
1154 void GuiView::message(docstring const & str)
1156 if (ForkedProcess::iAmAChild())
1159 // call is moved to GUI-thread by GuiProgress
1160 d.progress_->appendMessage(toqstr(str));
1164 void GuiView::clearMessageText()
1166 message(docstring());
1170 void GuiView::updateStatusBarMessage(QString const & str)
1172 statusBar()->showMessage(str);
1173 d.statusbar_timer_.stop();
1174 d.statusbar_timer_.start(3000);
1178 void GuiView::clearMessage()
1180 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1181 // the hasFocus function mostly returns false, even if the focus is on
1182 // a workarea in this view.
1186 d.statusbar_timer_.stop();
1190 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1192 if (wa != d.current_work_area_
1193 || wa->bufferView().buffer().isInternal())
1195 Buffer const & buf = wa->bufferView().buffer();
1196 // Set the windows title
1197 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1198 if (buf.notifiesExternalModification()) {
1199 title = bformat(_("%1$s (modified externally)"), title);
1200 // If the external modification status has changed, then maybe the status of
1201 // buffer-save has changed too.
1205 title += from_ascii(" - LyX");
1207 setWindowTitle(toqstr(title));
1208 // Sets the path for the window: this is used by OSX to
1209 // allow a context click on the title bar showing a menu
1210 // with the path up to the file
1211 setWindowFilePath(toqstr(buf.absFileName()));
1212 // Tell Qt whether the current document is changed
1213 setWindowModified(!buf.isClean());
1215 if (buf.params().shell_escape)
1216 shell_escape_->show();
1218 shell_escape_->hide();
1220 if (buf.hasReadonlyFlag())
1225 if (buf.lyxvc().inUse()) {
1226 version_control_->show();
1227 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1229 version_control_->hide();
1233 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1235 if (d.current_work_area_)
1236 // disconnect the current work area from all slots
1237 QObject::disconnect(d.current_work_area_, 0, this, 0);
1239 disconnectBufferView();
1240 connectBufferView(wa->bufferView());
1241 connectBuffer(wa->bufferView().buffer());
1242 d.current_work_area_ = wa;
1243 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1244 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1245 QObject::connect(wa, SIGNAL(busy(bool)),
1246 this, SLOT(setBusy(bool)));
1247 // connection of a signal to a signal
1248 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1249 this, SIGNAL(bufferViewChanged()));
1250 Q_EMIT updateWindowTitle(wa);
1251 Q_EMIT bufferViewChanged();
1255 void GuiView::onBufferViewChanged()
1258 // Buffer-dependent dialogs must be updated. This is done here because
1259 // some dialogs require buffer()->text.
1264 void GuiView::on_lastWorkAreaRemoved()
1267 // We already are in a close event. Nothing more to do.
1270 if (d.splitter_->count() > 1)
1271 // We have a splitter so don't close anything.
1274 // Reset and updates the dialogs.
1275 Q_EMIT bufferViewChanged();
1280 if (lyxrc.open_buffers_in_tabs)
1281 // Nothing more to do, the window should stay open.
1284 if (guiApp->viewIds().size() > 1) {
1290 // On Mac we also close the last window because the application stay
1291 // resident in memory. On other platforms we don't close the last
1292 // window because this would quit the application.
1298 void GuiView::updateStatusBar()
1300 // let the user see the explicit message
1301 if (d.statusbar_timer_.isActive())
1308 void GuiView::showMessage()
1312 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1313 if (msg.isEmpty()) {
1314 BufferView const * bv = currentBufferView();
1316 msg = toqstr(bv->cursor().currentState(devel_mode_));
1318 msg = qt_("Welcome to LyX!");
1320 statusBar()->showMessage(msg);
1324 bool GuiView::event(QEvent * e)
1328 // Useful debug code:
1329 //case QEvent::ActivationChange:
1330 //case QEvent::WindowDeactivate:
1331 //case QEvent::Paint:
1332 //case QEvent::Enter:
1333 //case QEvent::Leave:
1334 //case QEvent::HoverEnter:
1335 //case QEvent::HoverLeave:
1336 //case QEvent::HoverMove:
1337 //case QEvent::StatusTip:
1338 //case QEvent::DragEnter:
1339 //case QEvent::DragLeave:
1340 //case QEvent::Drop:
1343 case QEvent::WindowActivate: {
1344 GuiView * old_view = guiApp->currentView();
1345 if (this == old_view) {
1347 return QMainWindow::event(e);
1349 if (old_view && old_view->currentBufferView()) {
1350 // save current selection to the selection buffer to allow
1351 // middle-button paste in this window.
1352 cap::saveSelection(old_view->currentBufferView()->cursor());
1354 guiApp->setCurrentView(this);
1355 if (d.current_work_area_)
1356 on_currentWorkAreaChanged(d.current_work_area_);
1360 return QMainWindow::event(e);
1363 case QEvent::ShortcutOverride: {
1365 if (isFullScreen() && menuBar()->isHidden()) {
1366 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1367 // FIXME: we should also try to detect special LyX shortcut such as
1368 // Alt-P and Alt-M. Right now there is a hack in
1369 // GuiWorkArea::processKeySym() that hides again the menubar for
1371 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1373 return QMainWindow::event(e);
1376 return QMainWindow::event(e);
1380 return QMainWindow::event(e);
1384 void GuiView::resetWindowTitle()
1386 setWindowTitle(qt_("LyX"));
1389 bool GuiView::focusNextPrevChild(bool /*next*/)
1396 bool GuiView::busy() const
1402 void GuiView::setBusy(bool busy)
1404 bool const busy_before = busy_ > 0;
1405 busy ? ++busy_ : --busy_;
1406 if ((busy_ > 0) == busy_before)
1407 // busy state didn't change
1411 QApplication::setOverrideCursor(Qt::WaitCursor);
1414 QApplication::restoreOverrideCursor();
1419 void GuiView::resetCommandExecute()
1421 command_execute_ = false;
1426 double GuiView::pixelRatio() const
1428 #if QT_VERSION >= 0x050000
1429 return qt_scale_factor * devicePixelRatio();
1436 GuiWorkArea * GuiView::workArea(int index)
1438 if (TabWorkArea * twa = d.currentTabWorkArea())
1439 if (index < twa->count())
1440 return twa->workArea(index);
1445 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1447 if (currentWorkArea()
1448 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1449 return currentWorkArea();
1450 if (TabWorkArea * twa = d.currentTabWorkArea())
1451 return twa->workArea(buffer);
1456 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1458 // Automatically create a TabWorkArea if there are none yet.
1459 TabWorkArea * tab_widget = d.splitter_->count()
1460 ? d.currentTabWorkArea() : addTabWorkArea();
1461 return tab_widget->addWorkArea(buffer, *this);
1465 TabWorkArea * GuiView::addTabWorkArea()
1467 TabWorkArea * twa = new TabWorkArea;
1468 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1469 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1470 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1471 this, SLOT(on_lastWorkAreaRemoved()));
1473 d.splitter_->addWidget(twa);
1474 d.stack_widget_->setCurrentWidget(d.splitter_);
1479 GuiWorkArea const * GuiView::currentWorkArea() const
1481 return d.current_work_area_;
1485 GuiWorkArea * GuiView::currentWorkArea()
1487 return d.current_work_area_;
1491 GuiWorkArea const * GuiView::currentMainWorkArea() const
1493 if (!d.currentTabWorkArea())
1495 return d.currentTabWorkArea()->currentWorkArea();
1499 GuiWorkArea * GuiView::currentMainWorkArea()
1501 if (!d.currentTabWorkArea())
1503 return d.currentTabWorkArea()->currentWorkArea();
1507 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1509 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1511 d.current_work_area_ = 0;
1513 Q_EMIT bufferViewChanged();
1517 // FIXME: I've no clue why this is here and why it accesses
1518 // theGuiApp()->currentView, which might be 0 (bug 6464).
1519 // See also 27525 (vfr).
1520 if (theGuiApp()->currentView() == this
1521 && theGuiApp()->currentView()->currentWorkArea() == wa)
1524 if (currentBufferView())
1525 cap::saveSelection(currentBufferView()->cursor());
1527 theGuiApp()->setCurrentView(this);
1528 d.current_work_area_ = wa;
1530 // We need to reset this now, because it will need to be
1531 // right if the tabWorkArea gets reset in the for loop. We
1532 // will change it back if we aren't in that case.
1533 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1534 d.current_main_work_area_ = wa;
1536 for (int i = 0; i != d.splitter_->count(); ++i) {
1537 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1538 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1539 << ", Current main wa: " << currentMainWorkArea());
1544 d.current_main_work_area_ = old_cmwa;
1546 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1547 on_currentWorkAreaChanged(wa);
1548 BufferView & bv = wa->bufferView();
1549 bv.cursor().fixIfBroken();
1551 wa->setUpdatesEnabled(true);
1552 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1556 void GuiView::removeWorkArea(GuiWorkArea * wa)
1558 LASSERT(wa, return);
1559 if (wa == d.current_work_area_) {
1561 disconnectBufferView();
1562 d.current_work_area_ = 0;
1563 d.current_main_work_area_ = 0;
1566 bool found_twa = false;
1567 for (int i = 0; i != d.splitter_->count(); ++i) {
1568 TabWorkArea * twa = d.tabWorkArea(i);
1569 if (twa->removeWorkArea(wa)) {
1570 // Found in this tab group, and deleted the GuiWorkArea.
1572 if (twa->count() != 0) {
1573 if (d.current_work_area_ == 0)
1574 // This means that we are closing the current GuiWorkArea, so
1575 // switch to the next GuiWorkArea in the found TabWorkArea.
1576 setCurrentWorkArea(twa->currentWorkArea());
1578 // No more WorkAreas in this tab group, so delete it.
1585 // It is not a tabbed work area (i.e., the search work area), so it
1586 // should be deleted by other means.
1587 LASSERT(found_twa, return);
1589 if (d.current_work_area_ == 0) {
1590 if (d.splitter_->count() != 0) {
1591 TabWorkArea * twa = d.currentTabWorkArea();
1592 setCurrentWorkArea(twa->currentWorkArea());
1594 // No more work areas, switch to the background widget.
1595 setCurrentWorkArea(0);
1601 LayoutBox * GuiView::getLayoutDialog() const
1607 void GuiView::updateLayoutList()
1610 d.layout_->updateContents(false);
1614 void GuiView::updateToolbars()
1616 ToolbarMap::iterator end = d.toolbars_.end();
1617 if (d.current_work_area_) {
1619 if (d.current_work_area_->bufferView().cursor().inMathed()
1620 && !d.current_work_area_->bufferView().cursor().inRegexped())
1621 context |= Toolbars::MATH;
1622 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1623 context |= Toolbars::TABLE;
1624 if (currentBufferView()->buffer().areChangesPresent()
1625 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1626 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1627 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1628 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1629 context |= Toolbars::REVIEW;
1630 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1631 context |= Toolbars::MATHMACROTEMPLATE;
1632 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1633 context |= Toolbars::IPA;
1634 if (command_execute_)
1635 context |= Toolbars::MINIBUFFER;
1636 if (minibuffer_focus_) {
1637 context |= Toolbars::MINIBUFFER_FOCUS;
1638 minibuffer_focus_ = false;
1641 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1642 it->second->update(context);
1644 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1645 it->second->update();
1649 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1651 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1652 LASSERT(newBuffer, return);
1654 GuiWorkArea * wa = workArea(*newBuffer);
1657 newBuffer->masterBuffer()->updateBuffer();
1659 wa = addWorkArea(*newBuffer);
1660 // scroll to the position when the BufferView was last closed
1661 if (lyxrc.use_lastfilepos) {
1662 LastFilePosSection::FilePos filepos =
1663 theSession().lastFilePos().load(newBuffer->fileName());
1664 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1667 //Disconnect the old buffer...there's no new one.
1670 connectBuffer(*newBuffer);
1671 connectBufferView(wa->bufferView());
1673 setCurrentWorkArea(wa);
1677 void GuiView::connectBuffer(Buffer & buf)
1679 buf.setGuiDelegate(this);
1683 void GuiView::disconnectBuffer()
1685 if (d.current_work_area_)
1686 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1690 void GuiView::connectBufferView(BufferView & bv)
1692 bv.setGuiDelegate(this);
1696 void GuiView::disconnectBufferView()
1698 if (d.current_work_area_)
1699 d.current_work_area_->bufferView().setGuiDelegate(0);
1703 void GuiView::errors(string const & error_type, bool from_master)
1705 BufferView const * const bv = currentBufferView();
1709 ErrorList const & el = from_master ?
1710 bv->buffer().masterBuffer()->errorList(error_type) :
1711 bv->buffer().errorList(error_type);
1716 string err = error_type;
1718 err = "from_master|" + error_type;
1719 showDialog("errorlist", err);
1723 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1725 d.toc_models_.updateItem(toqstr(type), dit);
1729 void GuiView::structureChanged()
1731 // This is called from the Buffer, which has no way to ensure that cursors
1732 // in BufferView remain valid.
1733 if (documentBufferView())
1734 documentBufferView()->cursor().sanitize();
1735 // FIXME: This is slightly expensive, though less than the tocBackend update
1736 // (#9880). This also resets the view in the Toc Widget (#6675).
1737 d.toc_models_.reset(documentBufferView());
1738 // Navigator needs more than a simple update in this case. It needs to be
1740 updateDialog("toc", "");
1744 void GuiView::updateDialog(string const & name, string const & sdata)
1746 if (!isDialogVisible(name))
1749 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1750 if (it == d.dialogs_.end())
1753 Dialog * const dialog = it->second.get();
1754 if (dialog->isVisibleView())
1755 dialog->initialiseParams(sdata);
1759 BufferView * GuiView::documentBufferView()
1761 return currentMainWorkArea()
1762 ? ¤tMainWorkArea()->bufferView()
1767 BufferView const * GuiView::documentBufferView() const
1769 return currentMainWorkArea()
1770 ? ¤tMainWorkArea()->bufferView()
1775 BufferView * GuiView::currentBufferView()
1777 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1781 BufferView const * GuiView::currentBufferView() const
1783 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1787 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1788 Buffer const * orig, Buffer * clone)
1790 bool const success = clone->autoSave();
1792 busyBuffers.remove(orig);
1794 ? _("Automatic save done.")
1795 : _("Automatic save failed!");
1799 void GuiView::autoSave()
1801 LYXERR(Debug::INFO, "Running autoSave()");
1803 Buffer * buffer = documentBufferView()
1804 ? &documentBufferView()->buffer() : 0;
1806 resetAutosaveTimers();
1810 GuiViewPrivate::busyBuffers.insert(buffer);
1811 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1812 buffer, buffer->cloneBufferOnly());
1813 d.autosave_watcher_.setFuture(f);
1814 resetAutosaveTimers();
1818 void GuiView::resetAutosaveTimers()
1821 d.autosave_timeout_.restart();
1825 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1828 Buffer * buf = currentBufferView()
1829 ? ¤tBufferView()->buffer() : 0;
1830 Buffer * doc_buffer = documentBufferView()
1831 ? &(documentBufferView()->buffer()) : 0;
1834 /* In LyX/Mac, when a dialog is open, the menus of the
1835 application can still be accessed without giving focus to
1836 the main window. In this case, we want to disable the menu
1837 entries that are buffer-related.
1838 This code must not be used on Linux and Windows, since it
1839 would disable buffer-related entries when hovering over the
1840 menu (see bug #9574).
1842 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1848 // Check whether we need a buffer
1849 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1850 // no, exit directly
1851 flag.message(from_utf8(N_("Command not allowed with"
1852 "out any document open")));
1853 flag.setEnabled(false);
1857 if (cmd.origin() == FuncRequest::TOC) {
1858 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1859 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1860 flag.setEnabled(false);
1864 switch(cmd.action()) {
1865 case LFUN_BUFFER_IMPORT:
1868 case LFUN_MASTER_BUFFER_UPDATE:
1869 case LFUN_MASTER_BUFFER_VIEW:
1871 && (doc_buffer->parent() != 0
1872 || doc_buffer->hasChildren())
1873 && !d.processing_thread_watcher_.isRunning();
1876 case LFUN_BUFFER_UPDATE:
1877 case LFUN_BUFFER_VIEW: {
1878 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1882 string format = to_utf8(cmd.argument());
1883 if (cmd.argument().empty())
1884 format = doc_buffer->params().getDefaultOutputFormat();
1885 enable = doc_buffer->params().isExportable(format, true);
1889 case LFUN_BUFFER_RELOAD:
1890 enable = doc_buffer && !doc_buffer->isUnnamed()
1891 && doc_buffer->fileName().exists()
1892 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1895 case LFUN_BUFFER_CHILD_OPEN:
1896 enable = doc_buffer != 0;
1899 case LFUN_BUFFER_WRITE:
1900 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1903 //FIXME: This LFUN should be moved to GuiApplication.
1904 case LFUN_BUFFER_WRITE_ALL: {
1905 // We enable the command only if there are some modified buffers
1906 Buffer * first = theBufferList().first();
1911 // We cannot use a for loop as the buffer list is a cycle.
1913 if (!b->isClean()) {
1917 b = theBufferList().next(b);
1918 } while (b != first);
1922 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1923 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1926 case LFUN_BUFFER_EXPORT: {
1927 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1931 return doc_buffer->getStatus(cmd, flag);
1935 case LFUN_BUFFER_EXPORT_AS:
1936 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1941 case LFUN_BUFFER_WRITE_AS:
1942 enable = doc_buffer != 0;
1945 case LFUN_EXPORT_CANCEL:
1946 enable = d.processing_thread_watcher_.isRunning();
1949 case LFUN_BUFFER_CLOSE:
1950 case LFUN_VIEW_CLOSE:
1951 enable = doc_buffer != 0;
1954 case LFUN_BUFFER_CLOSE_ALL:
1955 enable = theBufferList().last() != theBufferList().first();
1958 case LFUN_BUFFER_CHKTEX: {
1959 // hide if we have no checktex command
1960 if (lyxrc.chktex_command.empty()) {
1961 flag.setUnknown(true);
1965 if (!doc_buffer || !doc_buffer->params().isLatex()
1966 || d.processing_thread_watcher_.isRunning()) {
1967 // grey out, don't hide
1975 case LFUN_VIEW_SPLIT:
1976 if (cmd.getArg(0) == "vertical")
1977 enable = doc_buffer && (d.splitter_->count() == 1 ||
1978 d.splitter_->orientation() == Qt::Vertical);
1980 enable = doc_buffer && (d.splitter_->count() == 1 ||
1981 d.splitter_->orientation() == Qt::Horizontal);
1984 case LFUN_TAB_GROUP_CLOSE:
1985 enable = d.tabWorkAreaCount() > 1;
1988 case LFUN_DEVEL_MODE_TOGGLE:
1989 flag.setOnOff(devel_mode_);
1992 case LFUN_TOOLBAR_TOGGLE: {
1993 string const name = cmd.getArg(0);
1994 if (GuiToolbar * t = toolbar(name))
1995 flag.setOnOff(t->isVisible());
1998 docstring const msg =
1999 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2005 case LFUN_TOOLBAR_MOVABLE: {
2006 string const name = cmd.getArg(0);
2007 // use negation since locked == !movable
2009 // toolbar name * locks all toolbars
2010 flag.setOnOff(!toolbarsMovable_);
2011 else if (GuiToolbar * t = toolbar(name))
2012 flag.setOnOff(!(t->isMovable()));
2015 docstring const msg =
2016 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2022 case LFUN_ICON_SIZE:
2023 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2026 case LFUN_DROP_LAYOUTS_CHOICE:
2030 case LFUN_UI_TOGGLE:
2031 flag.setOnOff(isFullScreen());
2034 case LFUN_DIALOG_DISCONNECT_INSET:
2037 case LFUN_DIALOG_HIDE:
2038 // FIXME: should we check if the dialog is shown?
2041 case LFUN_DIALOG_TOGGLE:
2042 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2045 case LFUN_DIALOG_SHOW: {
2046 string const name = cmd.getArg(0);
2048 enable = name == "aboutlyx"
2049 || name == "file" //FIXME: should be removed.
2051 || name == "texinfo"
2052 || name == "progress"
2053 || name == "compare";
2054 else if (name == "character" || name == "symbols"
2055 || name == "mathdelimiter" || name == "mathmatrix") {
2056 if (!buf || buf->isReadonly())
2059 Cursor const & cur = currentBufferView()->cursor();
2060 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2063 else if (name == "latexlog")
2064 enable = FileName(doc_buffer->logName()).isReadableFile();
2065 else if (name == "spellchecker")
2066 enable = theSpellChecker()
2067 && !doc_buffer->isReadonly()
2068 && !doc_buffer->text().empty();
2069 else if (name == "vclog")
2070 enable = doc_buffer->lyxvc().inUse();
2074 case LFUN_DIALOG_UPDATE: {
2075 string const name = cmd.getArg(0);
2077 enable = name == "prefs";
2081 case LFUN_COMMAND_EXECUTE:
2083 case LFUN_MENU_OPEN:
2084 // Nothing to check.
2087 case LFUN_COMPLETION_INLINE:
2088 if (!d.current_work_area_
2089 || !d.current_work_area_->completer().inlinePossible(
2090 currentBufferView()->cursor()))
2094 case LFUN_COMPLETION_POPUP:
2095 if (!d.current_work_area_
2096 || !d.current_work_area_->completer().popupPossible(
2097 currentBufferView()->cursor()))
2102 if (!d.current_work_area_
2103 || !d.current_work_area_->completer().inlinePossible(
2104 currentBufferView()->cursor()))
2108 case LFUN_COMPLETION_ACCEPT:
2109 if (!d.current_work_area_
2110 || (!d.current_work_area_->completer().popupVisible()
2111 && !d.current_work_area_->completer().inlineVisible()
2112 && !d.current_work_area_->completer().completionAvailable()))
2116 case LFUN_COMPLETION_CANCEL:
2117 if (!d.current_work_area_
2118 || (!d.current_work_area_->completer().popupVisible()
2119 && !d.current_work_area_->completer().inlineVisible()))
2123 case LFUN_BUFFER_ZOOM_OUT:
2124 case LFUN_BUFFER_ZOOM_IN: {
2125 // only diff between these two is that the default for ZOOM_OUT
2127 bool const neg_zoom =
2128 convert<int>(cmd.argument()) < 0 ||
2129 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2130 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2131 docstring const msg =
2132 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2136 enable = doc_buffer;
2140 case LFUN_BUFFER_ZOOM: {
2141 bool const less_than_min_zoom =
2142 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2143 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2144 docstring const msg =
2145 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2150 enable = doc_buffer;
2154 case LFUN_BUFFER_MOVE_NEXT:
2155 case LFUN_BUFFER_MOVE_PREVIOUS:
2156 // we do not cycle when moving
2157 case LFUN_BUFFER_NEXT:
2158 case LFUN_BUFFER_PREVIOUS:
2159 // because we cycle, it doesn't matter whether on first or last
2160 enable = (d.currentTabWorkArea()->count() > 1);
2162 case LFUN_BUFFER_SWITCH:
2163 // toggle on the current buffer, but do not toggle off
2164 // the other ones (is that a good idea?)
2166 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2167 flag.setOnOff(true);
2170 case LFUN_VC_REGISTER:
2171 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2173 case LFUN_VC_RENAME:
2174 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2177 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2179 case LFUN_VC_CHECK_IN:
2180 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2182 case LFUN_VC_CHECK_OUT:
2183 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2185 case LFUN_VC_LOCKING_TOGGLE:
2186 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2187 && doc_buffer->lyxvc().lockingToggleEnabled();
2188 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2190 case LFUN_VC_REVERT:
2191 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2192 && !doc_buffer->hasReadonlyFlag();
2194 case LFUN_VC_UNDO_LAST:
2195 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2197 case LFUN_VC_REPO_UPDATE:
2198 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2200 case LFUN_VC_COMMAND: {
2201 if (cmd.argument().empty())
2203 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2207 case LFUN_VC_COMPARE:
2208 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2211 case LFUN_SERVER_GOTO_FILE_ROW:
2212 case LFUN_LYX_ACTIVATE:
2214 case LFUN_FORWARD_SEARCH:
2215 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2218 case LFUN_FILE_INSERT_PLAINTEXT:
2219 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2220 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2223 case LFUN_SPELLING_CONTINUOUSLY:
2224 flag.setOnOff(lyxrc.spellcheck_continuously);
2232 flag.setEnabled(false);
2238 static FileName selectTemplateFile()
2240 FileDialog dlg(qt_("Select template file"));
2241 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2242 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2244 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2245 QStringList(qt_("LyX Documents (*.lyx)")));
2247 if (result.first == FileDialog::Later)
2249 if (result.second.isEmpty())
2251 return FileName(fromqstr(result.second));
2255 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2259 Buffer * newBuffer = 0;
2261 newBuffer = checkAndLoadLyXFile(filename);
2262 } catch (ExceptionMessage const & e) {
2269 message(_("Document not loaded."));
2273 setBuffer(newBuffer);
2274 newBuffer->errors("Parse");
2277 theSession().lastFiles().add(filename);
2278 theSession().writeFile();
2285 void GuiView::openDocument(string const & fname)
2287 string initpath = lyxrc.document_path;
2289 if (documentBufferView()) {
2290 string const trypath = documentBufferView()->buffer().filePath();
2291 // If directory is writeable, use this as default.
2292 if (FileName(trypath).isDirWritable())
2298 if (fname.empty()) {
2299 FileDialog dlg(qt_("Select document to open"));
2300 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2301 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2303 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2304 FileDialog::Result result =
2305 dlg.open(toqstr(initpath), filter);
2307 if (result.first == FileDialog::Later)
2310 filename = fromqstr(result.second);
2312 // check selected filename
2313 if (filename.empty()) {
2314 message(_("Canceled."));
2320 // get absolute path of file and add ".lyx" to the filename if
2322 FileName const fullname =
2323 fileSearch(string(), filename, "lyx", support::may_not_exist);
2324 if (!fullname.empty())
2325 filename = fullname.absFileName();
2327 if (!fullname.onlyPath().isDirectory()) {
2328 Alert::warning(_("Invalid filename"),
2329 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2330 from_utf8(fullname.absFileName())));
2334 // if the file doesn't exist and isn't already open (bug 6645),
2335 // let the user create one
2336 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2337 !LyXVC::file_not_found_hook(fullname)) {
2338 // the user specifically chose this name. Believe him.
2339 Buffer * const b = newFile(filename, string(), true);
2345 docstring const disp_fn = makeDisplayPath(filename);
2346 message(bformat(_("Opening document %1$s..."), disp_fn));
2349 Buffer * buf = loadDocument(fullname);
2351 str2 = bformat(_("Document %1$s opened."), disp_fn);
2352 if (buf->lyxvc().inUse())
2353 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2354 " " + _("Version control detected.");
2356 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2361 // FIXME: clean that
2362 static bool import(GuiView * lv, FileName const & filename,
2363 string const & format, ErrorList & errorList)
2365 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2367 string loader_format;
2368 vector<string> loaders = theConverters().loaders();
2369 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2370 vector<string>::const_iterator it = loaders.begin();
2371 vector<string>::const_iterator en = loaders.end();
2372 for (; it != en; ++it) {
2373 if (!theConverters().isReachable(format, *it))
2376 string const tofile =
2377 support::changeExtension(filename.absFileName(),
2378 theFormats().extension(*it));
2379 if (theConverters().convert(0, filename, FileName(tofile),
2380 filename, format, *it, errorList) != Converters::SUCCESS)
2382 loader_format = *it;
2385 if (loader_format.empty()) {
2386 frontend::Alert::error(_("Couldn't import file"),
2387 bformat(_("No information for importing the format %1$s."),
2388 theFormats().prettyName(format)));
2392 loader_format = format;
2394 if (loader_format == "lyx") {
2395 Buffer * buf = lv->loadDocument(lyxfile);
2399 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2403 bool as_paragraphs = loader_format == "textparagraph";
2404 string filename2 = (loader_format == format) ? filename.absFileName()
2405 : support::changeExtension(filename.absFileName(),
2406 theFormats().extension(loader_format));
2407 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2409 guiApp->setCurrentView(lv);
2410 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2417 void GuiView::importDocument(string const & argument)
2420 string filename = split(argument, format, ' ');
2422 LYXERR(Debug::INFO, format << " file: " << filename);
2424 // need user interaction
2425 if (filename.empty()) {
2426 string initpath = lyxrc.document_path;
2427 if (documentBufferView()) {
2428 string const trypath = documentBufferView()->buffer().filePath();
2429 // If directory is writeable, use this as default.
2430 if (FileName(trypath).isDirWritable())
2434 docstring const text = bformat(_("Select %1$s file to import"),
2435 theFormats().prettyName(format));
2437 FileDialog dlg(toqstr(text));
2438 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2439 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2441 docstring filter = theFormats().prettyName(format);
2444 filter += from_utf8(theFormats().extensions(format));
2447 FileDialog::Result result =
2448 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2450 if (result.first == FileDialog::Later)
2453 filename = fromqstr(result.second);
2455 // check selected filename
2456 if (filename.empty())
2457 message(_("Canceled."));
2460 if (filename.empty())
2463 // get absolute path of file
2464 FileName const fullname(support::makeAbsPath(filename));
2466 // Can happen if the user entered a path into the dialog
2468 if (fullname.onlyFileName().empty()) {
2469 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2470 "Aborting import."),
2471 from_utf8(fullname.absFileName()));
2472 frontend::Alert::error(_("File name error"), msg);
2473 message(_("Canceled."));
2478 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2480 // Check if the document already is open
2481 Buffer * buf = theBufferList().getBuffer(lyxfile);
2484 if (!closeBuffer()) {
2485 message(_("Canceled."));
2490 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2492 // if the file exists already, and we didn't do
2493 // -i lyx thefile.lyx, warn
2494 if (lyxfile.exists() && fullname != lyxfile) {
2496 docstring text = bformat(_("The document %1$s already exists.\n\n"
2497 "Do you want to overwrite that document?"), displaypath);
2498 int const ret = Alert::prompt(_("Overwrite document?"),
2499 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2502 message(_("Canceled."));
2507 message(bformat(_("Importing %1$s..."), displaypath));
2508 ErrorList errorList;
2509 if (import(this, fullname, format, errorList))
2510 message(_("imported."));
2512 message(_("file not imported!"));
2514 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2518 void GuiView::newDocument(string const & filename, bool from_template)
2520 FileName initpath(lyxrc.document_path);
2521 if (documentBufferView()) {
2522 FileName const trypath(documentBufferView()->buffer().filePath());
2523 // If directory is writeable, use this as default.
2524 if (trypath.isDirWritable())
2528 string templatefile;
2529 if (from_template) {
2530 templatefile = selectTemplateFile().absFileName();
2531 if (templatefile.empty())
2536 if (filename.empty())
2537 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2539 b = newFile(filename, templatefile, true);
2544 // If no new document could be created, it is unsure
2545 // whether there is a valid BufferView.
2546 if (currentBufferView())
2547 // Ensure the cursor is correctly positioned on screen.
2548 currentBufferView()->showCursor();
2552 void GuiView::insertLyXFile(docstring const & fname)
2554 BufferView * bv = documentBufferView();
2559 FileName filename(to_utf8(fname));
2560 if (filename.empty()) {
2561 // Launch a file browser
2563 string initpath = lyxrc.document_path;
2564 string const trypath = bv->buffer().filePath();
2565 // If directory is writeable, use this as default.
2566 if (FileName(trypath).isDirWritable())
2570 FileDialog dlg(qt_("Select LyX document to insert"));
2571 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2572 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2574 FileDialog::Result result = dlg.open(toqstr(initpath),
2575 QStringList(qt_("LyX Documents (*.lyx)")));
2577 if (result.first == FileDialog::Later)
2581 filename.set(fromqstr(result.second));
2583 // check selected filename
2584 if (filename.empty()) {
2585 // emit message signal.
2586 message(_("Canceled."));
2591 bv->insertLyXFile(filename);
2592 bv->buffer().errors("Parse");
2596 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2598 FileName fname = b.fileName();
2599 FileName const oldname = fname;
2601 if (!newname.empty()) {
2603 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2605 // Switch to this Buffer.
2608 // No argument? Ask user through dialog.
2610 FileDialog dlg(qt_("Choose a filename to save document as"));
2611 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2612 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2614 if (!isLyXFileName(fname.absFileName()))
2615 fname.changeExtension(".lyx");
2617 FileDialog::Result result =
2618 dlg.save(toqstr(fname.onlyPath().absFileName()),
2619 QStringList(qt_("LyX Documents (*.lyx)")),
2620 toqstr(fname.onlyFileName()));
2622 if (result.first == FileDialog::Later)
2625 fname.set(fromqstr(result.second));
2630 if (!isLyXFileName(fname.absFileName()))
2631 fname.changeExtension(".lyx");
2634 // fname is now the new Buffer location.
2636 // if there is already a Buffer open with this name, we do not want
2637 // to have another one. (the second test makes sure we're not just
2638 // trying to overwrite ourselves, which is fine.)
2639 if (theBufferList().exists(fname) && fname != oldname
2640 && theBufferList().getBuffer(fname) != &b) {
2641 docstring const text =
2642 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2643 "Please close it before attempting to overwrite it.\n"
2644 "Do you want to choose a new filename?"),
2645 from_utf8(fname.absFileName()));
2646 int const ret = Alert::prompt(_("Chosen File Already Open"),
2647 text, 0, 1, _("&Rename"), _("&Cancel"));
2649 case 0: return renameBuffer(b, docstring(), kind);
2650 case 1: return false;
2655 bool const existsLocal = fname.exists();
2656 bool const existsInVC = LyXVC::fileInVC(fname);
2657 if (existsLocal || existsInVC) {
2658 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2659 if (kind != LV_WRITE_AS && existsInVC) {
2660 // renaming to a name that is already in VC
2662 docstring text = bformat(_("The document %1$s "
2663 "is already registered.\n\n"
2664 "Do you want to choose a new name?"),
2666 docstring const title = (kind == LV_VC_RENAME) ?
2667 _("Rename document?") : _("Copy document?");
2668 docstring const button = (kind == LV_VC_RENAME) ?
2669 _("&Rename") : _("&Copy");
2670 int const ret = Alert::prompt(title, text, 0, 1,
2671 button, _("&Cancel"));
2673 case 0: return renameBuffer(b, docstring(), kind);
2674 case 1: return false;
2679 docstring text = bformat(_("The document %1$s "
2680 "already exists.\n\n"
2681 "Do you want to overwrite that document?"),
2683 int const ret = Alert::prompt(_("Overwrite document?"),
2684 text, 0, 2, _("&Overwrite"),
2685 _("&Rename"), _("&Cancel"));
2688 case 1: return renameBuffer(b, docstring(), kind);
2689 case 2: return false;
2695 case LV_VC_RENAME: {
2696 string msg = b.lyxvc().rename(fname);
2699 message(from_utf8(msg));
2703 string msg = b.lyxvc().copy(fname);
2706 message(from_utf8(msg));
2712 // LyXVC created the file already in case of LV_VC_RENAME or
2713 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2714 // relative paths of included stuff right if we moved e.g. from
2715 // /a/b.lyx to /a/c/b.lyx.
2717 bool const saved = saveBuffer(b, fname);
2724 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2726 FileName fname = b.fileName();
2728 FileDialog dlg(qt_("Choose a filename to export the document as"));
2729 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2732 QString const anyformat = qt_("Guess from extension (*.*)");
2735 vector<Format const *> export_formats;
2736 for (Format const & f : theFormats())
2737 if (f.documentFormat())
2738 export_formats.push_back(&f);
2739 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2740 map<QString, string> fmap;
2743 for (Format const * f : export_formats) {
2744 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2745 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2747 from_ascii(f->extension())));
2748 types << loc_filter;
2749 fmap[loc_filter] = f->name();
2750 if (from_ascii(f->name()) == iformat) {
2751 filter = loc_filter;
2752 ext = f->extension();
2755 string ofname = fname.onlyFileName();
2757 ofname = support::changeExtension(ofname, ext);
2758 FileDialog::Result result =
2759 dlg.save(toqstr(fname.onlyPath().absFileName()),
2763 if (result.first != FileDialog::Chosen)
2767 fname.set(fromqstr(result.second));
2768 if (filter == anyformat)
2769 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2771 fmt_name = fmap[filter];
2772 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2773 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2775 if (fmt_name.empty() || fname.empty())
2778 // fname is now the new Buffer location.
2779 if (FileName(fname).exists()) {
2780 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2781 docstring text = bformat(_("The document %1$s already "
2782 "exists.\n\nDo you want to "
2783 "overwrite that document?"),
2785 int const ret = Alert::prompt(_("Overwrite document?"),
2786 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2789 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2790 case 2: return false;
2794 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2797 return dr.dispatched();
2801 bool GuiView::saveBuffer(Buffer & b)
2803 return saveBuffer(b, FileName());
2807 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2809 if (workArea(b) && workArea(b)->inDialogMode())
2812 if (fn.empty() && b.isUnnamed())
2813 return renameBuffer(b, docstring());
2815 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2817 theSession().lastFiles().add(b.fileName());
2818 theSession().writeFile();
2822 // Switch to this Buffer.
2825 // FIXME: we don't tell the user *WHY* the save failed !!
2826 docstring const file = makeDisplayPath(b.absFileName(), 30);
2827 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2828 "Do you want to rename the document and "
2829 "try again?"), file);
2830 int const ret = Alert::prompt(_("Rename and save?"),
2831 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2834 if (!renameBuffer(b, docstring()))
2843 return saveBuffer(b, fn);
2847 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2849 return closeWorkArea(wa, false);
2853 // We only want to close the buffer if it is not visible in other workareas
2854 // of the same view, nor in other views, and if this is not a child
2855 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2857 Buffer & buf = wa->bufferView().buffer();
2859 bool last_wa = d.countWorkAreasOf(buf) == 1
2860 && !inOtherView(buf) && !buf.parent();
2862 bool close_buffer = last_wa;
2865 if (lyxrc.close_buffer_with_last_view == "yes")
2867 else if (lyxrc.close_buffer_with_last_view == "no")
2868 close_buffer = false;
2871 if (buf.isUnnamed())
2872 file = from_utf8(buf.fileName().onlyFileName());
2874 file = buf.fileName().displayName(30);
2875 docstring const text = bformat(
2876 _("Last view on document %1$s is being closed.\n"
2877 "Would you like to close or hide the document?\n"
2879 "Hidden documents can be displayed back through\n"
2880 "the menu: View->Hidden->...\n"
2882 "To remove this question, set your preference in:\n"
2883 " Tools->Preferences->Look&Feel->UserInterface\n"
2885 int ret = Alert::prompt(_("Close or hide document?"),
2886 text, 0, 1, _("&Close"), _("&Hide"));
2887 close_buffer = (ret == 0);
2891 return closeWorkArea(wa, close_buffer);
2895 bool GuiView::closeBuffer()
2897 GuiWorkArea * wa = currentMainWorkArea();
2898 // coverity complained about this
2899 // it seems unnecessary, but perhaps is worth the check
2900 LASSERT(wa, return false);
2902 setCurrentWorkArea(wa);
2903 Buffer & buf = wa->bufferView().buffer();
2904 return closeWorkArea(wa, !buf.parent());
2908 void GuiView::writeSession() const {
2909 GuiWorkArea const * active_wa = currentMainWorkArea();
2910 for (int i = 0; i < d.splitter_->count(); ++i) {
2911 TabWorkArea * twa = d.tabWorkArea(i);
2912 for (int j = 0; j < twa->count(); ++j) {
2913 GuiWorkArea * wa = twa->workArea(j);
2914 Buffer & buf = wa->bufferView().buffer();
2915 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2921 bool GuiView::closeBufferAll()
2923 // Close the workareas in all other views
2924 QList<int> const ids = guiApp->viewIds();
2925 for (int i = 0; i != ids.size(); ++i) {
2926 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2930 // Close our own workareas
2931 if (!closeWorkAreaAll())
2934 // Now close the hidden buffers. We prevent hidden buffers from being
2935 // dirty, so we can just close them.
2936 theBufferList().closeAll();
2941 bool GuiView::closeWorkAreaAll()
2943 setCurrentWorkArea(currentMainWorkArea());
2945 // We might be in a situation that there is still a tabWorkArea, but
2946 // there are no tabs anymore. This can happen when we get here after a
2947 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2948 // many TabWorkArea's have no documents anymore.
2951 // We have to call count() each time, because it can happen that
2952 // more than one splitter will disappear in one iteration (bug 5998).
2953 while (d.splitter_->count() > empty_twa) {
2954 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2956 if (twa->count() == 0)
2959 setCurrentWorkArea(twa->currentWorkArea());
2960 if (!closeTabWorkArea(twa))
2968 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2973 Buffer & buf = wa->bufferView().buffer();
2975 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2976 Alert::warning(_("Close document"),
2977 _("Document could not be closed because it is being processed by LyX."));
2982 return closeBuffer(buf);
2984 if (!inMultiTabs(wa))
2985 if (!saveBufferIfNeeded(buf, true))
2993 bool GuiView::closeBuffer(Buffer & buf)
2995 // If we are in a close_event all children will be closed in some time,
2996 // so no need to do it here. This will ensure that the children end up
2997 // in the session file in the correct order. If we close the master
2998 // buffer, we can close or release the child buffers here too.
2999 bool success = true;
3001 ListOfBuffers clist = buf.getChildren();
3002 ListOfBuffers::const_iterator it = clist.begin();
3003 ListOfBuffers::const_iterator const bend = clist.end();
3004 for (; it != bend; ++it) {
3005 Buffer * child_buf = *it;
3006 if (theBufferList().isOthersChild(&buf, child_buf)) {
3007 child_buf->setParent(0);
3011 // FIXME: should we look in other tabworkareas?
3012 // ANSWER: I don't think so. I've tested, and if the child is
3013 // open in some other window, it closes without a problem.
3014 GuiWorkArea * child_wa = workArea(*child_buf);
3016 success = closeWorkArea(child_wa, true);
3020 // In this case the child buffer is open but hidden.
3021 // It therefore should not (MUST NOT) be dirty!
3022 LATTEST(child_buf->isClean());
3023 theBufferList().release(child_buf);
3028 // goto bookmark to update bookmark pit.
3029 // FIXME: we should update only the bookmarks related to this buffer!
3030 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3031 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3032 guiApp->gotoBookmark(i+1, false, false);
3034 if (saveBufferIfNeeded(buf, false)) {
3035 buf.removeAutosaveFile();
3036 theBufferList().release(&buf);
3040 // open all children again to avoid a crash because of dangling
3041 // pointers (bug 6603)
3047 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3049 while (twa == d.currentTabWorkArea()) {
3050 twa->setCurrentIndex(twa->count() - 1);
3052 GuiWorkArea * wa = twa->currentWorkArea();
3053 Buffer & b = wa->bufferView().buffer();
3055 // We only want to close the buffer if the same buffer is not visible
3056 // in another view, and if this is not a child and if we are closing
3057 // a view (not a tabgroup).
3058 bool const close_buffer =
3059 !inOtherView(b) && !b.parent() && closing_;
3061 if (!closeWorkArea(wa, close_buffer))
3068 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3070 if (buf.isClean() || buf.paragraphs().empty())
3073 // Switch to this Buffer.
3079 if (buf.isUnnamed()) {
3080 file = from_utf8(buf.fileName().onlyFileName());
3083 FileName filename = buf.fileName();
3085 file = filename.displayName(30);
3086 exists = filename.exists();
3089 // Bring this window to top before asking questions.
3094 if (hiding && buf.isUnnamed()) {
3095 docstring const text = bformat(_("The document %1$s has not been "
3096 "saved yet.\n\nDo you want to save "
3097 "the document?"), file);
3098 ret = Alert::prompt(_("Save new document?"),
3099 text, 0, 1, _("&Save"), _("&Cancel"));
3103 docstring const text = exists ?
3104 bformat(_("The document %1$s has unsaved changes."
3105 "\n\nDo you want to save the document or "
3106 "discard the changes?"), file) :
3107 bformat(_("The document %1$s has not been saved yet."
3108 "\n\nDo you want to save the document or "
3109 "discard it entirely?"), file);
3110 docstring const title = exists ?
3111 _("Save changed document?") : _("Save document?");
3112 ret = Alert::prompt(title, text, 0, 2,
3113 _("&Save"), _("&Discard"), _("&Cancel"));
3118 if (!saveBuffer(buf))
3122 // If we crash after this we could have no autosave file
3123 // but I guess this is really improbable (Jug).
3124 // Sometimes improbable things happen:
3125 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3126 // buf.removeAutosaveFile();
3128 // revert all changes
3139 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3141 Buffer & buf = wa->bufferView().buffer();
3143 for (int i = 0; i != d.splitter_->count(); ++i) {
3144 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3145 if (wa_ && wa_ != wa)
3148 return inOtherView(buf);
3152 bool GuiView::inOtherView(Buffer & buf)
3154 QList<int> const ids = guiApp->viewIds();
3156 for (int i = 0; i != ids.size(); ++i) {
3160 if (guiApp->view(ids[i]).workArea(buf))
3167 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3169 if (!documentBufferView())
3172 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3173 Buffer * const curbuf = &documentBufferView()->buffer();
3174 int nwa = twa->count();
3175 for (int i = 0; i < nwa; ++i) {
3176 if (&workArea(i)->bufferView().buffer() == curbuf) {
3178 if (np == NEXTBUFFER)
3179 next_index = (i == nwa - 1 ? 0 : i + 1);
3181 next_index = (i == 0 ? nwa - 1 : i - 1);
3183 twa->moveTab(i, next_index);
3185 setBuffer(&workArea(next_index)->bufferView().buffer());
3193 /// make sure the document is saved
3194 static bool ensureBufferClean(Buffer * buffer)
3196 LASSERT(buffer, return false);
3197 if (buffer->isClean() && !buffer->isUnnamed())
3200 docstring const file = buffer->fileName().displayName(30);
3203 if (!buffer->isUnnamed()) {
3204 text = bformat(_("The document %1$s has unsaved "
3205 "changes.\n\nDo you want to save "
3206 "the document?"), file);
3207 title = _("Save changed document?");
3210 text = bformat(_("The document %1$s has not been "
3211 "saved yet.\n\nDo you want to save "
3212 "the document?"), file);
3213 title = _("Save new document?");
3215 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3218 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3220 return buffer->isClean() && !buffer->isUnnamed();
3224 bool GuiView::reloadBuffer(Buffer & buf)
3226 currentBufferView()->cursor().reset();
3227 Buffer::ReadStatus status = buf.reload();
3228 return status == Buffer::ReadSuccess;
3232 void GuiView::checkExternallyModifiedBuffers()
3234 BufferList::iterator bit = theBufferList().begin();
3235 BufferList::iterator const bend = theBufferList().end();
3236 for (; bit != bend; ++bit) {
3237 Buffer * buf = *bit;
3238 if (buf->fileName().exists() && buf->isChecksumModified()) {
3239 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3240 " Reload now? Any local changes will be lost."),
3241 from_utf8(buf->absFileName()));
3242 int const ret = Alert::prompt(_("Reload externally changed document?"),
3243 text, 0, 1, _("&Reload"), _("&Cancel"));
3251 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3253 Buffer * buffer = documentBufferView()
3254 ? &(documentBufferView()->buffer()) : 0;
3256 switch (cmd.action()) {
3257 case LFUN_VC_REGISTER:
3258 if (!buffer || !ensureBufferClean(buffer))
3260 if (!buffer->lyxvc().inUse()) {
3261 if (buffer->lyxvc().registrer()) {
3262 reloadBuffer(*buffer);
3263 dr.clearMessageUpdate();
3268 case LFUN_VC_RENAME:
3269 case LFUN_VC_COPY: {
3270 if (!buffer || !ensureBufferClean(buffer))
3272 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3273 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3274 // Some changes are not yet committed.
3275 // We test here and not in getStatus(), since
3276 // this test is expensive.
3278 LyXVC::CommandResult ret =
3279 buffer->lyxvc().checkIn(log);
3281 if (ret == LyXVC::ErrorCommand ||
3282 ret == LyXVC::VCSuccess)
3283 reloadBuffer(*buffer);
3284 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3285 frontend::Alert::error(
3286 _("Revision control error."),
3287 _("Document could not be checked in."));
3291 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3292 LV_VC_RENAME : LV_VC_COPY;
3293 renameBuffer(*buffer, cmd.argument(), kind);
3298 case LFUN_VC_CHECK_IN:
3299 if (!buffer || !ensureBufferClean(buffer))
3301 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3303 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3305 // Only skip reloading if the checkin was cancelled or
3306 // an error occurred before the real checkin VCS command
3307 // was executed, since the VCS might have changed the
3308 // file even if it could not checkin successfully.
3309 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3310 reloadBuffer(*buffer);
3314 case LFUN_VC_CHECK_OUT:
3315 if (!buffer || !ensureBufferClean(buffer))
3317 if (buffer->lyxvc().inUse()) {
3318 dr.setMessage(buffer->lyxvc().checkOut());
3319 reloadBuffer(*buffer);
3323 case LFUN_VC_LOCKING_TOGGLE:
3324 LASSERT(buffer, return);
3325 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3327 if (buffer->lyxvc().inUse()) {
3328 string res = buffer->lyxvc().lockingToggle();
3330 frontend::Alert::error(_("Revision control error."),
3331 _("Error when setting the locking property."));
3334 reloadBuffer(*buffer);
3339 case LFUN_VC_REVERT:
3340 LASSERT(buffer, return);
3341 if (buffer->lyxvc().revert()) {
3342 reloadBuffer(*buffer);
3343 dr.clearMessageUpdate();
3347 case LFUN_VC_UNDO_LAST:
3348 LASSERT(buffer, return);
3349 buffer->lyxvc().undoLast();
3350 reloadBuffer(*buffer);
3351 dr.clearMessageUpdate();
3354 case LFUN_VC_REPO_UPDATE:
3355 LASSERT(buffer, return);
3356 if (ensureBufferClean(buffer)) {
3357 dr.setMessage(buffer->lyxvc().repoUpdate());
3358 checkExternallyModifiedBuffers();
3362 case LFUN_VC_COMMAND: {
3363 string flag = cmd.getArg(0);
3364 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3367 if (contains(flag, 'M')) {
3368 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3371 string path = cmd.getArg(1);
3372 if (contains(path, "$$p") && buffer)
3373 path = subst(path, "$$p", buffer->filePath());
3374 LYXERR(Debug::LYXVC, "Directory: " << path);
3376 if (!pp.isReadableDirectory()) {
3377 lyxerr << _("Directory is not accessible.") << endl;
3380 support::PathChanger p(pp);
3382 string command = cmd.getArg(2);
3383 if (command.empty())
3386 command = subst(command, "$$i", buffer->absFileName());
3387 command = subst(command, "$$p", buffer->filePath());
3389 command = subst(command, "$$m", to_utf8(message));
3390 LYXERR(Debug::LYXVC, "Command: " << command);
3392 one.startscript(Systemcall::Wait, command);
3396 if (contains(flag, 'I'))
3397 buffer->markDirty();
3398 if (contains(flag, 'R'))
3399 reloadBuffer(*buffer);
3404 case LFUN_VC_COMPARE: {
3405 if (cmd.argument().empty()) {
3406 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3410 string rev1 = cmd.getArg(0);
3415 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3418 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3419 f2 = buffer->absFileName();
3421 string rev2 = cmd.getArg(1);
3425 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3429 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3430 f1 << "\n" << f2 << "\n" );
3431 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3432 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3442 void GuiView::openChildDocument(string const & fname)
3444 LASSERT(documentBufferView(), return);
3445 Buffer & buffer = documentBufferView()->buffer();
3446 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3447 documentBufferView()->saveBookmark(false);
3449 if (theBufferList().exists(filename)) {
3450 child = theBufferList().getBuffer(filename);
3453 message(bformat(_("Opening child document %1$s..."),
3454 makeDisplayPath(filename.absFileName())));
3455 child = loadDocument(filename, false);
3457 // Set the parent name of the child document.
3458 // This makes insertion of citations and references in the child work,
3459 // when the target is in the parent or another child document.
3461 child->setParent(&buffer);
3465 bool GuiView::goToFileRow(string const & argument)
3469 size_t i = argument.find_last_of(' ');
3470 if (i != string::npos) {
3471 file_name = os::internal_path(trim(argument.substr(0, i)));
3472 istringstream is(argument.substr(i + 1));
3477 if (i == string::npos) {
3478 LYXERR0("Wrong argument: " << argument);
3482 string const abstmp = package().temp_dir().absFileName();
3483 string const realtmp = package().temp_dir().realPath();
3484 // We have to use os::path_prefix_is() here, instead of
3485 // simply prefixIs(), because the file name comes from
3486 // an external application and may need case adjustment.
3487 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3488 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3489 // Needed by inverse dvi search. If it is a file
3490 // in tmpdir, call the apropriated function.
3491 // If tmpdir is a symlink, we may have the real
3492 // path passed back, so we correct for that.
3493 if (!prefixIs(file_name, abstmp))
3494 file_name = subst(file_name, realtmp, abstmp);
3495 buf = theBufferList().getBufferFromTmp(file_name);
3497 // Must replace extension of the file to be .lyx
3498 // and get full path
3499 FileName const s = fileSearch(string(),
3500 support::changeExtension(file_name, ".lyx"), "lyx");
3501 // Either change buffer or load the file
3502 if (theBufferList().exists(s))
3503 buf = theBufferList().getBuffer(s);
3504 else if (s.exists()) {
3505 buf = loadDocument(s);
3510 _("File does not exist: %1$s"),
3511 makeDisplayPath(file_name)));
3517 _("No buffer for file: %1$s."),
3518 makeDisplayPath(file_name))
3523 bool success = documentBufferView()->setCursorFromRow(row);
3525 LYXERR(Debug::LATEX,
3526 "setCursorFromRow: invalid position for row " << row);
3527 frontend::Alert::error(_("Inverse Search Failed"),
3528 _("Invalid position requested by inverse search.\n"
3529 "You may need to update the viewed document."));
3535 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3537 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3538 menu->exec(QCursor::pos());
3543 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3544 Buffer const * orig, Buffer * clone, string const & format)
3546 Buffer::ExportStatus const status = func(format);
3548 // the cloning operation will have produced a clone of the entire set of
3549 // documents, starting from the master. so we must delete those.
3550 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3552 busyBuffers.remove(orig);
3557 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3558 Buffer const * orig, Buffer * clone, string const & format)
3560 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3562 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3566 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3567 Buffer const * orig, Buffer * clone, string const & format)
3569 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3571 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3575 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3576 Buffer const * orig, Buffer * clone, string const & format)
3578 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3580 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3584 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3585 string const & argument,
3586 Buffer const * used_buffer,
3587 docstring const & msg,
3588 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3589 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3590 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3595 string format = argument;
3597 format = used_buffer->params().getDefaultOutputFormat();
3598 processing_format = format;
3600 progress_->clearMessages();
3603 #if EXPORT_in_THREAD
3604 GuiViewPrivate::busyBuffers.insert(used_buffer);
3605 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3606 if (!cloned_buffer) {
3607 Alert::error(_("Export Error"),
3608 _("Error cloning the Buffer."));
3611 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3616 setPreviewFuture(f);
3617 last_export_format = used_buffer->params().bufferFormat();
3620 // We are asynchronous, so we don't know here anything about the success
3623 Buffer::ExportStatus status;
3625 status = (used_buffer->*syncFunc)(format, true);
3626 } else if (previewFunc) {
3627 status = (used_buffer->*previewFunc)(format);
3630 handleExportStatus(gv_, status, format);
3632 return (status == Buffer::ExportSuccess
3633 || status == Buffer::PreviewSuccess);
3637 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3639 BufferView * bv = currentBufferView();
3640 LASSERT(bv, return);
3642 // Let the current BufferView dispatch its own actions.
3643 bv->dispatch(cmd, dr);
3644 if (dr.dispatched())
3647 // Try with the document BufferView dispatch if any.
3648 BufferView * doc_bv = documentBufferView();
3649 if (doc_bv && doc_bv != bv) {
3650 doc_bv->dispatch(cmd, dr);
3651 if (dr.dispatched())
3655 // Then let the current Cursor dispatch its own actions.
3656 bv->cursor().dispatch(cmd);
3658 // update completion. We do it here and not in
3659 // processKeySym to avoid another redraw just for a
3660 // changed inline completion
3661 if (cmd.origin() == FuncRequest::KEYBOARD) {
3662 if (cmd.action() == LFUN_SELF_INSERT
3663 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3664 updateCompletion(bv->cursor(), true, true);
3665 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3666 updateCompletion(bv->cursor(), false, true);
3668 updateCompletion(bv->cursor(), false, false);
3671 dr = bv->cursor().result();
3675 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3677 BufferView * bv = currentBufferView();
3678 // By default we won't need any update.
3679 dr.screenUpdate(Update::None);
3680 // assume cmd will be dispatched
3681 dr.dispatched(true);
3683 Buffer * doc_buffer = documentBufferView()
3684 ? &(documentBufferView()->buffer()) : 0;
3686 if (cmd.origin() == FuncRequest::TOC) {
3687 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3688 // FIXME: do we need to pass a DispatchResult object here?
3689 toc->doDispatch(bv->cursor(), cmd);
3693 string const argument = to_utf8(cmd.argument());
3695 switch(cmd.action()) {
3696 case LFUN_BUFFER_CHILD_OPEN:
3697 openChildDocument(to_utf8(cmd.argument()));
3700 case LFUN_BUFFER_IMPORT:
3701 importDocument(to_utf8(cmd.argument()));
3704 case LFUN_BUFFER_EXPORT: {
3707 // GCC only sees strfwd.h when building merged
3708 if (::lyx::operator==(cmd.argument(), "custom")) {
3709 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3713 string const dest = cmd.getArg(1);
3714 FileName target_dir;
3715 if (!dest.empty() && FileName::isAbsolute(dest))
3716 target_dir = FileName(support::onlyPath(dest));
3718 target_dir = doc_buffer->fileName().onlyPath();
3720 string const format = (argument.empty() || argument == "default") ?
3721 doc_buffer->params().getDefaultOutputFormat() : argument;
3723 if ((dest.empty() && doc_buffer->isUnnamed())
3724 || !target_dir.isDirWritable()) {
3725 exportBufferAs(*doc_buffer, from_utf8(format));
3728 /* TODO/Review: Is it a problem to also export the children?
3729 See the update_unincluded flag */
3730 d.asyncBufferProcessing(format,
3733 &GuiViewPrivate::exportAndDestroy,
3736 // TODO Inform user about success
3740 case LFUN_BUFFER_EXPORT_AS: {
3741 LASSERT(doc_buffer, break);
3742 docstring f = cmd.argument();
3744 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3745 exportBufferAs(*doc_buffer, f);
3749 case LFUN_BUFFER_UPDATE: {
3750 d.asyncBufferProcessing(argument,
3753 &GuiViewPrivate::compileAndDestroy,
3758 case LFUN_BUFFER_VIEW: {
3759 d.asyncBufferProcessing(argument,
3761 _("Previewing ..."),
3762 &GuiViewPrivate::previewAndDestroy,
3767 case LFUN_MASTER_BUFFER_UPDATE: {
3768 d.asyncBufferProcessing(argument,
3769 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3771 &GuiViewPrivate::compileAndDestroy,
3776 case LFUN_MASTER_BUFFER_VIEW: {
3777 d.asyncBufferProcessing(argument,
3778 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3780 &GuiViewPrivate::previewAndDestroy,
3781 0, &Buffer::preview);
3784 case LFUN_EXPORT_CANCEL: {
3785 Systemcall::killscript();
3788 case LFUN_BUFFER_SWITCH: {
3789 string const file_name = to_utf8(cmd.argument());
3790 if (!FileName::isAbsolute(file_name)) {
3792 dr.setMessage(_("Absolute filename expected."));
3796 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3799 dr.setMessage(_("Document not loaded"));
3803 // Do we open or switch to the buffer in this view ?
3804 if (workArea(*buffer)
3805 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3810 // Look for the buffer in other views
3811 QList<int> const ids = guiApp->viewIds();
3813 for (; i != ids.size(); ++i) {
3814 GuiView & gv = guiApp->view(ids[i]);
3815 if (gv.workArea(*buffer)) {
3817 gv.activateWindow();
3819 gv.setBuffer(buffer);
3824 // If necessary, open a new window as a last resort
3825 if (i == ids.size()) {
3826 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3832 case LFUN_BUFFER_NEXT:
3833 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3836 case LFUN_BUFFER_MOVE_NEXT:
3837 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3840 case LFUN_BUFFER_PREVIOUS:
3841 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3844 case LFUN_BUFFER_MOVE_PREVIOUS:
3845 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3848 case LFUN_BUFFER_CHKTEX:
3849 LASSERT(doc_buffer, break);
3850 doc_buffer->runChktex();
3853 case LFUN_COMMAND_EXECUTE: {
3854 command_execute_ = true;
3855 minibuffer_focus_ = true;
3858 case LFUN_DROP_LAYOUTS_CHOICE:
3859 d.layout_->showPopup();
3862 case LFUN_MENU_OPEN:
3863 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3864 menu->exec(QCursor::pos());
3867 case LFUN_FILE_INSERT:
3868 insertLyXFile(cmd.argument());
3871 case LFUN_FILE_INSERT_PLAINTEXT:
3872 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3873 string const fname = to_utf8(cmd.argument());
3874 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3875 dr.setMessage(_("Absolute filename expected."));
3879 FileName filename(fname);
3880 if (fname.empty()) {
3881 FileDialog dlg(qt_("Select file to insert"));
3883 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3884 QStringList(qt_("All Files (*)")));
3886 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3887 dr.setMessage(_("Canceled."));
3891 filename.set(fromqstr(result.second));
3895 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3896 bv->dispatch(new_cmd, dr);
3901 case LFUN_BUFFER_RELOAD: {
3902 LASSERT(doc_buffer, break);
3905 bool drop = (cmd.argument()=="dump");
3908 if (!drop && !doc_buffer->isClean()) {
3909 docstring const file =
3910 makeDisplayPath(doc_buffer->absFileName(), 20);
3911 if (doc_buffer->notifiesExternalModification()) {
3912 docstring text = _("The current version will be lost. "
3913 "Are you sure you want to load the version on disk "
3914 "of the document %1$s?");
3915 ret = Alert::prompt(_("Reload saved document?"),
3916 bformat(text, file), 1, 1,
3917 _("&Reload"), _("&Cancel"));
3919 docstring text = _("Any changes will be lost. "
3920 "Are you sure you want to revert to the saved version "
3921 "of the document %1$s?");
3922 ret = Alert::prompt(_("Revert to saved document?"),
3923 bformat(text, file), 1, 1,
3924 _("&Revert"), _("&Cancel"));
3929 doc_buffer->markClean();
3930 reloadBuffer(*doc_buffer);
3931 dr.forceBufferUpdate();
3936 case LFUN_BUFFER_WRITE:
3937 LASSERT(doc_buffer, break);
3938 saveBuffer(*doc_buffer);
3941 case LFUN_BUFFER_WRITE_AS:
3942 LASSERT(doc_buffer, break);
3943 renameBuffer(*doc_buffer, cmd.argument());
3946 case LFUN_BUFFER_WRITE_ALL: {
3947 Buffer * first = theBufferList().first();
3950 message(_("Saving all documents..."));
3951 // We cannot use a for loop as the buffer list cycles.
3954 if (!b->isClean()) {
3956 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3958 b = theBufferList().next(b);
3959 } while (b != first);
3960 dr.setMessage(_("All documents saved."));
3964 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3965 LASSERT(doc_buffer, break);
3966 doc_buffer->clearExternalModification();
3969 case LFUN_BUFFER_CLOSE:
3973 case LFUN_BUFFER_CLOSE_ALL:
3977 case LFUN_DEVEL_MODE_TOGGLE:
3978 devel_mode_ = !devel_mode_;
3980 dr.setMessage(_("Developer mode is now enabled."));
3982 dr.setMessage(_("Developer mode is now disabled."));
3985 case LFUN_TOOLBAR_TOGGLE: {
3986 string const name = cmd.getArg(0);
3987 if (GuiToolbar * t = toolbar(name))
3992 case LFUN_TOOLBAR_MOVABLE: {
3993 string const name = cmd.getArg(0);
3995 // toggle (all) toolbars movablility
3996 toolbarsMovable_ = !toolbarsMovable_;
3997 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3998 GuiToolbar * tb = toolbar(ti.name);
3999 if (tb && tb->isMovable() != toolbarsMovable_)
4000 // toggle toolbar movablity if it does not fit lock
4001 // (all) toolbars positions state silent = true, since
4002 // status bar notifications are slow
4005 if (toolbarsMovable_)
4006 dr.setMessage(_("Toolbars unlocked."));
4008 dr.setMessage(_("Toolbars locked."));
4009 } else if (GuiToolbar * t = toolbar(name)) {
4010 // toggle current toolbar movablity
4012 // update lock (all) toolbars positions
4013 updateLockToolbars();
4018 case LFUN_ICON_SIZE: {
4019 QSize size = d.iconSize(cmd.argument());
4021 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4022 size.width(), size.height()));
4026 case LFUN_DIALOG_UPDATE: {
4027 string const name = to_utf8(cmd.argument());
4028 if (name == "prefs" || name == "document")
4029 updateDialog(name, string());
4030 else if (name == "paragraph")
4031 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4032 else if (currentBufferView()) {
4033 Inset * inset = currentBufferView()->editedInset(name);
4034 // Can only update a dialog connected to an existing inset
4036 // FIXME: get rid of this indirection; GuiView ask the inset
4037 // if he is kind enough to update itself...
4038 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4039 //FIXME: pass DispatchResult here?
4040 inset->dispatch(currentBufferView()->cursor(), fr);
4046 case LFUN_DIALOG_TOGGLE: {
4047 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4048 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4049 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4053 case LFUN_DIALOG_DISCONNECT_INSET:
4054 disconnectDialog(to_utf8(cmd.argument()));
4057 case LFUN_DIALOG_HIDE: {
4058 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4062 case LFUN_DIALOG_SHOW: {
4063 string const name = cmd.getArg(0);
4064 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4066 if (name == "character") {
4067 sdata = freefont2string();
4069 showDialog("character", sdata);
4070 } else if (name == "latexlog") {
4071 // gettatus checks that
4072 LATTEST(doc_buffer);
4073 Buffer::LogType type;
4074 string const logfile = doc_buffer->logName(&type);
4076 case Buffer::latexlog:
4079 case Buffer::buildlog:
4080 sdata = "literate ";
4083 sdata += Lexer::quoteString(logfile);
4084 showDialog("log", sdata);
4085 } else if (name == "vclog") {
4086 // getStatus checks that
4087 LATTEST(doc_buffer);
4088 string const sdata2 = "vc " +
4089 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4090 showDialog("log", sdata2);
4091 } else if (name == "symbols") {
4092 sdata = bv->cursor().getEncoding()->name();
4094 showDialog("symbols", sdata);
4096 } else if (name == "prefs" && isFullScreen()) {
4097 lfunUiToggle("fullscreen");
4098 showDialog("prefs", sdata);
4100 showDialog(name, sdata);
4105 dr.setMessage(cmd.argument());
4108 case LFUN_UI_TOGGLE: {
4109 string arg = cmd.getArg(0);
4110 if (!lfunUiToggle(arg)) {
4111 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4112 dr.setMessage(bformat(msg, from_utf8(arg)));
4114 // Make sure the keyboard focus stays in the work area.
4119 case LFUN_VIEW_SPLIT: {
4120 LASSERT(doc_buffer, break);
4121 string const orientation = cmd.getArg(0);
4122 d.splitter_->setOrientation(orientation == "vertical"
4123 ? Qt::Vertical : Qt::Horizontal);
4124 TabWorkArea * twa = addTabWorkArea();
4125 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4126 setCurrentWorkArea(wa);
4129 case LFUN_TAB_GROUP_CLOSE:
4130 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4131 closeTabWorkArea(twa);
4132 d.current_work_area_ = 0;
4133 twa = d.currentTabWorkArea();
4134 // Switch to the next GuiWorkArea in the found TabWorkArea.
4136 // Make sure the work area is up to date.
4137 setCurrentWorkArea(twa->currentWorkArea());
4139 setCurrentWorkArea(0);
4144 case LFUN_VIEW_CLOSE:
4145 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4146 closeWorkArea(twa->currentWorkArea());
4147 d.current_work_area_ = 0;
4148 twa = d.currentTabWorkArea();
4149 // Switch to the next GuiWorkArea in the found TabWorkArea.
4151 // Make sure the work area is up to date.
4152 setCurrentWorkArea(twa->currentWorkArea());
4154 setCurrentWorkArea(0);
4159 case LFUN_COMPLETION_INLINE:
4160 if (d.current_work_area_)
4161 d.current_work_area_->completer().showInline();
4164 case LFUN_COMPLETION_POPUP:
4165 if (d.current_work_area_)
4166 d.current_work_area_->completer().showPopup();
4171 if (d.current_work_area_)
4172 d.current_work_area_->completer().tab();
4175 case LFUN_COMPLETION_CANCEL:
4176 if (d.current_work_area_) {
4177 if (d.current_work_area_->completer().popupVisible())
4178 d.current_work_area_->completer().hidePopup();
4180 d.current_work_area_->completer().hideInline();
4184 case LFUN_COMPLETION_ACCEPT:
4185 if (d.current_work_area_)
4186 d.current_work_area_->completer().activate();
4189 case LFUN_BUFFER_ZOOM_IN:
4190 case LFUN_BUFFER_ZOOM_OUT:
4191 case LFUN_BUFFER_ZOOM: {
4192 if (cmd.argument().empty()) {
4193 if (cmd.action() == LFUN_BUFFER_ZOOM)
4195 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4200 if (cmd.action() == LFUN_BUFFER_ZOOM)
4201 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4202 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4203 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4205 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4208 // Actual zoom value: default zoom + fractional extra value
4209 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4210 if (zoom < static_cast<int>(zoom_min_))
4213 lyxrc.currentZoom = zoom;
4215 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4216 lyxrc.currentZoom, lyxrc.defaultZoom));
4218 // The global QPixmapCache is used in GuiPainter to cache text
4219 // painting so we must reset it.
4220 QPixmapCache::clear();
4221 guiApp->fontLoader().update();
4222 dr.screenUpdate(Update::Force | Update::FitCursor);
4226 case LFUN_VC_REGISTER:
4227 case LFUN_VC_RENAME:
4229 case LFUN_VC_CHECK_IN:
4230 case LFUN_VC_CHECK_OUT:
4231 case LFUN_VC_REPO_UPDATE:
4232 case LFUN_VC_LOCKING_TOGGLE:
4233 case LFUN_VC_REVERT:
4234 case LFUN_VC_UNDO_LAST:
4235 case LFUN_VC_COMMAND:
4236 case LFUN_VC_COMPARE:
4237 dispatchVC(cmd, dr);
4240 case LFUN_SERVER_GOTO_FILE_ROW:
4241 if(goToFileRow(to_utf8(cmd.argument())))
4242 dr.screenUpdate(Update::Force | Update::FitCursor);
4245 case LFUN_LYX_ACTIVATE:
4249 case LFUN_FORWARD_SEARCH: {
4250 // it seems safe to assume we have a document buffer, since
4251 // getStatus wants one.
4252 LATTEST(doc_buffer);
4253 Buffer const * doc_master = doc_buffer->masterBuffer();
4254 FileName const path(doc_master->temppath());
4255 string const texname = doc_master->isChild(doc_buffer)
4256 ? DocFileName(changeExtension(
4257 doc_buffer->absFileName(),
4258 "tex")).mangledFileName()
4259 : doc_buffer->latexName();
4260 string const fulltexname =
4261 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4262 string const mastername =
4263 removeExtension(doc_master->latexName());
4264 FileName const dviname(addName(path.absFileName(),
4265 addExtension(mastername, "dvi")));
4266 FileName const pdfname(addName(path.absFileName(),
4267 addExtension(mastername, "pdf")));
4268 bool const have_dvi = dviname.exists();
4269 bool const have_pdf = pdfname.exists();
4270 if (!have_dvi && !have_pdf) {
4271 dr.setMessage(_("Please, preview the document first."));
4274 string outname = dviname.onlyFileName();
4275 string command = lyxrc.forward_search_dvi;
4276 if (!have_dvi || (have_pdf &&
4277 pdfname.lastModified() > dviname.lastModified())) {
4278 outname = pdfname.onlyFileName();
4279 command = lyxrc.forward_search_pdf;
4282 DocIterator cur = bv->cursor();
4283 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4284 LYXERR(Debug::ACTION, "Forward search: row:" << row
4286 if (row == -1 || command.empty()) {
4287 dr.setMessage(_("Couldn't proceed."));
4290 string texrow = convert<string>(row);
4292 command = subst(command, "$$n", texrow);
4293 command = subst(command, "$$f", fulltexname);
4294 command = subst(command, "$$t", texname);
4295 command = subst(command, "$$o", outname);
4297 PathChanger p(path);
4299 one.startscript(Systemcall::DontWait, command);
4303 case LFUN_SPELLING_CONTINUOUSLY:
4304 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4305 dr.screenUpdate(Update::Force);
4309 // The LFUN must be for one of BufferView, Buffer or Cursor;
4311 dispatchToBufferView(cmd, dr);
4315 // Part of automatic menu appearance feature.
4316 if (isFullScreen()) {
4317 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4321 // Need to update bv because many LFUNs here might have destroyed it
4322 bv = currentBufferView();
4324 // Clear non-empty selections
4325 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4327 Cursor & cur = bv->cursor();
4328 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4329 cur.clearSelection();
4335 bool GuiView::lfunUiToggle(string const & ui_component)
4337 if (ui_component == "scrollbar") {
4338 // hide() is of no help
4339 if (d.current_work_area_->verticalScrollBarPolicy() ==
4340 Qt::ScrollBarAlwaysOff)
4342 d.current_work_area_->setVerticalScrollBarPolicy(
4343 Qt::ScrollBarAsNeeded);
4345 d.current_work_area_->setVerticalScrollBarPolicy(
4346 Qt::ScrollBarAlwaysOff);
4347 } else if (ui_component == "statusbar") {
4348 statusBar()->setVisible(!statusBar()->isVisible());
4349 } else if (ui_component == "menubar") {
4350 menuBar()->setVisible(!menuBar()->isVisible());
4352 if (ui_component == "frame") {
4354 getContentsMargins(&l, &t, &r, &b);
4355 //are the frames in default state?
4356 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4358 setContentsMargins(-2, -2, -2, -2);
4360 setContentsMargins(0, 0, 0, 0);
4363 if (ui_component == "fullscreen") {
4371 void GuiView::toggleFullScreen()
4373 if (isFullScreen()) {
4374 for (int i = 0; i != d.splitter_->count(); ++i)
4375 d.tabWorkArea(i)->setFullScreen(false);
4376 setContentsMargins(0, 0, 0, 0);
4377 setWindowState(windowState() ^ Qt::WindowFullScreen);
4380 statusBar()->show();
4383 hideDialogs("prefs", 0);
4384 for (int i = 0; i != d.splitter_->count(); ++i)
4385 d.tabWorkArea(i)->setFullScreen(true);
4386 setContentsMargins(-2, -2, -2, -2);
4388 setWindowState(windowState() ^ Qt::WindowFullScreen);
4389 if (lyxrc.full_screen_statusbar)
4390 statusBar()->hide();
4391 if (lyxrc.full_screen_menubar)
4393 if (lyxrc.full_screen_toolbars) {
4394 ToolbarMap::iterator end = d.toolbars_.end();
4395 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4400 // give dialogs like the TOC a chance to adapt
4405 Buffer const * GuiView::updateInset(Inset const * inset)
4410 Buffer const * inset_buffer = &(inset->buffer());
4412 for (int i = 0; i != d.splitter_->count(); ++i) {
4413 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4416 Buffer const * buffer = &(wa->bufferView().buffer());
4417 if (inset_buffer == buffer)
4418 wa->scheduleRedraw(true);
4420 return inset_buffer;
4424 void GuiView::restartCaret()
4426 /* When we move around, or type, it's nice to be able to see
4427 * the caret immediately after the keypress.
4429 if (d.current_work_area_)
4430 d.current_work_area_->startBlinkingCaret();
4432 // Take this occasion to update the other GUI elements.
4438 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4440 if (d.current_work_area_)
4441 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4446 // This list should be kept in sync with the list of insets in
4447 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4448 // dialog should have the same name as the inset.
4449 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4450 // docs in LyXAction.cpp.
4452 char const * const dialognames[] = {
4454 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4455 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4456 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4457 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4458 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4459 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4460 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4461 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4463 char const * const * const end_dialognames =
4464 dialognames + (sizeof(dialognames) / sizeof(char *));
4468 cmpCStr(char const * name) : name_(name) {}
4469 bool operator()(char const * other) {
4470 return strcmp(other, name_) == 0;
4477 bool isValidName(string const & name)
4479 return find_if(dialognames, end_dialognames,
4480 cmpCStr(name.c_str())) != end_dialognames;
4486 void GuiView::resetDialogs()
4488 // Make sure that no LFUN uses any GuiView.
4489 guiApp->setCurrentView(0);
4493 constructToolbars();
4494 guiApp->menus().fillMenuBar(menuBar(), this, false);
4495 d.layout_->updateContents(true);
4496 // Now update controls with current buffer.
4497 guiApp->setCurrentView(this);
4503 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4505 if (!isValidName(name))
4508 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4510 if (it != d.dialogs_.end()) {
4512 it->second->hideView();
4513 return it->second.get();
4516 Dialog * dialog = build(name);
4517 d.dialogs_[name].reset(dialog);
4518 if (lyxrc.allow_geometry_session)
4519 dialog->restoreSession();
4526 void GuiView::showDialog(string const & name, string const & sdata,
4529 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4533 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4539 const string name = fromqstr(qname);
4540 const string sdata = fromqstr(qdata);
4544 Dialog * dialog = findOrBuild(name, false);
4546 bool const visible = dialog->isVisibleView();
4547 dialog->showData(sdata);
4548 if (inset && currentBufferView())
4549 currentBufferView()->editInset(name, inset);
4550 // We only set the focus to the new dialog if it was not yet
4551 // visible in order not to change the existing previous behaviour
4553 // activateWindow is needed for floating dockviews
4554 dialog->asQWidget()->raise();
4555 dialog->asQWidget()->activateWindow();
4556 dialog->asQWidget()->setFocus();
4560 catch (ExceptionMessage const & ex) {
4568 bool GuiView::isDialogVisible(string const & name) const
4570 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4571 if (it == d.dialogs_.end())
4573 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4577 void GuiView::hideDialog(string const & name, Inset * inset)
4579 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4580 if (it == d.dialogs_.end())
4584 if (!currentBufferView())
4586 if (inset != currentBufferView()->editedInset(name))
4590 Dialog * const dialog = it->second.get();
4591 if (dialog->isVisibleView())
4593 if (currentBufferView())
4594 currentBufferView()->editInset(name, 0);
4598 void GuiView::disconnectDialog(string const & name)
4600 if (!isValidName(name))
4602 if (currentBufferView())
4603 currentBufferView()->editInset(name, 0);
4607 void GuiView::hideAll() const
4609 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4610 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4612 for(; it != end; ++it)
4613 it->second->hideView();
4617 void GuiView::updateDialogs()
4619 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4620 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4622 for(; it != end; ++it) {
4623 Dialog * dialog = it->second.get();
4625 if (dialog->needBufferOpen() && !documentBufferView())
4626 hideDialog(fromqstr(dialog->name()), 0);
4627 else if (dialog->isVisibleView())
4628 dialog->checkStatus();
4635 Dialog * createDialog(GuiView & lv, string const & name);
4637 // will be replaced by a proper factory...
4638 Dialog * createGuiAbout(GuiView & lv);
4639 Dialog * createGuiBibtex(GuiView & lv);
4640 Dialog * createGuiChanges(GuiView & lv);
4641 Dialog * createGuiCharacter(GuiView & lv);
4642 Dialog * createGuiCitation(GuiView & lv);
4643 Dialog * createGuiCompare(GuiView & lv);
4644 Dialog * createGuiCompareHistory(GuiView & lv);
4645 Dialog * createGuiDelimiter(GuiView & lv);
4646 Dialog * createGuiDocument(GuiView & lv);
4647 Dialog * createGuiErrorList(GuiView & lv);
4648 Dialog * createGuiExternal(GuiView & lv);
4649 Dialog * createGuiGraphics(GuiView & lv);
4650 Dialog * createGuiInclude(GuiView & lv);
4651 Dialog * createGuiIndex(GuiView & lv);
4652 Dialog * createGuiListings(GuiView & lv);
4653 Dialog * createGuiLog(GuiView & lv);
4654 Dialog * createGuiMathMatrix(GuiView & lv);
4655 Dialog * createGuiNote(GuiView & lv);
4656 Dialog * createGuiParagraph(GuiView & lv);
4657 Dialog * createGuiPhantom(GuiView & lv);
4658 Dialog * createGuiPreferences(GuiView & lv);
4659 Dialog * createGuiPrint(GuiView & lv);
4660 Dialog * createGuiPrintindex(GuiView & lv);
4661 Dialog * createGuiRef(GuiView & lv);
4662 Dialog * createGuiSearch(GuiView & lv);
4663 Dialog * createGuiSearchAdv(GuiView & lv);
4664 Dialog * createGuiSendTo(GuiView & lv);
4665 Dialog * createGuiShowFile(GuiView & lv);
4666 Dialog * createGuiSpellchecker(GuiView & lv);
4667 Dialog * createGuiSymbols(GuiView & lv);
4668 Dialog * createGuiTabularCreate(GuiView & lv);
4669 Dialog * createGuiTexInfo(GuiView & lv);
4670 Dialog * createGuiToc(GuiView & lv);
4671 Dialog * createGuiThesaurus(GuiView & lv);
4672 Dialog * createGuiViewSource(GuiView & lv);
4673 Dialog * createGuiWrap(GuiView & lv);
4674 Dialog * createGuiProgressView(GuiView & lv);
4678 Dialog * GuiView::build(string const & name)
4680 LASSERT(isValidName(name), return 0);
4682 Dialog * dialog = createDialog(*this, name);
4686 if (name == "aboutlyx")
4687 return createGuiAbout(*this);
4688 if (name == "bibtex")
4689 return createGuiBibtex(*this);
4690 if (name == "changes")
4691 return createGuiChanges(*this);
4692 if (name == "character")
4693 return createGuiCharacter(*this);
4694 if (name == "citation")
4695 return createGuiCitation(*this);
4696 if (name == "compare")
4697 return createGuiCompare(*this);
4698 if (name == "comparehistory")
4699 return createGuiCompareHistory(*this);
4700 if (name == "document")
4701 return createGuiDocument(*this);
4702 if (name == "errorlist")
4703 return createGuiErrorList(*this);
4704 if (name == "external")
4705 return createGuiExternal(*this);
4707 return createGuiShowFile(*this);
4708 if (name == "findreplace")
4709 return createGuiSearch(*this);
4710 if (name == "findreplaceadv")
4711 return createGuiSearchAdv(*this);
4712 if (name == "graphics")
4713 return createGuiGraphics(*this);
4714 if (name == "include")
4715 return createGuiInclude(*this);
4716 if (name == "index")
4717 return createGuiIndex(*this);
4718 if (name == "index_print")
4719 return createGuiPrintindex(*this);
4720 if (name == "listings")
4721 return createGuiListings(*this);
4723 return createGuiLog(*this);
4724 if (name == "mathdelimiter")
4725 return createGuiDelimiter(*this);
4726 if (name == "mathmatrix")
4727 return createGuiMathMatrix(*this);
4729 return createGuiNote(*this);
4730 if (name == "paragraph")
4731 return createGuiParagraph(*this);
4732 if (name == "phantom")
4733 return createGuiPhantom(*this);
4734 if (name == "prefs")
4735 return createGuiPreferences(*this);
4737 return createGuiRef(*this);
4738 if (name == "sendto")
4739 return createGuiSendTo(*this);
4740 if (name == "spellchecker")
4741 return createGuiSpellchecker(*this);
4742 if (name == "symbols")
4743 return createGuiSymbols(*this);
4744 if (name == "tabularcreate")
4745 return createGuiTabularCreate(*this);
4746 if (name == "texinfo")
4747 return createGuiTexInfo(*this);
4748 if (name == "thesaurus")
4749 return createGuiThesaurus(*this);
4751 return createGuiToc(*this);
4752 if (name == "view-source")
4753 return createGuiViewSource(*this);
4755 return createGuiWrap(*this);
4756 if (name == "progress")
4757 return createGuiProgressView(*this);
4763 SEMenu::SEMenu(QWidget * parent)
4765 QAction * action = addAction(qt_("Disable Shell Escape"));
4766 connect(action, SIGNAL(triggered()),
4767 parent, SLOT(disableShellEscape()));
4770 } // namespace frontend
4773 #include "moc_GuiView.cpp"