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_EXPORT:
1870 && (doc_buffer->parent() != 0
1871 || doc_buffer->hasChildren())
1872 && !d.processing_thread_watcher_.isRunning()
1873 // this launches a dialog, which would be in the wrong Buffer
1874 && !(::lyx::operator==(cmd.argument(), "custom"));
1877 case LFUN_MASTER_BUFFER_UPDATE:
1878 case LFUN_MASTER_BUFFER_VIEW:
1880 && (doc_buffer->parent() != 0
1881 || doc_buffer->hasChildren())
1882 && !d.processing_thread_watcher_.isRunning();
1885 case LFUN_BUFFER_UPDATE:
1886 case LFUN_BUFFER_VIEW: {
1887 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1891 string format = to_utf8(cmd.argument());
1892 if (cmd.argument().empty())
1893 format = doc_buffer->params().getDefaultOutputFormat();
1894 enable = doc_buffer->params().isExportable(format, true);
1898 case LFUN_BUFFER_RELOAD:
1899 enable = doc_buffer && !doc_buffer->isUnnamed()
1900 && doc_buffer->fileName().exists()
1901 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1904 case LFUN_BUFFER_CHILD_OPEN:
1905 enable = doc_buffer != 0;
1908 case LFUN_BUFFER_WRITE:
1909 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1912 //FIXME: This LFUN should be moved to GuiApplication.
1913 case LFUN_BUFFER_WRITE_ALL: {
1914 // We enable the command only if there are some modified buffers
1915 Buffer * first = theBufferList().first();
1920 // We cannot use a for loop as the buffer list is a cycle.
1922 if (!b->isClean()) {
1926 b = theBufferList().next(b);
1927 } while (b != first);
1931 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1932 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1935 case LFUN_BUFFER_EXPORT: {
1936 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1940 return doc_buffer->getStatus(cmd, flag);
1944 case LFUN_BUFFER_EXPORT_AS:
1945 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1950 case LFUN_BUFFER_WRITE_AS:
1951 enable = doc_buffer != 0;
1954 case LFUN_EXPORT_CANCEL:
1955 enable = d.processing_thread_watcher_.isRunning();
1958 case LFUN_BUFFER_CLOSE:
1959 case LFUN_VIEW_CLOSE:
1960 enable = doc_buffer != 0;
1963 case LFUN_BUFFER_CLOSE_ALL:
1964 enable = theBufferList().last() != theBufferList().first();
1967 case LFUN_BUFFER_CHKTEX: {
1968 // hide if we have no checktex command
1969 if (lyxrc.chktex_command.empty()) {
1970 flag.setUnknown(true);
1974 if (!doc_buffer || !doc_buffer->params().isLatex()
1975 || d.processing_thread_watcher_.isRunning()) {
1976 // grey out, don't hide
1984 case LFUN_VIEW_SPLIT:
1985 if (cmd.getArg(0) == "vertical")
1986 enable = doc_buffer && (d.splitter_->count() == 1 ||
1987 d.splitter_->orientation() == Qt::Vertical);
1989 enable = doc_buffer && (d.splitter_->count() == 1 ||
1990 d.splitter_->orientation() == Qt::Horizontal);
1993 case LFUN_TAB_GROUP_CLOSE:
1994 enable = d.tabWorkAreaCount() > 1;
1997 case LFUN_DEVEL_MODE_TOGGLE:
1998 flag.setOnOff(devel_mode_);
2001 case LFUN_TOOLBAR_TOGGLE: {
2002 string const name = cmd.getArg(0);
2003 if (GuiToolbar * t = toolbar(name))
2004 flag.setOnOff(t->isVisible());
2007 docstring const msg =
2008 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2014 case LFUN_TOOLBAR_MOVABLE: {
2015 string const name = cmd.getArg(0);
2016 // use negation since locked == !movable
2018 // toolbar name * locks all toolbars
2019 flag.setOnOff(!toolbarsMovable_);
2020 else if (GuiToolbar * t = toolbar(name))
2021 flag.setOnOff(!(t->isMovable()));
2024 docstring const msg =
2025 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2031 case LFUN_ICON_SIZE:
2032 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2035 case LFUN_DROP_LAYOUTS_CHOICE:
2039 case LFUN_UI_TOGGLE:
2040 flag.setOnOff(isFullScreen());
2043 case LFUN_DIALOG_DISCONNECT_INSET:
2046 case LFUN_DIALOG_HIDE:
2047 // FIXME: should we check if the dialog is shown?
2050 case LFUN_DIALOG_TOGGLE:
2051 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2054 case LFUN_DIALOG_SHOW: {
2055 string const name = cmd.getArg(0);
2057 enable = name == "aboutlyx"
2058 || name == "file" //FIXME: should be removed.
2060 || name == "texinfo"
2061 || name == "progress"
2062 || name == "compare";
2063 else if (name == "character" || name == "symbols"
2064 || name == "mathdelimiter" || name == "mathmatrix") {
2065 if (!buf || buf->isReadonly())
2068 Cursor const & cur = currentBufferView()->cursor();
2069 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2072 else if (name == "latexlog")
2073 enable = FileName(doc_buffer->logName()).isReadableFile();
2074 else if (name == "spellchecker")
2075 enable = theSpellChecker()
2076 && !doc_buffer->isReadonly()
2077 && !doc_buffer->text().empty();
2078 else if (name == "vclog")
2079 enable = doc_buffer->lyxvc().inUse();
2083 case LFUN_DIALOG_UPDATE: {
2084 string const name = cmd.getArg(0);
2086 enable = name == "prefs";
2090 case LFUN_COMMAND_EXECUTE:
2092 case LFUN_MENU_OPEN:
2093 // Nothing to check.
2096 case LFUN_COMPLETION_INLINE:
2097 if (!d.current_work_area_
2098 || !d.current_work_area_->completer().inlinePossible(
2099 currentBufferView()->cursor()))
2103 case LFUN_COMPLETION_POPUP:
2104 if (!d.current_work_area_
2105 || !d.current_work_area_->completer().popupPossible(
2106 currentBufferView()->cursor()))
2111 if (!d.current_work_area_
2112 || !d.current_work_area_->completer().inlinePossible(
2113 currentBufferView()->cursor()))
2117 case LFUN_COMPLETION_ACCEPT:
2118 if (!d.current_work_area_
2119 || (!d.current_work_area_->completer().popupVisible()
2120 && !d.current_work_area_->completer().inlineVisible()
2121 && !d.current_work_area_->completer().completionAvailable()))
2125 case LFUN_COMPLETION_CANCEL:
2126 if (!d.current_work_area_
2127 || (!d.current_work_area_->completer().popupVisible()
2128 && !d.current_work_area_->completer().inlineVisible()))
2132 case LFUN_BUFFER_ZOOM_OUT:
2133 case LFUN_BUFFER_ZOOM_IN: {
2134 // only diff between these two is that the default for ZOOM_OUT
2136 bool const neg_zoom =
2137 convert<int>(cmd.argument()) < 0 ||
2138 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2139 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2140 docstring const msg =
2141 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2145 enable = doc_buffer;
2149 case LFUN_BUFFER_ZOOM: {
2150 bool const less_than_min_zoom =
2151 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2152 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2153 docstring const msg =
2154 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2159 enable = doc_buffer;
2163 case LFUN_BUFFER_MOVE_NEXT:
2164 case LFUN_BUFFER_MOVE_PREVIOUS:
2165 // we do not cycle when moving
2166 case LFUN_BUFFER_NEXT:
2167 case LFUN_BUFFER_PREVIOUS:
2168 // because we cycle, it doesn't matter whether on first or last
2169 enable = (d.currentTabWorkArea()->count() > 1);
2171 case LFUN_BUFFER_SWITCH:
2172 // toggle on the current buffer, but do not toggle off
2173 // the other ones (is that a good idea?)
2175 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2176 flag.setOnOff(true);
2179 case LFUN_VC_REGISTER:
2180 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2182 case LFUN_VC_RENAME:
2183 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2186 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2188 case LFUN_VC_CHECK_IN:
2189 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2191 case LFUN_VC_CHECK_OUT:
2192 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2194 case LFUN_VC_LOCKING_TOGGLE:
2195 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2196 && doc_buffer->lyxvc().lockingToggleEnabled();
2197 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2199 case LFUN_VC_REVERT:
2200 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2201 && !doc_buffer->hasReadonlyFlag();
2203 case LFUN_VC_UNDO_LAST:
2204 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2206 case LFUN_VC_REPO_UPDATE:
2207 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2209 case LFUN_VC_COMMAND: {
2210 if (cmd.argument().empty())
2212 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2216 case LFUN_VC_COMPARE:
2217 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2220 case LFUN_SERVER_GOTO_FILE_ROW:
2221 case LFUN_LYX_ACTIVATE:
2223 case LFUN_FORWARD_SEARCH:
2224 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2227 case LFUN_FILE_INSERT_PLAINTEXT:
2228 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2229 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2232 case LFUN_SPELLING_CONTINUOUSLY:
2233 flag.setOnOff(lyxrc.spellcheck_continuously);
2241 flag.setEnabled(false);
2247 static FileName selectTemplateFile()
2249 FileDialog dlg(qt_("Select template file"));
2250 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2251 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2253 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2254 QStringList(qt_("LyX Documents (*.lyx)")));
2256 if (result.first == FileDialog::Later)
2258 if (result.second.isEmpty())
2260 return FileName(fromqstr(result.second));
2264 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2268 Buffer * newBuffer = 0;
2270 newBuffer = checkAndLoadLyXFile(filename);
2271 } catch (ExceptionMessage const & e) {
2278 message(_("Document not loaded."));
2282 setBuffer(newBuffer);
2283 newBuffer->errors("Parse");
2286 theSession().lastFiles().add(filename);
2287 theSession().writeFile();
2294 void GuiView::openDocument(string const & fname)
2296 string initpath = lyxrc.document_path;
2298 if (documentBufferView()) {
2299 string const trypath = documentBufferView()->buffer().filePath();
2300 // If directory is writeable, use this as default.
2301 if (FileName(trypath).isDirWritable())
2307 if (fname.empty()) {
2308 FileDialog dlg(qt_("Select document to open"));
2309 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2310 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2312 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2313 FileDialog::Result result =
2314 dlg.open(toqstr(initpath), filter);
2316 if (result.first == FileDialog::Later)
2319 filename = fromqstr(result.second);
2321 // check selected filename
2322 if (filename.empty()) {
2323 message(_("Canceled."));
2329 // get absolute path of file and add ".lyx" to the filename if
2331 FileName const fullname =
2332 fileSearch(string(), filename, "lyx", support::may_not_exist);
2333 if (!fullname.empty())
2334 filename = fullname.absFileName();
2336 if (!fullname.onlyPath().isDirectory()) {
2337 Alert::warning(_("Invalid filename"),
2338 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2339 from_utf8(fullname.absFileName())));
2343 // if the file doesn't exist and isn't already open (bug 6645),
2344 // let the user create one
2345 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2346 !LyXVC::file_not_found_hook(fullname)) {
2347 // the user specifically chose this name. Believe him.
2348 Buffer * const b = newFile(filename, string(), true);
2354 docstring const disp_fn = makeDisplayPath(filename);
2355 message(bformat(_("Opening document %1$s..."), disp_fn));
2358 Buffer * buf = loadDocument(fullname);
2360 str2 = bformat(_("Document %1$s opened."), disp_fn);
2361 if (buf->lyxvc().inUse())
2362 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2363 " " + _("Version control detected.");
2365 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2370 // FIXME: clean that
2371 static bool import(GuiView * lv, FileName const & filename,
2372 string const & format, ErrorList & errorList)
2374 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2376 string loader_format;
2377 vector<string> loaders = theConverters().loaders();
2378 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2379 vector<string>::const_iterator it = loaders.begin();
2380 vector<string>::const_iterator en = loaders.end();
2381 for (; it != en; ++it) {
2382 if (!theConverters().isReachable(format, *it))
2385 string const tofile =
2386 support::changeExtension(filename.absFileName(),
2387 theFormats().extension(*it));
2388 if (theConverters().convert(0, filename, FileName(tofile),
2389 filename, format, *it, errorList) != Converters::SUCCESS)
2391 loader_format = *it;
2394 if (loader_format.empty()) {
2395 frontend::Alert::error(_("Couldn't import file"),
2396 bformat(_("No information for importing the format %1$s."),
2397 theFormats().prettyName(format)));
2401 loader_format = format;
2403 if (loader_format == "lyx") {
2404 Buffer * buf = lv->loadDocument(lyxfile);
2408 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2412 bool as_paragraphs = loader_format == "textparagraph";
2413 string filename2 = (loader_format == format) ? filename.absFileName()
2414 : support::changeExtension(filename.absFileName(),
2415 theFormats().extension(loader_format));
2416 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2418 guiApp->setCurrentView(lv);
2419 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2426 void GuiView::importDocument(string const & argument)
2429 string filename = split(argument, format, ' ');
2431 LYXERR(Debug::INFO, format << " file: " << filename);
2433 // need user interaction
2434 if (filename.empty()) {
2435 string initpath = lyxrc.document_path;
2436 if (documentBufferView()) {
2437 string const trypath = documentBufferView()->buffer().filePath();
2438 // If directory is writeable, use this as default.
2439 if (FileName(trypath).isDirWritable())
2443 docstring const text = bformat(_("Select %1$s file to import"),
2444 theFormats().prettyName(format));
2446 FileDialog dlg(toqstr(text));
2447 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2448 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2450 docstring filter = theFormats().prettyName(format);
2453 filter += from_utf8(theFormats().extensions(format));
2456 FileDialog::Result result =
2457 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2459 if (result.first == FileDialog::Later)
2462 filename = fromqstr(result.second);
2464 // check selected filename
2465 if (filename.empty())
2466 message(_("Canceled."));
2469 if (filename.empty())
2472 // get absolute path of file
2473 FileName const fullname(support::makeAbsPath(filename));
2475 // Can happen if the user entered a path into the dialog
2477 if (fullname.onlyFileName().empty()) {
2478 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2479 "Aborting import."),
2480 from_utf8(fullname.absFileName()));
2481 frontend::Alert::error(_("File name error"), msg);
2482 message(_("Canceled."));
2487 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2489 // Check if the document already is open
2490 Buffer * buf = theBufferList().getBuffer(lyxfile);
2493 if (!closeBuffer()) {
2494 message(_("Canceled."));
2499 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2501 // if the file exists already, and we didn't do
2502 // -i lyx thefile.lyx, warn
2503 if (lyxfile.exists() && fullname != lyxfile) {
2505 docstring text = bformat(_("The document %1$s already exists.\n\n"
2506 "Do you want to overwrite that document?"), displaypath);
2507 int const ret = Alert::prompt(_("Overwrite document?"),
2508 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2511 message(_("Canceled."));
2516 message(bformat(_("Importing %1$s..."), displaypath));
2517 ErrorList errorList;
2518 if (import(this, fullname, format, errorList))
2519 message(_("imported."));
2521 message(_("file not imported!"));
2523 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2527 void GuiView::newDocument(string const & filename, bool from_template)
2529 FileName initpath(lyxrc.document_path);
2530 if (documentBufferView()) {
2531 FileName const trypath(documentBufferView()->buffer().filePath());
2532 // If directory is writeable, use this as default.
2533 if (trypath.isDirWritable())
2537 string templatefile;
2538 if (from_template) {
2539 templatefile = selectTemplateFile().absFileName();
2540 if (templatefile.empty())
2545 if (filename.empty())
2546 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2548 b = newFile(filename, templatefile, true);
2553 // If no new document could be created, it is unsure
2554 // whether there is a valid BufferView.
2555 if (currentBufferView())
2556 // Ensure the cursor is correctly positioned on screen.
2557 currentBufferView()->showCursor();
2561 void GuiView::insertLyXFile(docstring const & fname)
2563 BufferView * bv = documentBufferView();
2568 FileName filename(to_utf8(fname));
2569 if (filename.empty()) {
2570 // Launch a file browser
2572 string initpath = lyxrc.document_path;
2573 string const trypath = bv->buffer().filePath();
2574 // If directory is writeable, use this as default.
2575 if (FileName(trypath).isDirWritable())
2579 FileDialog dlg(qt_("Select LyX document to insert"));
2580 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2581 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2583 FileDialog::Result result = dlg.open(toqstr(initpath),
2584 QStringList(qt_("LyX Documents (*.lyx)")));
2586 if (result.first == FileDialog::Later)
2590 filename.set(fromqstr(result.second));
2592 // check selected filename
2593 if (filename.empty()) {
2594 // emit message signal.
2595 message(_("Canceled."));
2600 bv->insertLyXFile(filename);
2601 bv->buffer().errors("Parse");
2605 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2607 FileName fname = b.fileName();
2608 FileName const oldname = fname;
2610 if (!newname.empty()) {
2612 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2614 // Switch to this Buffer.
2617 // No argument? Ask user through dialog.
2619 FileDialog dlg(qt_("Choose a filename to save document as"));
2620 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2621 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2623 if (!isLyXFileName(fname.absFileName()))
2624 fname.changeExtension(".lyx");
2626 FileDialog::Result result =
2627 dlg.save(toqstr(fname.onlyPath().absFileName()),
2628 QStringList(qt_("LyX Documents (*.lyx)")),
2629 toqstr(fname.onlyFileName()));
2631 if (result.first == FileDialog::Later)
2634 fname.set(fromqstr(result.second));
2639 if (!isLyXFileName(fname.absFileName()))
2640 fname.changeExtension(".lyx");
2643 // fname is now the new Buffer location.
2645 // if there is already a Buffer open with this name, we do not want
2646 // to have another one. (the second test makes sure we're not just
2647 // trying to overwrite ourselves, which is fine.)
2648 if (theBufferList().exists(fname) && fname != oldname
2649 && theBufferList().getBuffer(fname) != &b) {
2650 docstring const text =
2651 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2652 "Please close it before attempting to overwrite it.\n"
2653 "Do you want to choose a new filename?"),
2654 from_utf8(fname.absFileName()));
2655 int const ret = Alert::prompt(_("Chosen File Already Open"),
2656 text, 0, 1, _("&Rename"), _("&Cancel"));
2658 case 0: return renameBuffer(b, docstring(), kind);
2659 case 1: return false;
2664 bool const existsLocal = fname.exists();
2665 bool const existsInVC = LyXVC::fileInVC(fname);
2666 if (existsLocal || existsInVC) {
2667 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2668 if (kind != LV_WRITE_AS && existsInVC) {
2669 // renaming to a name that is already in VC
2671 docstring text = bformat(_("The document %1$s "
2672 "is already registered.\n\n"
2673 "Do you want to choose a new name?"),
2675 docstring const title = (kind == LV_VC_RENAME) ?
2676 _("Rename document?") : _("Copy document?");
2677 docstring const button = (kind == LV_VC_RENAME) ?
2678 _("&Rename") : _("&Copy");
2679 int const ret = Alert::prompt(title, text, 0, 1,
2680 button, _("&Cancel"));
2682 case 0: return renameBuffer(b, docstring(), kind);
2683 case 1: return false;
2688 docstring text = bformat(_("The document %1$s "
2689 "already exists.\n\n"
2690 "Do you want to overwrite that document?"),
2692 int const ret = Alert::prompt(_("Overwrite document?"),
2693 text, 0, 2, _("&Overwrite"),
2694 _("&Rename"), _("&Cancel"));
2697 case 1: return renameBuffer(b, docstring(), kind);
2698 case 2: return false;
2704 case LV_VC_RENAME: {
2705 string msg = b.lyxvc().rename(fname);
2708 message(from_utf8(msg));
2712 string msg = b.lyxvc().copy(fname);
2715 message(from_utf8(msg));
2721 // LyXVC created the file already in case of LV_VC_RENAME or
2722 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2723 // relative paths of included stuff right if we moved e.g. from
2724 // /a/b.lyx to /a/c/b.lyx.
2726 bool const saved = saveBuffer(b, fname);
2733 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2735 FileName fname = b.fileName();
2737 FileDialog dlg(qt_("Choose a filename to export the document as"));
2738 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2741 QString const anyformat = qt_("Guess from extension (*.*)");
2744 vector<Format const *> export_formats;
2745 for (Format const & f : theFormats())
2746 if (f.documentFormat())
2747 export_formats.push_back(&f);
2748 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2749 map<QString, string> fmap;
2752 for (Format const * f : export_formats) {
2753 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2754 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2756 from_ascii(f->extension())));
2757 types << loc_filter;
2758 fmap[loc_filter] = f->name();
2759 if (from_ascii(f->name()) == iformat) {
2760 filter = loc_filter;
2761 ext = f->extension();
2764 string ofname = fname.onlyFileName();
2766 ofname = support::changeExtension(ofname, ext);
2767 FileDialog::Result result =
2768 dlg.save(toqstr(fname.onlyPath().absFileName()),
2772 if (result.first != FileDialog::Chosen)
2776 fname.set(fromqstr(result.second));
2777 if (filter == anyformat)
2778 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2780 fmt_name = fmap[filter];
2781 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2782 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2784 if (fmt_name.empty() || fname.empty())
2787 // fname is now the new Buffer location.
2788 if (FileName(fname).exists()) {
2789 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2790 docstring text = bformat(_("The document %1$s already "
2791 "exists.\n\nDo you want to "
2792 "overwrite that document?"),
2794 int const ret = Alert::prompt(_("Overwrite document?"),
2795 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2798 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2799 case 2: return false;
2803 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2806 return dr.dispatched();
2810 bool GuiView::saveBuffer(Buffer & b)
2812 return saveBuffer(b, FileName());
2816 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2818 if (workArea(b) && workArea(b)->inDialogMode())
2821 if (fn.empty() && b.isUnnamed())
2822 return renameBuffer(b, docstring());
2824 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2826 theSession().lastFiles().add(b.fileName());
2827 theSession().writeFile();
2831 // Switch to this Buffer.
2834 // FIXME: we don't tell the user *WHY* the save failed !!
2835 docstring const file = makeDisplayPath(b.absFileName(), 30);
2836 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2837 "Do you want to rename the document and "
2838 "try again?"), file);
2839 int const ret = Alert::prompt(_("Rename and save?"),
2840 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2843 if (!renameBuffer(b, docstring()))
2852 return saveBuffer(b, fn);
2856 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2858 return closeWorkArea(wa, false);
2862 // We only want to close the buffer if it is not visible in other workareas
2863 // of the same view, nor in other views, and if this is not a child
2864 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2866 Buffer & buf = wa->bufferView().buffer();
2868 bool last_wa = d.countWorkAreasOf(buf) == 1
2869 && !inOtherView(buf) && !buf.parent();
2871 bool close_buffer = last_wa;
2874 if (lyxrc.close_buffer_with_last_view == "yes")
2876 else if (lyxrc.close_buffer_with_last_view == "no")
2877 close_buffer = false;
2880 if (buf.isUnnamed())
2881 file = from_utf8(buf.fileName().onlyFileName());
2883 file = buf.fileName().displayName(30);
2884 docstring const text = bformat(
2885 _("Last view on document %1$s is being closed.\n"
2886 "Would you like to close or hide the document?\n"
2888 "Hidden documents can be displayed back through\n"
2889 "the menu: View->Hidden->...\n"
2891 "To remove this question, set your preference in:\n"
2892 " Tools->Preferences->Look&Feel->UserInterface\n"
2894 int ret = Alert::prompt(_("Close or hide document?"),
2895 text, 0, 1, _("&Close"), _("&Hide"));
2896 close_buffer = (ret == 0);
2900 return closeWorkArea(wa, close_buffer);
2904 bool GuiView::closeBuffer()
2906 GuiWorkArea * wa = currentMainWorkArea();
2907 // coverity complained about this
2908 // it seems unnecessary, but perhaps is worth the check
2909 LASSERT(wa, return false);
2911 setCurrentWorkArea(wa);
2912 Buffer & buf = wa->bufferView().buffer();
2913 return closeWorkArea(wa, !buf.parent());
2917 void GuiView::writeSession() const {
2918 GuiWorkArea const * active_wa = currentMainWorkArea();
2919 for (int i = 0; i < d.splitter_->count(); ++i) {
2920 TabWorkArea * twa = d.tabWorkArea(i);
2921 for (int j = 0; j < twa->count(); ++j) {
2922 GuiWorkArea * wa = twa->workArea(j);
2923 Buffer & buf = wa->bufferView().buffer();
2924 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2930 bool GuiView::closeBufferAll()
2932 // Close the workareas in all other views
2933 QList<int> const ids = guiApp->viewIds();
2934 for (int i = 0; i != ids.size(); ++i) {
2935 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2939 // Close our own workareas
2940 if (!closeWorkAreaAll())
2943 // Now close the hidden buffers. We prevent hidden buffers from being
2944 // dirty, so we can just close them.
2945 theBufferList().closeAll();
2950 bool GuiView::closeWorkAreaAll()
2952 setCurrentWorkArea(currentMainWorkArea());
2954 // We might be in a situation that there is still a tabWorkArea, but
2955 // there are no tabs anymore. This can happen when we get here after a
2956 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2957 // many TabWorkArea's have no documents anymore.
2960 // We have to call count() each time, because it can happen that
2961 // more than one splitter will disappear in one iteration (bug 5998).
2962 while (d.splitter_->count() > empty_twa) {
2963 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2965 if (twa->count() == 0)
2968 setCurrentWorkArea(twa->currentWorkArea());
2969 if (!closeTabWorkArea(twa))
2977 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2982 Buffer & buf = wa->bufferView().buffer();
2984 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2985 Alert::warning(_("Close document"),
2986 _("Document could not be closed because it is being processed by LyX."));
2991 return closeBuffer(buf);
2993 if (!inMultiTabs(wa))
2994 if (!saveBufferIfNeeded(buf, true))
3002 bool GuiView::closeBuffer(Buffer & buf)
3004 // If we are in a close_event all children will be closed in some time,
3005 // so no need to do it here. This will ensure that the children end up
3006 // in the session file in the correct order. If we close the master
3007 // buffer, we can close or release the child buffers here too.
3008 bool success = true;
3010 ListOfBuffers clist = buf.getChildren();
3011 ListOfBuffers::const_iterator it = clist.begin();
3012 ListOfBuffers::const_iterator const bend = clist.end();
3013 for (; it != bend; ++it) {
3014 Buffer * child_buf = *it;
3015 if (theBufferList().isOthersChild(&buf, child_buf)) {
3016 child_buf->setParent(0);
3020 // FIXME: should we look in other tabworkareas?
3021 // ANSWER: I don't think so. I've tested, and if the child is
3022 // open in some other window, it closes without a problem.
3023 GuiWorkArea * child_wa = workArea(*child_buf);
3025 success = closeWorkArea(child_wa, true);
3029 // In this case the child buffer is open but hidden.
3030 // It therefore should not (MUST NOT) be dirty!
3031 LATTEST(child_buf->isClean());
3032 theBufferList().release(child_buf);
3037 // goto bookmark to update bookmark pit.
3038 // FIXME: we should update only the bookmarks related to this buffer!
3039 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3040 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3041 guiApp->gotoBookmark(i+1, false, false);
3043 if (saveBufferIfNeeded(buf, false)) {
3044 buf.removeAutosaveFile();
3045 theBufferList().release(&buf);
3049 // open all children again to avoid a crash because of dangling
3050 // pointers (bug 6603)
3056 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3058 while (twa == d.currentTabWorkArea()) {
3059 twa->setCurrentIndex(twa->count() - 1);
3061 GuiWorkArea * wa = twa->currentWorkArea();
3062 Buffer & b = wa->bufferView().buffer();
3064 // We only want to close the buffer if the same buffer is not visible
3065 // in another view, and if this is not a child and if we are closing
3066 // a view (not a tabgroup).
3067 bool const close_buffer =
3068 !inOtherView(b) && !b.parent() && closing_;
3070 if (!closeWorkArea(wa, close_buffer))
3077 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3079 if (buf.isClean() || buf.paragraphs().empty())
3082 // Switch to this Buffer.
3088 if (buf.isUnnamed()) {
3089 file = from_utf8(buf.fileName().onlyFileName());
3092 FileName filename = buf.fileName();
3094 file = filename.displayName(30);
3095 exists = filename.exists();
3098 // Bring this window to top before asking questions.
3103 if (hiding && buf.isUnnamed()) {
3104 docstring const text = bformat(_("The document %1$s has not been "
3105 "saved yet.\n\nDo you want to save "
3106 "the document?"), file);
3107 ret = Alert::prompt(_("Save new document?"),
3108 text, 0, 1, _("&Save"), _("&Cancel"));
3112 docstring const text = exists ?
3113 bformat(_("The document %1$s has unsaved changes."
3114 "\n\nDo you want to save the document or "
3115 "discard the changes?"), file) :
3116 bformat(_("The document %1$s has not been saved yet."
3117 "\n\nDo you want to save the document or "
3118 "discard it entirely?"), file);
3119 docstring const title = exists ?
3120 _("Save changed document?") : _("Save document?");
3121 ret = Alert::prompt(title, text, 0, 2,
3122 _("&Save"), _("&Discard"), _("&Cancel"));
3127 if (!saveBuffer(buf))
3131 // If we crash after this we could have no autosave file
3132 // but I guess this is really improbable (Jug).
3133 // Sometimes improbable things happen:
3134 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3135 // buf.removeAutosaveFile();
3137 // revert all changes
3148 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3150 Buffer & buf = wa->bufferView().buffer();
3152 for (int i = 0; i != d.splitter_->count(); ++i) {
3153 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3154 if (wa_ && wa_ != wa)
3157 return inOtherView(buf);
3161 bool GuiView::inOtherView(Buffer & buf)
3163 QList<int> const ids = guiApp->viewIds();
3165 for (int i = 0; i != ids.size(); ++i) {
3169 if (guiApp->view(ids[i]).workArea(buf))
3176 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3178 if (!documentBufferView())
3181 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3182 Buffer * const curbuf = &documentBufferView()->buffer();
3183 int nwa = twa->count();
3184 for (int i = 0; i < nwa; ++i) {
3185 if (&workArea(i)->bufferView().buffer() == curbuf) {
3187 if (np == NEXTBUFFER)
3188 next_index = (i == nwa - 1 ? 0 : i + 1);
3190 next_index = (i == 0 ? nwa - 1 : i - 1);
3192 twa->moveTab(i, next_index);
3194 setBuffer(&workArea(next_index)->bufferView().buffer());
3202 /// make sure the document is saved
3203 static bool ensureBufferClean(Buffer * buffer)
3205 LASSERT(buffer, return false);
3206 if (buffer->isClean() && !buffer->isUnnamed())
3209 docstring const file = buffer->fileName().displayName(30);
3212 if (!buffer->isUnnamed()) {
3213 text = bformat(_("The document %1$s has unsaved "
3214 "changes.\n\nDo you want to save "
3215 "the document?"), file);
3216 title = _("Save changed document?");
3219 text = bformat(_("The document %1$s has not been "
3220 "saved yet.\n\nDo you want to save "
3221 "the document?"), file);
3222 title = _("Save new document?");
3224 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3227 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3229 return buffer->isClean() && !buffer->isUnnamed();
3233 bool GuiView::reloadBuffer(Buffer & buf)
3235 currentBufferView()->cursor().reset();
3236 Buffer::ReadStatus status = buf.reload();
3237 return status == Buffer::ReadSuccess;
3241 void GuiView::checkExternallyModifiedBuffers()
3243 BufferList::iterator bit = theBufferList().begin();
3244 BufferList::iterator const bend = theBufferList().end();
3245 for (; bit != bend; ++bit) {
3246 Buffer * buf = *bit;
3247 if (buf->fileName().exists() && buf->isChecksumModified()) {
3248 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3249 " Reload now? Any local changes will be lost."),
3250 from_utf8(buf->absFileName()));
3251 int const ret = Alert::prompt(_("Reload externally changed document?"),
3252 text, 0, 1, _("&Reload"), _("&Cancel"));
3260 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3262 Buffer * buffer = documentBufferView()
3263 ? &(documentBufferView()->buffer()) : 0;
3265 switch (cmd.action()) {
3266 case LFUN_VC_REGISTER:
3267 if (!buffer || !ensureBufferClean(buffer))
3269 if (!buffer->lyxvc().inUse()) {
3270 if (buffer->lyxvc().registrer()) {
3271 reloadBuffer(*buffer);
3272 dr.clearMessageUpdate();
3277 case LFUN_VC_RENAME:
3278 case LFUN_VC_COPY: {
3279 if (!buffer || !ensureBufferClean(buffer))
3281 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3282 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3283 // Some changes are not yet committed.
3284 // We test here and not in getStatus(), since
3285 // this test is expensive.
3287 LyXVC::CommandResult ret =
3288 buffer->lyxvc().checkIn(log);
3290 if (ret == LyXVC::ErrorCommand ||
3291 ret == LyXVC::VCSuccess)
3292 reloadBuffer(*buffer);
3293 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3294 frontend::Alert::error(
3295 _("Revision control error."),
3296 _("Document could not be checked in."));
3300 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3301 LV_VC_RENAME : LV_VC_COPY;
3302 renameBuffer(*buffer, cmd.argument(), kind);
3307 case LFUN_VC_CHECK_IN:
3308 if (!buffer || !ensureBufferClean(buffer))
3310 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3312 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3314 // Only skip reloading if the checkin was cancelled or
3315 // an error occurred before the real checkin VCS command
3316 // was executed, since the VCS might have changed the
3317 // file even if it could not checkin successfully.
3318 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3319 reloadBuffer(*buffer);
3323 case LFUN_VC_CHECK_OUT:
3324 if (!buffer || !ensureBufferClean(buffer))
3326 if (buffer->lyxvc().inUse()) {
3327 dr.setMessage(buffer->lyxvc().checkOut());
3328 reloadBuffer(*buffer);
3332 case LFUN_VC_LOCKING_TOGGLE:
3333 LASSERT(buffer, return);
3334 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3336 if (buffer->lyxvc().inUse()) {
3337 string res = buffer->lyxvc().lockingToggle();
3339 frontend::Alert::error(_("Revision control error."),
3340 _("Error when setting the locking property."));
3343 reloadBuffer(*buffer);
3348 case LFUN_VC_REVERT:
3349 LASSERT(buffer, return);
3350 if (buffer->lyxvc().revert()) {
3351 reloadBuffer(*buffer);
3352 dr.clearMessageUpdate();
3356 case LFUN_VC_UNDO_LAST:
3357 LASSERT(buffer, return);
3358 buffer->lyxvc().undoLast();
3359 reloadBuffer(*buffer);
3360 dr.clearMessageUpdate();
3363 case LFUN_VC_REPO_UPDATE:
3364 LASSERT(buffer, return);
3365 if (ensureBufferClean(buffer)) {
3366 dr.setMessage(buffer->lyxvc().repoUpdate());
3367 checkExternallyModifiedBuffers();
3371 case LFUN_VC_COMMAND: {
3372 string flag = cmd.getArg(0);
3373 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3376 if (contains(flag, 'M')) {
3377 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3380 string path = cmd.getArg(1);
3381 if (contains(path, "$$p") && buffer)
3382 path = subst(path, "$$p", buffer->filePath());
3383 LYXERR(Debug::LYXVC, "Directory: " << path);
3385 if (!pp.isReadableDirectory()) {
3386 lyxerr << _("Directory is not accessible.") << endl;
3389 support::PathChanger p(pp);
3391 string command = cmd.getArg(2);
3392 if (command.empty())
3395 command = subst(command, "$$i", buffer->absFileName());
3396 command = subst(command, "$$p", buffer->filePath());
3398 command = subst(command, "$$m", to_utf8(message));
3399 LYXERR(Debug::LYXVC, "Command: " << command);
3401 one.startscript(Systemcall::Wait, command);
3405 if (contains(flag, 'I'))
3406 buffer->markDirty();
3407 if (contains(flag, 'R'))
3408 reloadBuffer(*buffer);
3413 case LFUN_VC_COMPARE: {
3414 if (cmd.argument().empty()) {
3415 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3419 string rev1 = cmd.getArg(0);
3424 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3427 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3428 f2 = buffer->absFileName();
3430 string rev2 = cmd.getArg(1);
3434 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3438 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3439 f1 << "\n" << f2 << "\n" );
3440 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3441 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3451 void GuiView::openChildDocument(string const & fname)
3453 LASSERT(documentBufferView(), return);
3454 Buffer & buffer = documentBufferView()->buffer();
3455 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3456 documentBufferView()->saveBookmark(false);
3458 if (theBufferList().exists(filename)) {
3459 child = theBufferList().getBuffer(filename);
3462 message(bformat(_("Opening child document %1$s..."),
3463 makeDisplayPath(filename.absFileName())));
3464 child = loadDocument(filename, false);
3466 // Set the parent name of the child document.
3467 // This makes insertion of citations and references in the child work,
3468 // when the target is in the parent or another child document.
3470 child->setParent(&buffer);
3474 bool GuiView::goToFileRow(string const & argument)
3478 size_t i = argument.find_last_of(' ');
3479 if (i != string::npos) {
3480 file_name = os::internal_path(trim(argument.substr(0, i)));
3481 istringstream is(argument.substr(i + 1));
3486 if (i == string::npos) {
3487 LYXERR0("Wrong argument: " << argument);
3491 string const abstmp = package().temp_dir().absFileName();
3492 string const realtmp = package().temp_dir().realPath();
3493 // We have to use os::path_prefix_is() here, instead of
3494 // simply prefixIs(), because the file name comes from
3495 // an external application and may need case adjustment.
3496 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3497 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3498 // Needed by inverse dvi search. If it is a file
3499 // in tmpdir, call the apropriated function.
3500 // If tmpdir is a symlink, we may have the real
3501 // path passed back, so we correct for that.
3502 if (!prefixIs(file_name, abstmp))
3503 file_name = subst(file_name, realtmp, abstmp);
3504 buf = theBufferList().getBufferFromTmp(file_name);
3506 // Must replace extension of the file to be .lyx
3507 // and get full path
3508 FileName const s = fileSearch(string(),
3509 support::changeExtension(file_name, ".lyx"), "lyx");
3510 // Either change buffer or load the file
3511 if (theBufferList().exists(s))
3512 buf = theBufferList().getBuffer(s);
3513 else if (s.exists()) {
3514 buf = loadDocument(s);
3519 _("File does not exist: %1$s"),
3520 makeDisplayPath(file_name)));
3526 _("No buffer for file: %1$s."),
3527 makeDisplayPath(file_name))
3532 bool success = documentBufferView()->setCursorFromRow(row);
3534 LYXERR(Debug::LATEX,
3535 "setCursorFromRow: invalid position for row " << row);
3536 frontend::Alert::error(_("Inverse Search Failed"),
3537 _("Invalid position requested by inverse search.\n"
3538 "You may need to update the viewed document."));
3544 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3546 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3547 menu->exec(QCursor::pos());
3552 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3553 Buffer const * orig, Buffer * clone, string const & format)
3555 Buffer::ExportStatus const status = func(format);
3557 // the cloning operation will have produced a clone of the entire set of
3558 // documents, starting from the master. so we must delete those.
3559 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3561 busyBuffers.remove(orig);
3566 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
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, true), orig, clone, format);
3575 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3576 Buffer const * orig, Buffer * clone, string const & format)
3578 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3580 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3584 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3585 Buffer const * orig, Buffer * clone, string const & format)
3587 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3589 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3593 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3594 string const & argument,
3595 Buffer const * used_buffer,
3596 docstring const & msg,
3597 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3598 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3599 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3604 string format = argument;
3606 format = used_buffer->params().getDefaultOutputFormat();
3607 processing_format = format;
3609 progress_->clearMessages();
3612 #if EXPORT_in_THREAD
3613 GuiViewPrivate::busyBuffers.insert(used_buffer);
3614 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3615 if (!cloned_buffer) {
3616 Alert::error(_("Export Error"),
3617 _("Error cloning the Buffer."));
3620 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3625 setPreviewFuture(f);
3626 last_export_format = used_buffer->params().bufferFormat();
3629 // We are asynchronous, so we don't know here anything about the success
3632 Buffer::ExportStatus status;
3634 status = (used_buffer->*syncFunc)(format, true);
3635 } else if (previewFunc) {
3636 status = (used_buffer->*previewFunc)(format);
3639 handleExportStatus(gv_, status, format);
3641 return (status == Buffer::ExportSuccess
3642 || status == Buffer::PreviewSuccess);
3646 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3648 BufferView * bv = currentBufferView();
3649 LASSERT(bv, return);
3651 // Let the current BufferView dispatch its own actions.
3652 bv->dispatch(cmd, dr);
3653 if (dr.dispatched())
3656 // Try with the document BufferView dispatch if any.
3657 BufferView * doc_bv = documentBufferView();
3658 if (doc_bv && doc_bv != bv) {
3659 doc_bv->dispatch(cmd, dr);
3660 if (dr.dispatched())
3664 // Then let the current Cursor dispatch its own actions.
3665 bv->cursor().dispatch(cmd);
3667 // update completion. We do it here and not in
3668 // processKeySym to avoid another redraw just for a
3669 // changed inline completion
3670 if (cmd.origin() == FuncRequest::KEYBOARD) {
3671 if (cmd.action() == LFUN_SELF_INSERT
3672 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3673 updateCompletion(bv->cursor(), true, true);
3674 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3675 updateCompletion(bv->cursor(), false, true);
3677 updateCompletion(bv->cursor(), false, false);
3680 dr = bv->cursor().result();
3684 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3686 BufferView * bv = currentBufferView();
3687 // By default we won't need any update.
3688 dr.screenUpdate(Update::None);
3689 // assume cmd will be dispatched
3690 dr.dispatched(true);
3692 Buffer * doc_buffer = documentBufferView()
3693 ? &(documentBufferView()->buffer()) : 0;
3695 if (cmd.origin() == FuncRequest::TOC) {
3696 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3697 // FIXME: do we need to pass a DispatchResult object here?
3698 toc->doDispatch(bv->cursor(), cmd);
3702 string const argument = to_utf8(cmd.argument());
3704 switch(cmd.action()) {
3705 case LFUN_BUFFER_CHILD_OPEN:
3706 openChildDocument(to_utf8(cmd.argument()));
3709 case LFUN_BUFFER_IMPORT:
3710 importDocument(to_utf8(cmd.argument()));
3713 case LFUN_MASTER_BUFFER_EXPORT:
3715 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3717 case LFUN_BUFFER_EXPORT: {
3720 // GCC only sees strfwd.h when building merged
3721 if (::lyx::operator==(cmd.argument(), "custom")) {
3722 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3723 // so the following test should not be needed.
3724 // In principle, we could try to switch to such a view...
3725 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3726 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3730 string const dest = cmd.getArg(1);
3731 FileName target_dir;
3732 if (!dest.empty() && FileName::isAbsolute(dest))
3733 target_dir = FileName(support::onlyPath(dest));
3735 target_dir = doc_buffer->fileName().onlyPath();
3737 string const format = (argument.empty() || argument == "default") ?
3738 doc_buffer->params().getDefaultOutputFormat() : argument;
3740 if ((dest.empty() && doc_buffer->isUnnamed())
3741 || !target_dir.isDirWritable()) {
3742 exportBufferAs(*doc_buffer, from_utf8(format));
3745 /* TODO/Review: Is it a problem to also export the children?
3746 See the update_unincluded flag */
3747 d.asyncBufferProcessing(format,
3750 &GuiViewPrivate::exportAndDestroy,
3753 // TODO Inform user about success
3757 case LFUN_BUFFER_EXPORT_AS: {
3758 LASSERT(doc_buffer, break);
3759 docstring f = cmd.argument();
3761 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3762 exportBufferAs(*doc_buffer, f);
3766 case LFUN_BUFFER_UPDATE: {
3767 d.asyncBufferProcessing(argument,
3770 &GuiViewPrivate::compileAndDestroy,
3775 case LFUN_BUFFER_VIEW: {
3776 d.asyncBufferProcessing(argument,
3778 _("Previewing ..."),
3779 &GuiViewPrivate::previewAndDestroy,
3784 case LFUN_MASTER_BUFFER_UPDATE: {
3785 d.asyncBufferProcessing(argument,
3786 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3788 &GuiViewPrivate::compileAndDestroy,
3793 case LFUN_MASTER_BUFFER_VIEW: {
3794 d.asyncBufferProcessing(argument,
3795 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3797 &GuiViewPrivate::previewAndDestroy,
3798 0, &Buffer::preview);
3801 case LFUN_EXPORT_CANCEL: {
3802 Systemcall::killscript();
3805 case LFUN_BUFFER_SWITCH: {
3806 string const file_name = to_utf8(cmd.argument());
3807 if (!FileName::isAbsolute(file_name)) {
3809 dr.setMessage(_("Absolute filename expected."));
3813 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3816 dr.setMessage(_("Document not loaded"));
3820 // Do we open or switch to the buffer in this view ?
3821 if (workArea(*buffer)
3822 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3827 // Look for the buffer in other views
3828 QList<int> const ids = guiApp->viewIds();
3830 for (; i != ids.size(); ++i) {
3831 GuiView & gv = guiApp->view(ids[i]);
3832 if (gv.workArea(*buffer)) {
3834 gv.activateWindow();
3836 gv.setBuffer(buffer);
3841 // If necessary, open a new window as a last resort
3842 if (i == ids.size()) {
3843 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3849 case LFUN_BUFFER_NEXT:
3850 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3853 case LFUN_BUFFER_MOVE_NEXT:
3854 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3857 case LFUN_BUFFER_PREVIOUS:
3858 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3861 case LFUN_BUFFER_MOVE_PREVIOUS:
3862 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3865 case LFUN_BUFFER_CHKTEX:
3866 LASSERT(doc_buffer, break);
3867 doc_buffer->runChktex();
3870 case LFUN_COMMAND_EXECUTE: {
3871 command_execute_ = true;
3872 minibuffer_focus_ = true;
3875 case LFUN_DROP_LAYOUTS_CHOICE:
3876 d.layout_->showPopup();
3879 case LFUN_MENU_OPEN:
3880 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3881 menu->exec(QCursor::pos());
3884 case LFUN_FILE_INSERT:
3885 insertLyXFile(cmd.argument());
3888 case LFUN_FILE_INSERT_PLAINTEXT:
3889 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3890 string const fname = to_utf8(cmd.argument());
3891 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3892 dr.setMessage(_("Absolute filename expected."));
3896 FileName filename(fname);
3897 if (fname.empty()) {
3898 FileDialog dlg(qt_("Select file to insert"));
3900 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3901 QStringList(qt_("All Files (*)")));
3903 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3904 dr.setMessage(_("Canceled."));
3908 filename.set(fromqstr(result.second));
3912 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3913 bv->dispatch(new_cmd, dr);
3918 case LFUN_BUFFER_RELOAD: {
3919 LASSERT(doc_buffer, break);
3922 bool drop = (cmd.argument()=="dump");
3925 if (!drop && !doc_buffer->isClean()) {
3926 docstring const file =
3927 makeDisplayPath(doc_buffer->absFileName(), 20);
3928 if (doc_buffer->notifiesExternalModification()) {
3929 docstring text = _("The current version will be lost. "
3930 "Are you sure you want to load the version on disk "
3931 "of the document %1$s?");
3932 ret = Alert::prompt(_("Reload saved document?"),
3933 bformat(text, file), 1, 1,
3934 _("&Reload"), _("&Cancel"));
3936 docstring text = _("Any changes will be lost. "
3937 "Are you sure you want to revert to the saved version "
3938 "of the document %1$s?");
3939 ret = Alert::prompt(_("Revert to saved document?"),
3940 bformat(text, file), 1, 1,
3941 _("&Revert"), _("&Cancel"));
3946 doc_buffer->markClean();
3947 reloadBuffer(*doc_buffer);
3948 dr.forceBufferUpdate();
3953 case LFUN_BUFFER_WRITE:
3954 LASSERT(doc_buffer, break);
3955 saveBuffer(*doc_buffer);
3958 case LFUN_BUFFER_WRITE_AS:
3959 LASSERT(doc_buffer, break);
3960 renameBuffer(*doc_buffer, cmd.argument());
3963 case LFUN_BUFFER_WRITE_ALL: {
3964 Buffer * first = theBufferList().first();
3967 message(_("Saving all documents..."));
3968 // We cannot use a for loop as the buffer list cycles.
3971 if (!b->isClean()) {
3973 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3975 b = theBufferList().next(b);
3976 } while (b != first);
3977 dr.setMessage(_("All documents saved."));
3981 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3982 LASSERT(doc_buffer, break);
3983 doc_buffer->clearExternalModification();
3986 case LFUN_BUFFER_CLOSE:
3990 case LFUN_BUFFER_CLOSE_ALL:
3994 case LFUN_DEVEL_MODE_TOGGLE:
3995 devel_mode_ = !devel_mode_;
3997 dr.setMessage(_("Developer mode is now enabled."));
3999 dr.setMessage(_("Developer mode is now disabled."));
4002 case LFUN_TOOLBAR_TOGGLE: {
4003 string const name = cmd.getArg(0);
4004 if (GuiToolbar * t = toolbar(name))
4009 case LFUN_TOOLBAR_MOVABLE: {
4010 string const name = cmd.getArg(0);
4012 // toggle (all) toolbars movablility
4013 toolbarsMovable_ = !toolbarsMovable_;
4014 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4015 GuiToolbar * tb = toolbar(ti.name);
4016 if (tb && tb->isMovable() != toolbarsMovable_)
4017 // toggle toolbar movablity if it does not fit lock
4018 // (all) toolbars positions state silent = true, since
4019 // status bar notifications are slow
4022 if (toolbarsMovable_)
4023 dr.setMessage(_("Toolbars unlocked."));
4025 dr.setMessage(_("Toolbars locked."));
4026 } else if (GuiToolbar * t = toolbar(name)) {
4027 // toggle current toolbar movablity
4029 // update lock (all) toolbars positions
4030 updateLockToolbars();
4035 case LFUN_ICON_SIZE: {
4036 QSize size = d.iconSize(cmd.argument());
4038 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4039 size.width(), size.height()));
4043 case LFUN_DIALOG_UPDATE: {
4044 string const name = to_utf8(cmd.argument());
4045 if (name == "prefs" || name == "document")
4046 updateDialog(name, string());
4047 else if (name == "paragraph")
4048 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4049 else if (currentBufferView()) {
4050 Inset * inset = currentBufferView()->editedInset(name);
4051 // Can only update a dialog connected to an existing inset
4053 // FIXME: get rid of this indirection; GuiView ask the inset
4054 // if he is kind enough to update itself...
4055 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4056 //FIXME: pass DispatchResult here?
4057 inset->dispatch(currentBufferView()->cursor(), fr);
4063 case LFUN_DIALOG_TOGGLE: {
4064 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4065 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4066 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4070 case LFUN_DIALOG_DISCONNECT_INSET:
4071 disconnectDialog(to_utf8(cmd.argument()));
4074 case LFUN_DIALOG_HIDE: {
4075 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4079 case LFUN_DIALOG_SHOW: {
4080 string const name = cmd.getArg(0);
4081 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4083 if (name == "character") {
4084 sdata = freefont2string();
4086 showDialog("character", sdata);
4087 } else if (name == "latexlog") {
4088 // gettatus checks that
4089 LATTEST(doc_buffer);
4090 Buffer::LogType type;
4091 string const logfile = doc_buffer->logName(&type);
4093 case Buffer::latexlog:
4096 case Buffer::buildlog:
4097 sdata = "literate ";
4100 sdata += Lexer::quoteString(logfile);
4101 showDialog("log", sdata);
4102 } else if (name == "vclog") {
4103 // getStatus checks that
4104 LATTEST(doc_buffer);
4105 string const sdata2 = "vc " +
4106 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4107 showDialog("log", sdata2);
4108 } else if (name == "symbols") {
4109 sdata = bv->cursor().getEncoding()->name();
4111 showDialog("symbols", sdata);
4113 } else if (name == "prefs" && isFullScreen()) {
4114 lfunUiToggle("fullscreen");
4115 showDialog("prefs", sdata);
4117 showDialog(name, sdata);
4122 dr.setMessage(cmd.argument());
4125 case LFUN_UI_TOGGLE: {
4126 string arg = cmd.getArg(0);
4127 if (!lfunUiToggle(arg)) {
4128 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4129 dr.setMessage(bformat(msg, from_utf8(arg)));
4131 // Make sure the keyboard focus stays in the work area.
4136 case LFUN_VIEW_SPLIT: {
4137 LASSERT(doc_buffer, break);
4138 string const orientation = cmd.getArg(0);
4139 d.splitter_->setOrientation(orientation == "vertical"
4140 ? Qt::Vertical : Qt::Horizontal);
4141 TabWorkArea * twa = addTabWorkArea();
4142 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4143 setCurrentWorkArea(wa);
4146 case LFUN_TAB_GROUP_CLOSE:
4147 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4148 closeTabWorkArea(twa);
4149 d.current_work_area_ = 0;
4150 twa = d.currentTabWorkArea();
4151 // Switch to the next GuiWorkArea in the found TabWorkArea.
4153 // Make sure the work area is up to date.
4154 setCurrentWorkArea(twa->currentWorkArea());
4156 setCurrentWorkArea(0);
4161 case LFUN_VIEW_CLOSE:
4162 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4163 closeWorkArea(twa->currentWorkArea());
4164 d.current_work_area_ = 0;
4165 twa = d.currentTabWorkArea();
4166 // Switch to the next GuiWorkArea in the found TabWorkArea.
4168 // Make sure the work area is up to date.
4169 setCurrentWorkArea(twa->currentWorkArea());
4171 setCurrentWorkArea(0);
4176 case LFUN_COMPLETION_INLINE:
4177 if (d.current_work_area_)
4178 d.current_work_area_->completer().showInline();
4181 case LFUN_COMPLETION_POPUP:
4182 if (d.current_work_area_)
4183 d.current_work_area_->completer().showPopup();
4188 if (d.current_work_area_)
4189 d.current_work_area_->completer().tab();
4192 case LFUN_COMPLETION_CANCEL:
4193 if (d.current_work_area_) {
4194 if (d.current_work_area_->completer().popupVisible())
4195 d.current_work_area_->completer().hidePopup();
4197 d.current_work_area_->completer().hideInline();
4201 case LFUN_COMPLETION_ACCEPT:
4202 if (d.current_work_area_)
4203 d.current_work_area_->completer().activate();
4206 case LFUN_BUFFER_ZOOM_IN:
4207 case LFUN_BUFFER_ZOOM_OUT:
4208 case LFUN_BUFFER_ZOOM: {
4209 if (cmd.argument().empty()) {
4210 if (cmd.action() == LFUN_BUFFER_ZOOM)
4212 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4217 if (cmd.action() == LFUN_BUFFER_ZOOM)
4218 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4219 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4220 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4222 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4225 // Actual zoom value: default zoom + fractional extra value
4226 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4227 if (zoom < static_cast<int>(zoom_min_))
4230 lyxrc.currentZoom = zoom;
4232 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4233 lyxrc.currentZoom, lyxrc.defaultZoom));
4235 // The global QPixmapCache is used in GuiPainter to cache text
4236 // painting so we must reset it.
4237 QPixmapCache::clear();
4238 guiApp->fontLoader().update();
4239 dr.screenUpdate(Update::Force | Update::FitCursor);
4243 case LFUN_VC_REGISTER:
4244 case LFUN_VC_RENAME:
4246 case LFUN_VC_CHECK_IN:
4247 case LFUN_VC_CHECK_OUT:
4248 case LFUN_VC_REPO_UPDATE:
4249 case LFUN_VC_LOCKING_TOGGLE:
4250 case LFUN_VC_REVERT:
4251 case LFUN_VC_UNDO_LAST:
4252 case LFUN_VC_COMMAND:
4253 case LFUN_VC_COMPARE:
4254 dispatchVC(cmd, dr);
4257 case LFUN_SERVER_GOTO_FILE_ROW:
4258 if(goToFileRow(to_utf8(cmd.argument())))
4259 dr.screenUpdate(Update::Force | Update::FitCursor);
4262 case LFUN_LYX_ACTIVATE:
4266 case LFUN_FORWARD_SEARCH: {
4267 // it seems safe to assume we have a document buffer, since
4268 // getStatus wants one.
4269 LATTEST(doc_buffer);
4270 Buffer const * doc_master = doc_buffer->masterBuffer();
4271 FileName const path(doc_master->temppath());
4272 string const texname = doc_master->isChild(doc_buffer)
4273 ? DocFileName(changeExtension(
4274 doc_buffer->absFileName(),
4275 "tex")).mangledFileName()
4276 : doc_buffer->latexName();
4277 string const fulltexname =
4278 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4279 string const mastername =
4280 removeExtension(doc_master->latexName());
4281 FileName const dviname(addName(path.absFileName(),
4282 addExtension(mastername, "dvi")));
4283 FileName const pdfname(addName(path.absFileName(),
4284 addExtension(mastername, "pdf")));
4285 bool const have_dvi = dviname.exists();
4286 bool const have_pdf = pdfname.exists();
4287 if (!have_dvi && !have_pdf) {
4288 dr.setMessage(_("Please, preview the document first."));
4291 string outname = dviname.onlyFileName();
4292 string command = lyxrc.forward_search_dvi;
4293 if (!have_dvi || (have_pdf &&
4294 pdfname.lastModified() > dviname.lastModified())) {
4295 outname = pdfname.onlyFileName();
4296 command = lyxrc.forward_search_pdf;
4299 DocIterator cur = bv->cursor();
4300 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4301 LYXERR(Debug::ACTION, "Forward search: row:" << row
4303 if (row == -1 || command.empty()) {
4304 dr.setMessage(_("Couldn't proceed."));
4307 string texrow = convert<string>(row);
4309 command = subst(command, "$$n", texrow);
4310 command = subst(command, "$$f", fulltexname);
4311 command = subst(command, "$$t", texname);
4312 command = subst(command, "$$o", outname);
4314 PathChanger p(path);
4316 one.startscript(Systemcall::DontWait, command);
4320 case LFUN_SPELLING_CONTINUOUSLY:
4321 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4322 dr.screenUpdate(Update::Force);
4326 // The LFUN must be for one of BufferView, Buffer or Cursor;
4328 dispatchToBufferView(cmd, dr);
4332 // Part of automatic menu appearance feature.
4333 if (isFullScreen()) {
4334 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4338 // Need to update bv because many LFUNs here might have destroyed it
4339 bv = currentBufferView();
4341 // Clear non-empty selections
4342 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4344 Cursor & cur = bv->cursor();
4345 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4346 cur.clearSelection();
4352 bool GuiView::lfunUiToggle(string const & ui_component)
4354 if (ui_component == "scrollbar") {
4355 // hide() is of no help
4356 if (d.current_work_area_->verticalScrollBarPolicy() ==
4357 Qt::ScrollBarAlwaysOff)
4359 d.current_work_area_->setVerticalScrollBarPolicy(
4360 Qt::ScrollBarAsNeeded);
4362 d.current_work_area_->setVerticalScrollBarPolicy(
4363 Qt::ScrollBarAlwaysOff);
4364 } else if (ui_component == "statusbar") {
4365 statusBar()->setVisible(!statusBar()->isVisible());
4366 } else if (ui_component == "menubar") {
4367 menuBar()->setVisible(!menuBar()->isVisible());
4369 if (ui_component == "frame") {
4371 getContentsMargins(&l, &t, &r, &b);
4372 //are the frames in default state?
4373 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4375 setContentsMargins(-2, -2, -2, -2);
4377 setContentsMargins(0, 0, 0, 0);
4380 if (ui_component == "fullscreen") {
4388 void GuiView::toggleFullScreen()
4390 if (isFullScreen()) {
4391 for (int i = 0; i != d.splitter_->count(); ++i)
4392 d.tabWorkArea(i)->setFullScreen(false);
4393 setContentsMargins(0, 0, 0, 0);
4394 setWindowState(windowState() ^ Qt::WindowFullScreen);
4397 statusBar()->show();
4400 hideDialogs("prefs", 0);
4401 for (int i = 0; i != d.splitter_->count(); ++i)
4402 d.tabWorkArea(i)->setFullScreen(true);
4403 setContentsMargins(-2, -2, -2, -2);
4405 setWindowState(windowState() ^ Qt::WindowFullScreen);
4406 if (lyxrc.full_screen_statusbar)
4407 statusBar()->hide();
4408 if (lyxrc.full_screen_menubar)
4410 if (lyxrc.full_screen_toolbars) {
4411 ToolbarMap::iterator end = d.toolbars_.end();
4412 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4417 // give dialogs like the TOC a chance to adapt
4422 Buffer const * GuiView::updateInset(Inset const * inset)
4427 Buffer const * inset_buffer = &(inset->buffer());
4429 for (int i = 0; i != d.splitter_->count(); ++i) {
4430 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4433 Buffer const * buffer = &(wa->bufferView().buffer());
4434 if (inset_buffer == buffer)
4435 wa->scheduleRedraw(true);
4437 return inset_buffer;
4441 void GuiView::restartCaret()
4443 /* When we move around, or type, it's nice to be able to see
4444 * the caret immediately after the keypress.
4446 if (d.current_work_area_)
4447 d.current_work_area_->startBlinkingCaret();
4449 // Take this occasion to update the other GUI elements.
4455 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4457 if (d.current_work_area_)
4458 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4463 // This list should be kept in sync with the list of insets in
4464 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4465 // dialog should have the same name as the inset.
4466 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4467 // docs in LyXAction.cpp.
4469 char const * const dialognames[] = {
4471 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4472 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4473 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4474 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4475 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4476 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4477 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4478 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4480 char const * const * const end_dialognames =
4481 dialognames + (sizeof(dialognames) / sizeof(char *));
4485 cmpCStr(char const * name) : name_(name) {}
4486 bool operator()(char const * other) {
4487 return strcmp(other, name_) == 0;
4494 bool isValidName(string const & name)
4496 return find_if(dialognames, end_dialognames,
4497 cmpCStr(name.c_str())) != end_dialognames;
4503 void GuiView::resetDialogs()
4505 // Make sure that no LFUN uses any GuiView.
4506 guiApp->setCurrentView(0);
4510 constructToolbars();
4511 guiApp->menus().fillMenuBar(menuBar(), this, false);
4512 d.layout_->updateContents(true);
4513 // Now update controls with current buffer.
4514 guiApp->setCurrentView(this);
4520 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4522 if (!isValidName(name))
4525 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4527 if (it != d.dialogs_.end()) {
4529 it->second->hideView();
4530 return it->second.get();
4533 Dialog * dialog = build(name);
4534 d.dialogs_[name].reset(dialog);
4535 if (lyxrc.allow_geometry_session)
4536 dialog->restoreSession();
4543 void GuiView::showDialog(string const & name, string const & sdata,
4546 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4550 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4556 const string name = fromqstr(qname);
4557 const string sdata = fromqstr(qdata);
4561 Dialog * dialog = findOrBuild(name, false);
4563 bool const visible = dialog->isVisibleView();
4564 dialog->showData(sdata);
4565 if (inset && currentBufferView())
4566 currentBufferView()->editInset(name, inset);
4567 // We only set the focus to the new dialog if it was not yet
4568 // visible in order not to change the existing previous behaviour
4570 // activateWindow is needed for floating dockviews
4571 dialog->asQWidget()->raise();
4572 dialog->asQWidget()->activateWindow();
4573 dialog->asQWidget()->setFocus();
4577 catch (ExceptionMessage const & ex) {
4585 bool GuiView::isDialogVisible(string const & name) const
4587 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4588 if (it == d.dialogs_.end())
4590 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4594 void GuiView::hideDialog(string const & name, Inset * inset)
4596 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4597 if (it == d.dialogs_.end())
4601 if (!currentBufferView())
4603 if (inset != currentBufferView()->editedInset(name))
4607 Dialog * const dialog = it->second.get();
4608 if (dialog->isVisibleView())
4610 if (currentBufferView())
4611 currentBufferView()->editInset(name, 0);
4615 void GuiView::disconnectDialog(string const & name)
4617 if (!isValidName(name))
4619 if (currentBufferView())
4620 currentBufferView()->editInset(name, 0);
4624 void GuiView::hideAll() const
4626 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4627 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4629 for(; it != end; ++it)
4630 it->second->hideView();
4634 void GuiView::updateDialogs()
4636 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4637 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4639 for(; it != end; ++it) {
4640 Dialog * dialog = it->second.get();
4642 if (dialog->needBufferOpen() && !documentBufferView())
4643 hideDialog(fromqstr(dialog->name()), 0);
4644 else if (dialog->isVisibleView())
4645 dialog->checkStatus();
4652 Dialog * createDialog(GuiView & lv, string const & name);
4654 // will be replaced by a proper factory...
4655 Dialog * createGuiAbout(GuiView & lv);
4656 Dialog * createGuiBibtex(GuiView & lv);
4657 Dialog * createGuiChanges(GuiView & lv);
4658 Dialog * createGuiCharacter(GuiView & lv);
4659 Dialog * createGuiCitation(GuiView & lv);
4660 Dialog * createGuiCompare(GuiView & lv);
4661 Dialog * createGuiCompareHistory(GuiView & lv);
4662 Dialog * createGuiDelimiter(GuiView & lv);
4663 Dialog * createGuiDocument(GuiView & lv);
4664 Dialog * createGuiErrorList(GuiView & lv);
4665 Dialog * createGuiExternal(GuiView & lv);
4666 Dialog * createGuiGraphics(GuiView & lv);
4667 Dialog * createGuiInclude(GuiView & lv);
4668 Dialog * createGuiIndex(GuiView & lv);
4669 Dialog * createGuiListings(GuiView & lv);
4670 Dialog * createGuiLog(GuiView & lv);
4671 Dialog * createGuiMathMatrix(GuiView & lv);
4672 Dialog * createGuiNote(GuiView & lv);
4673 Dialog * createGuiParagraph(GuiView & lv);
4674 Dialog * createGuiPhantom(GuiView & lv);
4675 Dialog * createGuiPreferences(GuiView & lv);
4676 Dialog * createGuiPrint(GuiView & lv);
4677 Dialog * createGuiPrintindex(GuiView & lv);
4678 Dialog * createGuiRef(GuiView & lv);
4679 Dialog * createGuiSearch(GuiView & lv);
4680 Dialog * createGuiSearchAdv(GuiView & lv);
4681 Dialog * createGuiSendTo(GuiView & lv);
4682 Dialog * createGuiShowFile(GuiView & lv);
4683 Dialog * createGuiSpellchecker(GuiView & lv);
4684 Dialog * createGuiSymbols(GuiView & lv);
4685 Dialog * createGuiTabularCreate(GuiView & lv);
4686 Dialog * createGuiTexInfo(GuiView & lv);
4687 Dialog * createGuiToc(GuiView & lv);
4688 Dialog * createGuiThesaurus(GuiView & lv);
4689 Dialog * createGuiViewSource(GuiView & lv);
4690 Dialog * createGuiWrap(GuiView & lv);
4691 Dialog * createGuiProgressView(GuiView & lv);
4695 Dialog * GuiView::build(string const & name)
4697 LASSERT(isValidName(name), return 0);
4699 Dialog * dialog = createDialog(*this, name);
4703 if (name == "aboutlyx")
4704 return createGuiAbout(*this);
4705 if (name == "bibtex")
4706 return createGuiBibtex(*this);
4707 if (name == "changes")
4708 return createGuiChanges(*this);
4709 if (name == "character")
4710 return createGuiCharacter(*this);
4711 if (name == "citation")
4712 return createGuiCitation(*this);
4713 if (name == "compare")
4714 return createGuiCompare(*this);
4715 if (name == "comparehistory")
4716 return createGuiCompareHistory(*this);
4717 if (name == "document")
4718 return createGuiDocument(*this);
4719 if (name == "errorlist")
4720 return createGuiErrorList(*this);
4721 if (name == "external")
4722 return createGuiExternal(*this);
4724 return createGuiShowFile(*this);
4725 if (name == "findreplace")
4726 return createGuiSearch(*this);
4727 if (name == "findreplaceadv")
4728 return createGuiSearchAdv(*this);
4729 if (name == "graphics")
4730 return createGuiGraphics(*this);
4731 if (name == "include")
4732 return createGuiInclude(*this);
4733 if (name == "index")
4734 return createGuiIndex(*this);
4735 if (name == "index_print")
4736 return createGuiPrintindex(*this);
4737 if (name == "listings")
4738 return createGuiListings(*this);
4740 return createGuiLog(*this);
4741 if (name == "mathdelimiter")
4742 return createGuiDelimiter(*this);
4743 if (name == "mathmatrix")
4744 return createGuiMathMatrix(*this);
4746 return createGuiNote(*this);
4747 if (name == "paragraph")
4748 return createGuiParagraph(*this);
4749 if (name == "phantom")
4750 return createGuiPhantom(*this);
4751 if (name == "prefs")
4752 return createGuiPreferences(*this);
4754 return createGuiRef(*this);
4755 if (name == "sendto")
4756 return createGuiSendTo(*this);
4757 if (name == "spellchecker")
4758 return createGuiSpellchecker(*this);
4759 if (name == "symbols")
4760 return createGuiSymbols(*this);
4761 if (name == "tabularcreate")
4762 return createGuiTabularCreate(*this);
4763 if (name == "texinfo")
4764 return createGuiTexInfo(*this);
4765 if (name == "thesaurus")
4766 return createGuiThesaurus(*this);
4768 return createGuiToc(*this);
4769 if (name == "view-source")
4770 return createGuiViewSource(*this);
4772 return createGuiWrap(*this);
4773 if (name == "progress")
4774 return createGuiProgressView(*this);
4780 SEMenu::SEMenu(QWidget * parent)
4782 QAction * action = addAction(qt_("Disable Shell Escape"));
4783 connect(action, SIGNAL(triggered()),
4784 parent, SLOT(disableShellEscape()));
4787 } // namespace frontend
4790 #include "moc_GuiView.cpp"