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,
508 QVector<GuiWorkArea*> guiWorkAreas();
511 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
514 GuiView::GuiView(int id)
515 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
516 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
519 connect(this, SIGNAL(bufferViewChanged()),
520 this, SLOT(onBufferViewChanged()));
522 // GuiToolbars *must* be initialised before the menu bar.
523 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
526 // set ourself as the current view. This is needed for the menu bar
527 // filling, at least for the static special menu item on Mac. Otherwise
528 // they are greyed out.
529 guiApp->setCurrentView(this);
531 // Fill up the menu bar.
532 guiApp->menus().fillMenuBar(menuBar(), this, true);
534 setCentralWidget(d.stack_widget_);
536 // Start autosave timer
537 if (lyxrc.autosave) {
538 // The connection is closed when this is destroyed.
539 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
540 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
541 d.autosave_timeout_.start();
543 connect(&d.statusbar_timer_, SIGNAL(timeout()),
544 this, SLOT(clearMessage()));
546 // We don't want to keep the window in memory if it is closed.
547 setAttribute(Qt::WA_DeleteOnClose, true);
549 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
550 // QIcon::fromTheme was introduced in Qt 4.6
551 #if (QT_VERSION >= 0x040600)
552 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
553 // since the icon is provided in the application bundle. We use a themed
554 // version when available and use the bundled one as fallback.
555 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
557 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
563 // use tabbed dock area for multiple docks
564 // (such as "source" and "messages")
565 setDockOptions(QMainWindow::ForceTabbedDocks);
568 setAcceptDrops(true);
570 // add busy indicator to statusbar
571 QLabel * busylabel = new QLabel(statusBar());
572 statusBar()->addPermanentWidget(busylabel);
573 search_mode mode = theGuiApp()->imageSearchMode();
574 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
575 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
576 busylabel->setMovie(busyanim);
580 connect(&d.processing_thread_watcher_, SIGNAL(started()),
581 busylabel, SLOT(show()));
582 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
583 busylabel, SLOT(hide()));
585 QFontMetrics const fm(statusBar()->fontMetrics());
586 int const iconheight = max(int(d.normalIconSize), fm.height());
587 QSize const iconsize(iconheight, iconheight);
589 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
590 shell_escape_ = new QLabel(statusBar());
591 shell_escape_->setPixmap(shellescape);
592 shell_escape_->setScaledContents(true);
593 shell_escape_->setAlignment(Qt::AlignCenter);
594 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
595 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
596 "external commands for this document. "
597 "Right click to change."));
598 SEMenu * menu = new SEMenu(this);
599 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
600 menu, SLOT(showMenu(QPoint)));
601 shell_escape_->hide();
602 statusBar()->addPermanentWidget(shell_escape_);
604 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
605 read_only_ = new QLabel(statusBar());
606 read_only_->setPixmap(readonly);
607 read_only_->setScaledContents(true);
608 read_only_->setAlignment(Qt::AlignCenter);
610 statusBar()->addPermanentWidget(read_only_);
612 version_control_ = new QLabel(statusBar());
613 version_control_->setAlignment(Qt::AlignCenter);
614 version_control_->setFrameStyle(QFrame::StyledPanel);
615 version_control_->hide();
616 statusBar()->addPermanentWidget(version_control_);
618 statusBar()->setSizeGripEnabled(true);
621 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
622 SLOT(autoSaveThreadFinished()));
624 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
625 SLOT(processingThreadStarted()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
627 SLOT(processingThreadFinished()));
629 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
630 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
632 // set custom application bars context menu, e.g. tool bar and menu bar
633 setContextMenuPolicy(Qt::CustomContextMenu);
634 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
635 SLOT(toolBarPopup(const QPoint &)));
637 // Forbid too small unresizable window because it can happen
638 // with some window manager under X11.
639 setMinimumSize(300, 200);
641 if (lyxrc.allow_geometry_session) {
642 // Now take care of session management.
647 // no session handling, default to a sane size.
648 setGeometry(50, 50, 690, 510);
651 // clear session data if any.
653 settings.remove("views");
663 void GuiView::disableShellEscape()
665 BufferView * bv = documentBufferView();
668 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
669 bv->buffer().params().shell_escape = false;
670 bv->processUpdateFlags(Update::Force);
674 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
676 QVector<GuiWorkArea*> areas;
677 for (int i = 0; i < tabWorkAreaCount(); i++) {
678 TabWorkArea* ta = tabWorkArea(i);
679 for (int u = 0; u < ta->count(); u++) {
680 areas << ta->workArea(u);
686 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
687 string const & format)
689 docstring const fmt = theFormats().prettyName(format);
692 case Buffer::ExportSuccess:
693 msg = bformat(_("Successful export to format: %1$s"), fmt);
695 case Buffer::ExportCancel:
696 msg = _("Document export cancelled.");
698 case Buffer::ExportError:
699 case Buffer::ExportNoPathToFormat:
700 case Buffer::ExportTexPathHasSpaces:
701 case Buffer::ExportConverterError:
702 msg = bformat(_("Error while exporting format: %1$s"), fmt);
704 case Buffer::PreviewSuccess:
705 msg = bformat(_("Successful preview of format: %1$s"), fmt);
707 case Buffer::PreviewError:
708 msg = bformat(_("Error while previewing format: %1$s"), fmt);
710 case Buffer::ExportKilled:
711 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
718 void GuiView::processingThreadStarted()
723 void GuiView::processingThreadFinished()
725 QFutureWatcher<Buffer::ExportStatus> const * watcher =
726 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
728 Buffer::ExportStatus const status = watcher->result();
729 handleExportStatus(this, status, d.processing_format);
732 BufferView const * const bv = currentBufferView();
733 if (bv && !bv->buffer().errorList("Export").empty()) {
738 bool const error = (status != Buffer::ExportSuccess &&
739 status != Buffer::PreviewSuccess &&
740 status != Buffer::ExportCancel);
742 ErrorList & el = bv->buffer().errorList(d.last_export_format);
743 // at this point, we do not know if buffer-view or
744 // master-buffer-view was called. If there was an export error,
745 // and the current buffer's error log is empty, we guess that
746 // it must be master-buffer-view that was called so we set
748 errors(d.last_export_format, el.empty());
753 void GuiView::autoSaveThreadFinished()
755 QFutureWatcher<docstring> const * watcher =
756 static_cast<QFutureWatcher<docstring> const *>(sender());
757 message(watcher->result());
762 void GuiView::saveLayout() const
765 settings.setValue("zoom_ratio", zoom_ratio_);
766 settings.setValue("devel_mode", devel_mode_);
767 settings.beginGroup("views");
768 settings.beginGroup(QString::number(id_));
769 #if defined(Q_WS_X11) || defined(QPA_XCB)
770 settings.setValue("pos", pos());
771 settings.setValue("size", size());
773 settings.setValue("geometry", saveGeometry());
775 settings.setValue("layout", saveState(0));
776 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
780 void GuiView::saveUISettings() const
784 // Save the toolbar private states
785 ToolbarMap::iterator end = d.toolbars_.end();
786 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
787 it->second->saveSession(settings);
788 // Now take care of all other dialogs
789 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
790 for (; it!= d.dialogs_.end(); ++it)
791 it->second->saveSession(settings);
795 bool GuiView::restoreLayout()
798 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
799 // Actual zoom value: default zoom + fractional offset
800 int zoom = lyxrc.defaultZoom * zoom_ratio_;
801 if (zoom < static_cast<int>(zoom_min_))
803 lyxrc.currentZoom = zoom;
804 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
805 settings.beginGroup("views");
806 settings.beginGroup(QString::number(id_));
807 QString const icon_key = "icon_size";
808 if (!settings.contains(icon_key))
811 //code below is skipped when when ~/.config/LyX is (re)created
812 setIconSize(d.iconSize(settings.value(icon_key).toString()));
814 #if defined(Q_WS_X11) || defined(QPA_XCB)
815 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
816 QSize size = settings.value("size", QSize(690, 510)).toSize();
820 // Work-around for bug #6034: the window ends up in an undetermined
821 // state when trying to restore a maximized window when it is
822 // already maximized.
823 if (!(windowState() & Qt::WindowMaximized))
824 if (!restoreGeometry(settings.value("geometry").toByteArray()))
825 setGeometry(50, 50, 690, 510);
827 // Make sure layout is correctly oriented.
828 setLayoutDirection(qApp->layoutDirection());
830 // Allow the toc and view-source dock widget to be restored if needed.
832 if ((dialog = findOrBuild("toc", true)))
833 // see bug 5082. At least setup title and enabled state.
834 // Visibility will be adjusted by restoreState below.
835 dialog->prepareView();
836 if ((dialog = findOrBuild("view-source", true)))
837 dialog->prepareView();
838 if ((dialog = findOrBuild("progress", true)))
839 dialog->prepareView();
841 if (!restoreState(settings.value("layout").toByteArray(), 0))
844 // init the toolbars that have not been restored
845 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
846 Toolbars::Infos::iterator end = guiApp->toolbars().end();
847 for (; cit != end; ++cit) {
848 GuiToolbar * tb = toolbar(cit->name);
849 if (tb && !tb->isRestored())
850 initToolbar(cit->name);
853 // update lock (all) toolbars positions
854 updateLockToolbars();
861 GuiToolbar * GuiView::toolbar(string const & name)
863 ToolbarMap::iterator it = d.toolbars_.find(name);
864 if (it != d.toolbars_.end())
867 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
872 void GuiView::updateLockToolbars()
874 toolbarsMovable_ = false;
875 for (ToolbarInfo const & info : guiApp->toolbars()) {
876 GuiToolbar * tb = toolbar(info.name);
877 if (tb && tb->isMovable())
878 toolbarsMovable_ = true;
883 void GuiView::constructToolbars()
885 ToolbarMap::iterator it = d.toolbars_.begin();
886 for (; it != d.toolbars_.end(); ++it)
890 // I don't like doing this here, but the standard toolbar
891 // destroys this object when it's destroyed itself (vfr)
892 d.layout_ = new LayoutBox(*this);
893 d.stack_widget_->addWidget(d.layout_);
894 d.layout_->move(0,0);
896 // extracts the toolbars from the backend
897 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
898 Toolbars::Infos::iterator end = guiApp->toolbars().end();
899 for (; cit != end; ++cit)
900 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
904 void GuiView::initToolbars()
906 // extracts the toolbars from the backend
907 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
908 Toolbars::Infos::iterator end = guiApp->toolbars().end();
909 for (; cit != end; ++cit)
910 initToolbar(cit->name);
914 void GuiView::initToolbar(string const & name)
916 GuiToolbar * tb = toolbar(name);
919 int const visibility = guiApp->toolbars().defaultVisibility(name);
920 bool newline = !(visibility & Toolbars::SAMEROW);
921 tb->setVisible(false);
922 tb->setVisibility(visibility);
924 if (visibility & Toolbars::TOP) {
926 addToolBarBreak(Qt::TopToolBarArea);
927 addToolBar(Qt::TopToolBarArea, tb);
930 if (visibility & Toolbars::BOTTOM) {
932 addToolBarBreak(Qt::BottomToolBarArea);
933 addToolBar(Qt::BottomToolBarArea, tb);
936 if (visibility & Toolbars::LEFT) {
938 addToolBarBreak(Qt::LeftToolBarArea);
939 addToolBar(Qt::LeftToolBarArea, tb);
942 if (visibility & Toolbars::RIGHT) {
944 addToolBarBreak(Qt::RightToolBarArea);
945 addToolBar(Qt::RightToolBarArea, tb);
948 if (visibility & Toolbars::ON)
949 tb->setVisible(true);
951 tb->setMovable(true);
955 TocModels & GuiView::tocModels()
957 return d.toc_models_;
961 void GuiView::setFocus()
963 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
964 QMainWindow::setFocus();
968 bool GuiView::hasFocus() const
970 if (currentWorkArea())
971 return currentWorkArea()->hasFocus();
972 if (currentMainWorkArea())
973 return currentMainWorkArea()->hasFocus();
974 return d.bg_widget_->hasFocus();
978 void GuiView::focusInEvent(QFocusEvent * e)
980 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
981 QMainWindow::focusInEvent(e);
982 // Make sure guiApp points to the correct view.
983 guiApp->setCurrentView(this);
984 if (currentWorkArea())
985 currentWorkArea()->setFocus();
986 else if (currentMainWorkArea())
987 currentMainWorkArea()->setFocus();
989 d.bg_widget_->setFocus();
993 void GuiView::showEvent(QShowEvent * e)
995 LYXERR(Debug::GUI, "Passed Geometry "
996 << size().height() << "x" << size().width()
997 << "+" << pos().x() << "+" << pos().y());
999 if (d.splitter_->count() == 0)
1000 // No work area, switch to the background widget.
1004 QMainWindow::showEvent(e);
1008 bool GuiView::closeScheduled()
1015 bool GuiView::prepareAllBuffersForLogout()
1017 Buffer * first = theBufferList().first();
1021 // First, iterate over all buffers and ask the users if unsaved
1022 // changes should be saved.
1023 // We cannot use a for loop as the buffer list cycles.
1026 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1028 b = theBufferList().next(b);
1029 } while (b != first);
1031 // Next, save session state
1032 // When a view/window was closed before without quitting LyX, there
1033 // are already entries in the lastOpened list.
1034 theSession().lastOpened().clear();
1041 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1042 ** is responsibility of the container (e.g., dialog)
1044 void GuiView::closeEvent(QCloseEvent * close_event)
1046 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1048 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1049 Alert::warning(_("Exit LyX"),
1050 _("LyX could not be closed because documents are being processed by LyX."));
1051 close_event->setAccepted(false);
1055 // If the user pressed the x (so we didn't call closeView
1056 // programmatically), we want to clear all existing entries.
1058 theSession().lastOpened().clear();
1063 // it can happen that this event arrives without selecting the view,
1064 // e.g. when clicking the close button on a background window.
1066 if (!closeWorkAreaAll()) {
1068 close_event->ignore();
1072 // Make sure that nothing will use this to be closed View.
1073 guiApp->unregisterView(this);
1075 if (isFullScreen()) {
1076 // Switch off fullscreen before closing.
1081 // Make sure the timer time out will not trigger a statusbar update.
1082 d.statusbar_timer_.stop();
1084 // Saving fullscreen requires additional tweaks in the toolbar code.
1085 // It wouldn't also work under linux natively.
1086 if (lyxrc.allow_geometry_session) {
1091 close_event->accept();
1095 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1097 if (event->mimeData()->hasUrls())
1099 /// \todo Ask lyx-devel is this is enough:
1100 /// if (event->mimeData()->hasFormat("text/plain"))
1101 /// event->acceptProposedAction();
1105 void GuiView::dropEvent(QDropEvent * event)
1107 QList<QUrl> files = event->mimeData()->urls();
1108 if (files.isEmpty())
1111 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1112 for (int i = 0; i != files.size(); ++i) {
1113 string const file = os::internal_path(fromqstr(
1114 files.at(i).toLocalFile()));
1118 string const ext = support::getExtension(file);
1119 vector<const Format *> found_formats;
1121 // Find all formats that have the correct extension.
1122 vector<const Format *> const & import_formats
1123 = theConverters().importableFormats();
1124 vector<const Format *>::const_iterator it = import_formats.begin();
1125 for (; it != import_formats.end(); ++it)
1126 if ((*it)->hasExtension(ext))
1127 found_formats.push_back(*it);
1130 if (found_formats.size() >= 1) {
1131 if (found_formats.size() > 1) {
1132 //FIXME: show a dialog to choose the correct importable format
1133 LYXERR(Debug::FILES,
1134 "Multiple importable formats found, selecting first");
1136 string const arg = found_formats[0]->name() + " " + file;
1137 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1140 //FIXME: do we have to explicitly check whether it's a lyx file?
1141 LYXERR(Debug::FILES,
1142 "No formats found, trying to open it as a lyx file");
1143 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1145 // add the functions to the queue
1146 guiApp->addToFuncRequestQueue(cmd);
1149 // now process the collected functions. We perform the events
1150 // asynchronously. This prevents potential problems in case the
1151 // BufferView is closed within an event.
1152 guiApp->processFuncRequestQueueAsync();
1156 void GuiView::message(docstring const & str)
1158 if (ForkedProcess::iAmAChild())
1161 // call is moved to GUI-thread by GuiProgress
1162 d.progress_->appendMessage(toqstr(str));
1166 void GuiView::clearMessageText()
1168 message(docstring());
1172 void GuiView::updateStatusBarMessage(QString const & str)
1174 statusBar()->showMessage(str);
1175 d.statusbar_timer_.stop();
1176 d.statusbar_timer_.start(3000);
1180 void GuiView::clearMessage()
1182 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1183 // the hasFocus function mostly returns false, even if the focus is on
1184 // a workarea in this view.
1188 d.statusbar_timer_.stop();
1192 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1194 if (wa != d.current_work_area_
1195 || wa->bufferView().buffer().isInternal())
1197 Buffer const & buf = wa->bufferView().buffer();
1198 // Set the windows title
1199 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1200 if (buf.notifiesExternalModification()) {
1201 title = bformat(_("%1$s (modified externally)"), title);
1202 // If the external modification status has changed, then maybe the status of
1203 // buffer-save has changed too.
1207 title += from_ascii(" - LyX");
1209 setWindowTitle(toqstr(title));
1210 // Sets the path for the window: this is used by OSX to
1211 // allow a context click on the title bar showing a menu
1212 // with the path up to the file
1213 setWindowFilePath(toqstr(buf.absFileName()));
1214 // Tell Qt whether the current document is changed
1215 setWindowModified(!buf.isClean());
1217 if (buf.params().shell_escape)
1218 shell_escape_->show();
1220 shell_escape_->hide();
1222 if (buf.hasReadonlyFlag())
1227 if (buf.lyxvc().inUse()) {
1228 version_control_->show();
1229 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1231 version_control_->hide();
1235 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1237 if (d.current_work_area_)
1238 // disconnect the current work area from all slots
1239 QObject::disconnect(d.current_work_area_, 0, this, 0);
1241 disconnectBufferView();
1242 connectBufferView(wa->bufferView());
1243 connectBuffer(wa->bufferView().buffer());
1244 d.current_work_area_ = wa;
1245 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1246 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1247 QObject::connect(wa, SIGNAL(busy(bool)),
1248 this, SLOT(setBusy(bool)));
1249 // connection of a signal to a signal
1250 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1251 this, SIGNAL(bufferViewChanged()));
1252 Q_EMIT updateWindowTitle(wa);
1253 Q_EMIT bufferViewChanged();
1257 void GuiView::onBufferViewChanged()
1260 // Buffer-dependent dialogs must be updated. This is done here because
1261 // some dialogs require buffer()->text.
1266 void GuiView::on_lastWorkAreaRemoved()
1269 // We already are in a close event. Nothing more to do.
1272 if (d.splitter_->count() > 1)
1273 // We have a splitter so don't close anything.
1276 // Reset and updates the dialogs.
1277 Q_EMIT bufferViewChanged();
1282 if (lyxrc.open_buffers_in_tabs)
1283 // Nothing more to do, the window should stay open.
1286 if (guiApp->viewIds().size() > 1) {
1292 // On Mac we also close the last window because the application stay
1293 // resident in memory. On other platforms we don't close the last
1294 // window because this would quit the application.
1300 void GuiView::updateStatusBar()
1302 // let the user see the explicit message
1303 if (d.statusbar_timer_.isActive())
1310 void GuiView::showMessage()
1314 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1315 if (msg.isEmpty()) {
1316 BufferView const * bv = currentBufferView();
1318 msg = toqstr(bv->cursor().currentState(devel_mode_));
1320 msg = qt_("Welcome to LyX!");
1322 statusBar()->showMessage(msg);
1326 bool GuiView::event(QEvent * e)
1330 // Useful debug code:
1331 //case QEvent::ActivationChange:
1332 //case QEvent::WindowDeactivate:
1333 //case QEvent::Paint:
1334 //case QEvent::Enter:
1335 //case QEvent::Leave:
1336 //case QEvent::HoverEnter:
1337 //case QEvent::HoverLeave:
1338 //case QEvent::HoverMove:
1339 //case QEvent::StatusTip:
1340 //case QEvent::DragEnter:
1341 //case QEvent::DragLeave:
1342 //case QEvent::Drop:
1345 case QEvent::WindowActivate: {
1346 GuiView * old_view = guiApp->currentView();
1347 if (this == old_view) {
1349 return QMainWindow::event(e);
1351 if (old_view && old_view->currentBufferView()) {
1352 // save current selection to the selection buffer to allow
1353 // middle-button paste in this window.
1354 cap::saveSelection(old_view->currentBufferView()->cursor());
1356 guiApp->setCurrentView(this);
1357 if (d.current_work_area_)
1358 on_currentWorkAreaChanged(d.current_work_area_);
1362 return QMainWindow::event(e);
1365 case QEvent::ShortcutOverride: {
1367 if (isFullScreen() && menuBar()->isHidden()) {
1368 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1369 // FIXME: we should also try to detect special LyX shortcut such as
1370 // Alt-P and Alt-M. Right now there is a hack in
1371 // GuiWorkArea::processKeySym() that hides again the menubar for
1373 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1375 return QMainWindow::event(e);
1378 return QMainWindow::event(e);
1382 return QMainWindow::event(e);
1386 void GuiView::resetWindowTitle()
1388 setWindowTitle(qt_("LyX"));
1391 bool GuiView::focusNextPrevChild(bool /*next*/)
1398 bool GuiView::busy() const
1404 void GuiView::setBusy(bool busy)
1406 bool const busy_before = busy_ > 0;
1407 busy ? ++busy_ : --busy_;
1408 if ((busy_ > 0) == busy_before)
1409 // busy state didn't change
1413 QApplication::setOverrideCursor(Qt::WaitCursor);
1416 QApplication::restoreOverrideCursor();
1421 void GuiView::resetCommandExecute()
1423 command_execute_ = false;
1428 double GuiView::pixelRatio() const
1430 #if QT_VERSION >= 0x050000
1431 return qt_scale_factor * devicePixelRatio();
1438 GuiWorkArea * GuiView::workArea(int index)
1440 if (TabWorkArea * twa = d.currentTabWorkArea())
1441 if (index < twa->count())
1442 return twa->workArea(index);
1447 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1449 if (currentWorkArea()
1450 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1451 return currentWorkArea();
1452 if (TabWorkArea * twa = d.currentTabWorkArea())
1453 return twa->workArea(buffer);
1458 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1460 // Automatically create a TabWorkArea if there are none yet.
1461 TabWorkArea * tab_widget = d.splitter_->count()
1462 ? d.currentTabWorkArea() : addTabWorkArea();
1463 return tab_widget->addWorkArea(buffer, *this);
1467 TabWorkArea * GuiView::addTabWorkArea()
1469 TabWorkArea * twa = new TabWorkArea;
1470 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1471 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1472 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1473 this, SLOT(on_lastWorkAreaRemoved()));
1475 d.splitter_->addWidget(twa);
1476 d.stack_widget_->setCurrentWidget(d.splitter_);
1481 GuiWorkArea const * GuiView::currentWorkArea() const
1483 return d.current_work_area_;
1487 GuiWorkArea * GuiView::currentWorkArea()
1489 return d.current_work_area_;
1493 GuiWorkArea const * GuiView::currentMainWorkArea() const
1495 if (!d.currentTabWorkArea())
1497 return d.currentTabWorkArea()->currentWorkArea();
1501 GuiWorkArea * GuiView::currentMainWorkArea()
1503 if (!d.currentTabWorkArea())
1505 return d.currentTabWorkArea()->currentWorkArea();
1509 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1511 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1513 d.current_work_area_ = 0;
1515 Q_EMIT bufferViewChanged();
1519 // FIXME: I've no clue why this is here and why it accesses
1520 // theGuiApp()->currentView, which might be 0 (bug 6464).
1521 // See also 27525 (vfr).
1522 if (theGuiApp()->currentView() == this
1523 && theGuiApp()->currentView()->currentWorkArea() == wa)
1526 if (currentBufferView())
1527 cap::saveSelection(currentBufferView()->cursor());
1529 theGuiApp()->setCurrentView(this);
1530 d.current_work_area_ = wa;
1532 // We need to reset this now, because it will need to be
1533 // right if the tabWorkArea gets reset in the for loop. We
1534 // will change it back if we aren't in that case.
1535 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1536 d.current_main_work_area_ = wa;
1538 for (int i = 0; i != d.splitter_->count(); ++i) {
1539 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1540 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1541 << ", Current main wa: " << currentMainWorkArea());
1546 d.current_main_work_area_ = old_cmwa;
1548 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1549 on_currentWorkAreaChanged(wa);
1550 BufferView & bv = wa->bufferView();
1551 bv.cursor().fixIfBroken();
1553 wa->setUpdatesEnabled(true);
1554 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1558 void GuiView::removeWorkArea(GuiWorkArea * wa)
1560 LASSERT(wa, return);
1561 if (wa == d.current_work_area_) {
1563 disconnectBufferView();
1564 d.current_work_area_ = 0;
1565 d.current_main_work_area_ = 0;
1568 bool found_twa = false;
1569 for (int i = 0; i != d.splitter_->count(); ++i) {
1570 TabWorkArea * twa = d.tabWorkArea(i);
1571 if (twa->removeWorkArea(wa)) {
1572 // Found in this tab group, and deleted the GuiWorkArea.
1574 if (twa->count() != 0) {
1575 if (d.current_work_area_ == 0)
1576 // This means that we are closing the current GuiWorkArea, so
1577 // switch to the next GuiWorkArea in the found TabWorkArea.
1578 setCurrentWorkArea(twa->currentWorkArea());
1580 // No more WorkAreas in this tab group, so delete it.
1587 // It is not a tabbed work area (i.e., the search work area), so it
1588 // should be deleted by other means.
1589 LASSERT(found_twa, return);
1591 if (d.current_work_area_ == 0) {
1592 if (d.splitter_->count() != 0) {
1593 TabWorkArea * twa = d.currentTabWorkArea();
1594 setCurrentWorkArea(twa->currentWorkArea());
1596 // No more work areas, switch to the background widget.
1597 setCurrentWorkArea(0);
1603 LayoutBox * GuiView::getLayoutDialog() const
1609 void GuiView::updateLayoutList()
1612 d.layout_->updateContents(false);
1616 void GuiView::updateToolbars()
1618 ToolbarMap::iterator end = d.toolbars_.end();
1619 if (d.current_work_area_) {
1621 if (d.current_work_area_->bufferView().cursor().inMathed()
1622 && !d.current_work_area_->bufferView().cursor().inRegexped())
1623 context |= Toolbars::MATH;
1624 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1625 context |= Toolbars::TABLE;
1626 if (currentBufferView()->buffer().areChangesPresent()
1627 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1628 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1629 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1630 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1631 context |= Toolbars::REVIEW;
1632 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1633 context |= Toolbars::MATHMACROTEMPLATE;
1634 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1635 context |= Toolbars::IPA;
1636 if (command_execute_)
1637 context |= Toolbars::MINIBUFFER;
1638 if (minibuffer_focus_) {
1639 context |= Toolbars::MINIBUFFER_FOCUS;
1640 minibuffer_focus_ = false;
1643 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1644 it->second->update(context);
1646 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1647 it->second->update();
1651 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1653 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1654 LASSERT(newBuffer, return);
1656 GuiWorkArea * wa = workArea(*newBuffer);
1659 newBuffer->masterBuffer()->updateBuffer();
1661 wa = addWorkArea(*newBuffer);
1662 // scroll to the position when the BufferView was last closed
1663 if (lyxrc.use_lastfilepos) {
1664 LastFilePosSection::FilePos filepos =
1665 theSession().lastFilePos().load(newBuffer->fileName());
1666 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1669 //Disconnect the old buffer...there's no new one.
1672 connectBuffer(*newBuffer);
1673 connectBufferView(wa->bufferView());
1675 setCurrentWorkArea(wa);
1679 void GuiView::connectBuffer(Buffer & buf)
1681 buf.setGuiDelegate(this);
1685 void GuiView::disconnectBuffer()
1687 if (d.current_work_area_)
1688 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1692 void GuiView::connectBufferView(BufferView & bv)
1694 bv.setGuiDelegate(this);
1698 void GuiView::disconnectBufferView()
1700 if (d.current_work_area_)
1701 d.current_work_area_->bufferView().setGuiDelegate(0);
1705 void GuiView::errors(string const & error_type, bool from_master)
1707 BufferView const * const bv = currentBufferView();
1711 ErrorList const & el = from_master ?
1712 bv->buffer().masterBuffer()->errorList(error_type) :
1713 bv->buffer().errorList(error_type);
1718 string err = error_type;
1720 err = "from_master|" + error_type;
1721 showDialog("errorlist", err);
1725 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1727 d.toc_models_.updateItem(toqstr(type), dit);
1731 void GuiView::structureChanged()
1733 // This is called from the Buffer, which has no way to ensure that cursors
1734 // in BufferView remain valid.
1735 if (documentBufferView())
1736 documentBufferView()->cursor().sanitize();
1737 // FIXME: This is slightly expensive, though less than the tocBackend update
1738 // (#9880). This also resets the view in the Toc Widget (#6675).
1739 d.toc_models_.reset(documentBufferView());
1740 // Navigator needs more than a simple update in this case. It needs to be
1742 updateDialog("toc", "");
1746 void GuiView::updateDialog(string const & name, string const & sdata)
1748 if (!isDialogVisible(name))
1751 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1752 if (it == d.dialogs_.end())
1755 Dialog * const dialog = it->second.get();
1756 if (dialog->isVisibleView())
1757 dialog->initialiseParams(sdata);
1761 BufferView * GuiView::documentBufferView()
1763 return currentMainWorkArea()
1764 ? ¤tMainWorkArea()->bufferView()
1769 BufferView const * GuiView::documentBufferView() const
1771 return currentMainWorkArea()
1772 ? ¤tMainWorkArea()->bufferView()
1777 BufferView * GuiView::currentBufferView()
1779 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1783 BufferView const * GuiView::currentBufferView() const
1785 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1789 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1790 Buffer const * orig, Buffer * clone)
1792 bool const success = clone->autoSave();
1794 busyBuffers.remove(orig);
1796 ? _("Automatic save done.")
1797 : _("Automatic save failed!");
1801 void GuiView::autoSave()
1803 LYXERR(Debug::INFO, "Running autoSave()");
1805 Buffer * buffer = documentBufferView()
1806 ? &documentBufferView()->buffer() : 0;
1808 resetAutosaveTimers();
1812 GuiViewPrivate::busyBuffers.insert(buffer);
1813 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1814 buffer, buffer->cloneBufferOnly());
1815 d.autosave_watcher_.setFuture(f);
1816 resetAutosaveTimers();
1820 void GuiView::resetAutosaveTimers()
1823 d.autosave_timeout_.restart();
1827 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1830 Buffer * buf = currentBufferView()
1831 ? ¤tBufferView()->buffer() : 0;
1832 Buffer * doc_buffer = documentBufferView()
1833 ? &(documentBufferView()->buffer()) : 0;
1836 /* In LyX/Mac, when a dialog is open, the menus of the
1837 application can still be accessed without giving focus to
1838 the main window. In this case, we want to disable the menu
1839 entries that are buffer-related.
1840 This code must not be used on Linux and Windows, since it
1841 would disable buffer-related entries when hovering over the
1842 menu (see bug #9574).
1844 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1850 // Check whether we need a buffer
1851 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1852 // no, exit directly
1853 flag.message(from_utf8(N_("Command not allowed with"
1854 "out any document open")));
1855 flag.setEnabled(false);
1859 if (cmd.origin() == FuncRequest::TOC) {
1860 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1861 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1862 flag.setEnabled(false);
1866 switch(cmd.action()) {
1867 case LFUN_BUFFER_IMPORT:
1870 case LFUN_MASTER_BUFFER_EXPORT:
1872 && (doc_buffer->parent() != 0
1873 || doc_buffer->hasChildren())
1874 && !d.processing_thread_watcher_.isRunning()
1875 // this launches a dialog, which would be in the wrong Buffer
1876 && !(::lyx::operator==(cmd.argument(), "custom"));
1879 case LFUN_MASTER_BUFFER_UPDATE:
1880 case LFUN_MASTER_BUFFER_VIEW:
1882 && (doc_buffer->parent() != 0
1883 || doc_buffer->hasChildren())
1884 && !d.processing_thread_watcher_.isRunning();
1887 case LFUN_BUFFER_UPDATE:
1888 case LFUN_BUFFER_VIEW: {
1889 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1893 string format = to_utf8(cmd.argument());
1894 if (cmd.argument().empty())
1895 format = doc_buffer->params().getDefaultOutputFormat();
1896 enable = doc_buffer->params().isExportable(format, true);
1900 case LFUN_BUFFER_RELOAD:
1901 enable = doc_buffer && !doc_buffer->isUnnamed()
1902 && doc_buffer->fileName().exists()
1903 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1906 case LFUN_BUFFER_CHILD_OPEN:
1907 enable = doc_buffer != 0;
1910 case LFUN_BUFFER_WRITE:
1911 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1914 //FIXME: This LFUN should be moved to GuiApplication.
1915 case LFUN_BUFFER_WRITE_ALL: {
1916 // We enable the command only if there are some modified buffers
1917 Buffer * first = theBufferList().first();
1922 // We cannot use a for loop as the buffer list is a cycle.
1924 if (!b->isClean()) {
1928 b = theBufferList().next(b);
1929 } while (b != first);
1933 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1934 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1937 case LFUN_BUFFER_EXPORT: {
1938 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1942 return doc_buffer->getStatus(cmd, flag);
1946 case LFUN_BUFFER_EXPORT_AS:
1947 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1952 case LFUN_BUFFER_WRITE_AS:
1953 enable = doc_buffer != 0;
1956 case LFUN_EXPORT_CANCEL:
1957 enable = d.processing_thread_watcher_.isRunning();
1960 case LFUN_BUFFER_CLOSE:
1961 case LFUN_VIEW_CLOSE:
1962 enable = doc_buffer != 0;
1965 case LFUN_BUFFER_CLOSE_ALL:
1966 enable = theBufferList().last() != theBufferList().first();
1969 case LFUN_BUFFER_CHKTEX: {
1970 // hide if we have no checktex command
1971 if (lyxrc.chktex_command.empty()) {
1972 flag.setUnknown(true);
1976 if (!doc_buffer || !doc_buffer->params().isLatex()
1977 || d.processing_thread_watcher_.isRunning()) {
1978 // grey out, don't hide
1986 case LFUN_VIEW_SPLIT:
1987 if (cmd.getArg(0) == "vertical")
1988 enable = doc_buffer && (d.splitter_->count() == 1 ||
1989 d.splitter_->orientation() == Qt::Vertical);
1991 enable = doc_buffer && (d.splitter_->count() == 1 ||
1992 d.splitter_->orientation() == Qt::Horizontal);
1995 case LFUN_TAB_GROUP_CLOSE:
1996 enable = d.tabWorkAreaCount() > 1;
1999 case LFUN_DEVEL_MODE_TOGGLE:
2000 flag.setOnOff(devel_mode_);
2003 case LFUN_TOOLBAR_TOGGLE: {
2004 string const name = cmd.getArg(0);
2005 if (GuiToolbar * t = toolbar(name))
2006 flag.setOnOff(t->isVisible());
2009 docstring const msg =
2010 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2016 case LFUN_TOOLBAR_MOVABLE: {
2017 string const name = cmd.getArg(0);
2018 // use negation since locked == !movable
2020 // toolbar name * locks all toolbars
2021 flag.setOnOff(!toolbarsMovable_);
2022 else if (GuiToolbar * t = toolbar(name))
2023 flag.setOnOff(!(t->isMovable()));
2026 docstring const msg =
2027 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2033 case LFUN_ICON_SIZE:
2034 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2037 case LFUN_DROP_LAYOUTS_CHOICE:
2041 case LFUN_UI_TOGGLE:
2042 flag.setOnOff(isFullScreen());
2045 case LFUN_DIALOG_DISCONNECT_INSET:
2048 case LFUN_DIALOG_HIDE:
2049 // FIXME: should we check if the dialog is shown?
2052 case LFUN_DIALOG_TOGGLE:
2053 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2056 case LFUN_DIALOG_SHOW: {
2057 string const name = cmd.getArg(0);
2059 enable = name == "aboutlyx"
2060 || name == "file" //FIXME: should be removed.
2062 || name == "texinfo"
2063 || name == "progress"
2064 || name == "compare";
2065 else if (name == "character" || name == "symbols"
2066 || name == "mathdelimiter" || name == "mathmatrix") {
2067 if (!buf || buf->isReadonly())
2070 Cursor const & cur = currentBufferView()->cursor();
2071 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2074 else if (name == "latexlog")
2075 enable = FileName(doc_buffer->logName()).isReadableFile();
2076 else if (name == "spellchecker")
2077 enable = theSpellChecker()
2078 && !doc_buffer->isReadonly()
2079 && !doc_buffer->text().empty();
2080 else if (name == "vclog")
2081 enable = doc_buffer->lyxvc().inUse();
2085 case LFUN_DIALOG_UPDATE: {
2086 string const name = cmd.getArg(0);
2088 enable = name == "prefs";
2092 case LFUN_COMMAND_EXECUTE:
2094 case LFUN_MENU_OPEN:
2095 // Nothing to check.
2098 case LFUN_COMPLETION_INLINE:
2099 if (!d.current_work_area_
2100 || !d.current_work_area_->completer().inlinePossible(
2101 currentBufferView()->cursor()))
2105 case LFUN_COMPLETION_POPUP:
2106 if (!d.current_work_area_
2107 || !d.current_work_area_->completer().popupPossible(
2108 currentBufferView()->cursor()))
2113 if (!d.current_work_area_
2114 || !d.current_work_area_->completer().inlinePossible(
2115 currentBufferView()->cursor()))
2119 case LFUN_COMPLETION_ACCEPT:
2120 if (!d.current_work_area_
2121 || (!d.current_work_area_->completer().popupVisible()
2122 && !d.current_work_area_->completer().inlineVisible()
2123 && !d.current_work_area_->completer().completionAvailable()))
2127 case LFUN_COMPLETION_CANCEL:
2128 if (!d.current_work_area_
2129 || (!d.current_work_area_->completer().popupVisible()
2130 && !d.current_work_area_->completer().inlineVisible()))
2134 case LFUN_BUFFER_ZOOM_OUT:
2135 case LFUN_BUFFER_ZOOM_IN: {
2136 // only diff between these two is that the default for ZOOM_OUT
2138 bool const neg_zoom =
2139 convert<int>(cmd.argument()) < 0 ||
2140 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2141 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2142 docstring const msg =
2143 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2147 enable = doc_buffer;
2151 case LFUN_BUFFER_ZOOM: {
2152 bool const less_than_min_zoom =
2153 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2154 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2155 docstring const msg =
2156 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2161 enable = doc_buffer;
2165 case LFUN_BUFFER_MOVE_NEXT:
2166 case LFUN_BUFFER_MOVE_PREVIOUS:
2167 // we do not cycle when moving
2168 case LFUN_BUFFER_NEXT:
2169 case LFUN_BUFFER_PREVIOUS:
2170 // because we cycle, it doesn't matter whether on first or last
2171 enable = (d.currentTabWorkArea()->count() > 1);
2173 case LFUN_BUFFER_SWITCH:
2174 // toggle on the current buffer, but do not toggle off
2175 // the other ones (is that a good idea?)
2177 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2178 flag.setOnOff(true);
2181 case LFUN_VC_REGISTER:
2182 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2184 case LFUN_VC_RENAME:
2185 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2188 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2190 case LFUN_VC_CHECK_IN:
2191 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2193 case LFUN_VC_CHECK_OUT:
2194 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2196 case LFUN_VC_LOCKING_TOGGLE:
2197 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2198 && doc_buffer->lyxvc().lockingToggleEnabled();
2199 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2201 case LFUN_VC_REVERT:
2202 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2203 && !doc_buffer->hasReadonlyFlag();
2205 case LFUN_VC_UNDO_LAST:
2206 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2208 case LFUN_VC_REPO_UPDATE:
2209 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2211 case LFUN_VC_COMMAND: {
2212 if (cmd.argument().empty())
2214 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2218 case LFUN_VC_COMPARE:
2219 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2222 case LFUN_SERVER_GOTO_FILE_ROW:
2223 case LFUN_LYX_ACTIVATE:
2225 case LFUN_FORWARD_SEARCH:
2226 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2229 case LFUN_FILE_INSERT_PLAINTEXT:
2230 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2231 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2234 case LFUN_SPELLING_CONTINUOUSLY:
2235 flag.setOnOff(lyxrc.spellcheck_continuously);
2243 flag.setEnabled(false);
2249 static FileName selectTemplateFile()
2251 FileDialog dlg(qt_("Select template file"));
2252 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2253 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2255 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2256 QStringList(qt_("LyX Documents (*.lyx)")));
2258 if (result.first == FileDialog::Later)
2260 if (result.second.isEmpty())
2262 return FileName(fromqstr(result.second));
2266 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2270 Buffer * newBuffer = 0;
2272 newBuffer = checkAndLoadLyXFile(filename);
2273 } catch (ExceptionMessage const & e) {
2280 message(_("Document not loaded."));
2284 setBuffer(newBuffer);
2285 newBuffer->errors("Parse");
2288 theSession().lastFiles().add(filename);
2289 theSession().writeFile();
2296 void GuiView::openDocument(string const & fname)
2298 string initpath = lyxrc.document_path;
2300 if (documentBufferView()) {
2301 string const trypath = documentBufferView()->buffer().filePath();
2302 // If directory is writeable, use this as default.
2303 if (FileName(trypath).isDirWritable())
2309 if (fname.empty()) {
2310 FileDialog dlg(qt_("Select document to open"));
2311 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2312 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2314 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2315 FileDialog::Result result =
2316 dlg.open(toqstr(initpath), filter);
2318 if (result.first == FileDialog::Later)
2321 filename = fromqstr(result.second);
2323 // check selected filename
2324 if (filename.empty()) {
2325 message(_("Canceled."));
2331 // get absolute path of file and add ".lyx" to the filename if
2333 FileName const fullname =
2334 fileSearch(string(), filename, "lyx", support::may_not_exist);
2335 if (!fullname.empty())
2336 filename = fullname.absFileName();
2338 if (!fullname.onlyPath().isDirectory()) {
2339 Alert::warning(_("Invalid filename"),
2340 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2341 from_utf8(fullname.absFileName())));
2345 // if the file doesn't exist and isn't already open (bug 6645),
2346 // let the user create one
2347 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2348 !LyXVC::file_not_found_hook(fullname)) {
2349 // the user specifically chose this name. Believe him.
2350 Buffer * const b = newFile(filename, string(), true);
2356 docstring const disp_fn = makeDisplayPath(filename);
2357 message(bformat(_("Opening document %1$s..."), disp_fn));
2360 Buffer * buf = loadDocument(fullname);
2362 str2 = bformat(_("Document %1$s opened."), disp_fn);
2363 if (buf->lyxvc().inUse())
2364 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2365 " " + _("Version control detected.");
2367 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2372 // FIXME: clean that
2373 static bool import(GuiView * lv, FileName const & filename,
2374 string const & format, ErrorList & errorList)
2376 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2378 string loader_format;
2379 vector<string> loaders = theConverters().loaders();
2380 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2381 vector<string>::const_iterator it = loaders.begin();
2382 vector<string>::const_iterator en = loaders.end();
2383 for (; it != en; ++it) {
2384 if (!theConverters().isReachable(format, *it))
2387 string const tofile =
2388 support::changeExtension(filename.absFileName(),
2389 theFormats().extension(*it));
2390 if (theConverters().convert(0, filename, FileName(tofile),
2391 filename, format, *it, errorList) != Converters::SUCCESS)
2393 loader_format = *it;
2396 if (loader_format.empty()) {
2397 frontend::Alert::error(_("Couldn't import file"),
2398 bformat(_("No information for importing the format %1$s."),
2399 theFormats().prettyName(format)));
2403 loader_format = format;
2405 if (loader_format == "lyx") {
2406 Buffer * buf = lv->loadDocument(lyxfile);
2410 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2414 bool as_paragraphs = loader_format == "textparagraph";
2415 string filename2 = (loader_format == format) ? filename.absFileName()
2416 : support::changeExtension(filename.absFileName(),
2417 theFormats().extension(loader_format));
2418 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2420 guiApp->setCurrentView(lv);
2421 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2428 void GuiView::importDocument(string const & argument)
2431 string filename = split(argument, format, ' ');
2433 LYXERR(Debug::INFO, format << " file: " << filename);
2435 // need user interaction
2436 if (filename.empty()) {
2437 string initpath = lyxrc.document_path;
2438 if (documentBufferView()) {
2439 string const trypath = documentBufferView()->buffer().filePath();
2440 // If directory is writeable, use this as default.
2441 if (FileName(trypath).isDirWritable())
2445 docstring const text = bformat(_("Select %1$s file to import"),
2446 theFormats().prettyName(format));
2448 FileDialog dlg(toqstr(text));
2449 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2450 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2452 docstring filter = theFormats().prettyName(format);
2455 filter += from_utf8(theFormats().extensions(format));
2458 FileDialog::Result result =
2459 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2461 if (result.first == FileDialog::Later)
2464 filename = fromqstr(result.second);
2466 // check selected filename
2467 if (filename.empty())
2468 message(_("Canceled."));
2471 if (filename.empty())
2474 // get absolute path of file
2475 FileName const fullname(support::makeAbsPath(filename));
2477 // Can happen if the user entered a path into the dialog
2479 if (fullname.onlyFileName().empty()) {
2480 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2481 "Aborting import."),
2482 from_utf8(fullname.absFileName()));
2483 frontend::Alert::error(_("File name error"), msg);
2484 message(_("Canceled."));
2489 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2491 // Check if the document already is open
2492 Buffer * buf = theBufferList().getBuffer(lyxfile);
2495 if (!closeBuffer()) {
2496 message(_("Canceled."));
2501 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2503 // if the file exists already, and we didn't do
2504 // -i lyx thefile.lyx, warn
2505 if (lyxfile.exists() && fullname != lyxfile) {
2507 docstring text = bformat(_("The document %1$s already exists.\n\n"
2508 "Do you want to overwrite that document?"), displaypath);
2509 int const ret = Alert::prompt(_("Overwrite document?"),
2510 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2513 message(_("Canceled."));
2518 message(bformat(_("Importing %1$s..."), displaypath));
2519 ErrorList errorList;
2520 if (import(this, fullname, format, errorList))
2521 message(_("imported."));
2523 message(_("file not imported!"));
2525 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2529 void GuiView::newDocument(string const & filename, bool from_template)
2531 FileName initpath(lyxrc.document_path);
2532 if (documentBufferView()) {
2533 FileName const trypath(documentBufferView()->buffer().filePath());
2534 // If directory is writeable, use this as default.
2535 if (trypath.isDirWritable())
2539 string templatefile;
2540 if (from_template) {
2541 templatefile = selectTemplateFile().absFileName();
2542 if (templatefile.empty())
2547 if (filename.empty())
2548 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2550 b = newFile(filename, templatefile, true);
2555 // If no new document could be created, it is unsure
2556 // whether there is a valid BufferView.
2557 if (currentBufferView())
2558 // Ensure the cursor is correctly positioned on screen.
2559 currentBufferView()->showCursor();
2563 void GuiView::insertLyXFile(docstring const & fname)
2565 BufferView * bv = documentBufferView();
2570 FileName filename(to_utf8(fname));
2571 if (filename.empty()) {
2572 // Launch a file browser
2574 string initpath = lyxrc.document_path;
2575 string const trypath = bv->buffer().filePath();
2576 // If directory is writeable, use this as default.
2577 if (FileName(trypath).isDirWritable())
2581 FileDialog dlg(qt_("Select LyX document to insert"));
2582 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2583 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2585 FileDialog::Result result = dlg.open(toqstr(initpath),
2586 QStringList(qt_("LyX Documents (*.lyx)")));
2588 if (result.first == FileDialog::Later)
2592 filename.set(fromqstr(result.second));
2594 // check selected filename
2595 if (filename.empty()) {
2596 // emit message signal.
2597 message(_("Canceled."));
2602 bv->insertLyXFile(filename);
2603 bv->buffer().errors("Parse");
2607 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2609 FileName fname = b.fileName();
2610 FileName const oldname = fname;
2612 if (!newname.empty()) {
2614 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2616 // Switch to this Buffer.
2619 // No argument? Ask user through dialog.
2621 FileDialog dlg(qt_("Choose a filename to save document as"));
2622 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2623 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2625 if (!isLyXFileName(fname.absFileName()))
2626 fname.changeExtension(".lyx");
2628 FileDialog::Result result =
2629 dlg.save(toqstr(fname.onlyPath().absFileName()),
2630 QStringList(qt_("LyX Documents (*.lyx)")),
2631 toqstr(fname.onlyFileName()));
2633 if (result.first == FileDialog::Later)
2636 fname.set(fromqstr(result.second));
2641 if (!isLyXFileName(fname.absFileName()))
2642 fname.changeExtension(".lyx");
2645 // fname is now the new Buffer location.
2647 // if there is already a Buffer open with this name, we do not want
2648 // to have another one. (the second test makes sure we're not just
2649 // trying to overwrite ourselves, which is fine.)
2650 if (theBufferList().exists(fname) && fname != oldname
2651 && theBufferList().getBuffer(fname) != &b) {
2652 docstring const text =
2653 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2654 "Please close it before attempting to overwrite it.\n"
2655 "Do you want to choose a new filename?"),
2656 from_utf8(fname.absFileName()));
2657 int const ret = Alert::prompt(_("Chosen File Already Open"),
2658 text, 0, 1, _("&Rename"), _("&Cancel"));
2660 case 0: return renameBuffer(b, docstring(), kind);
2661 case 1: return false;
2666 bool const existsLocal = fname.exists();
2667 bool const existsInVC = LyXVC::fileInVC(fname);
2668 if (existsLocal || existsInVC) {
2669 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2670 if (kind != LV_WRITE_AS && existsInVC) {
2671 // renaming to a name that is already in VC
2673 docstring text = bformat(_("The document %1$s "
2674 "is already registered.\n\n"
2675 "Do you want to choose a new name?"),
2677 docstring const title = (kind == LV_VC_RENAME) ?
2678 _("Rename document?") : _("Copy document?");
2679 docstring const button = (kind == LV_VC_RENAME) ?
2680 _("&Rename") : _("&Copy");
2681 int const ret = Alert::prompt(title, text, 0, 1,
2682 button, _("&Cancel"));
2684 case 0: return renameBuffer(b, docstring(), kind);
2685 case 1: return false;
2690 docstring text = bformat(_("The document %1$s "
2691 "already exists.\n\n"
2692 "Do you want to overwrite that document?"),
2694 int const ret = Alert::prompt(_("Overwrite document?"),
2695 text, 0, 2, _("&Overwrite"),
2696 _("&Rename"), _("&Cancel"));
2699 case 1: return renameBuffer(b, docstring(), kind);
2700 case 2: return false;
2706 case LV_VC_RENAME: {
2707 string msg = b.lyxvc().rename(fname);
2710 message(from_utf8(msg));
2714 string msg = b.lyxvc().copy(fname);
2717 message(from_utf8(msg));
2723 // LyXVC created the file already in case of LV_VC_RENAME or
2724 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2725 // relative paths of included stuff right if we moved e.g. from
2726 // /a/b.lyx to /a/c/b.lyx.
2728 bool const saved = saveBuffer(b, fname);
2735 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2737 FileName fname = b.fileName();
2739 FileDialog dlg(qt_("Choose a filename to export the document as"));
2740 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2743 QString const anyformat = qt_("Guess from extension (*.*)");
2746 vector<Format const *> export_formats;
2747 for (Format const & f : theFormats())
2748 if (f.documentFormat())
2749 export_formats.push_back(&f);
2750 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2751 map<QString, string> fmap;
2754 for (Format const * f : export_formats) {
2755 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2756 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2758 from_ascii(f->extension())));
2759 types << loc_filter;
2760 fmap[loc_filter] = f->name();
2761 if (from_ascii(f->name()) == iformat) {
2762 filter = loc_filter;
2763 ext = f->extension();
2766 string ofname = fname.onlyFileName();
2768 ofname = support::changeExtension(ofname, ext);
2769 FileDialog::Result result =
2770 dlg.save(toqstr(fname.onlyPath().absFileName()),
2774 if (result.first != FileDialog::Chosen)
2778 fname.set(fromqstr(result.second));
2779 if (filter == anyformat)
2780 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2782 fmt_name = fmap[filter];
2783 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2784 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2786 if (fmt_name.empty() || fname.empty())
2789 // fname is now the new Buffer location.
2790 if (FileName(fname).exists()) {
2791 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2792 docstring text = bformat(_("The document %1$s already "
2793 "exists.\n\nDo you want to "
2794 "overwrite that document?"),
2796 int const ret = Alert::prompt(_("Overwrite document?"),
2797 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2800 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2801 case 2: return false;
2805 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2808 return dr.dispatched();
2812 bool GuiView::saveBuffer(Buffer & b)
2814 return saveBuffer(b, FileName());
2818 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2820 if (workArea(b) && workArea(b)->inDialogMode())
2823 if (fn.empty() && b.isUnnamed())
2824 return renameBuffer(b, docstring());
2826 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2828 theSession().lastFiles().add(b.fileName());
2829 theSession().writeFile();
2833 // Switch to this Buffer.
2836 // FIXME: we don't tell the user *WHY* the save failed !!
2837 docstring const file = makeDisplayPath(b.absFileName(), 30);
2838 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2839 "Do you want to rename the document and "
2840 "try again?"), file);
2841 int const ret = Alert::prompt(_("Rename and save?"),
2842 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2845 if (!renameBuffer(b, docstring()))
2854 return saveBuffer(b, fn);
2858 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2860 return closeWorkArea(wa, false);
2864 // We only want to close the buffer if it is not visible in other workareas
2865 // of the same view, nor in other views, and if this is not a child
2866 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2868 Buffer & buf = wa->bufferView().buffer();
2870 bool last_wa = d.countWorkAreasOf(buf) == 1
2871 && !inOtherView(buf) && !buf.parent();
2873 bool close_buffer = last_wa;
2876 if (lyxrc.close_buffer_with_last_view == "yes")
2878 else if (lyxrc.close_buffer_with_last_view == "no")
2879 close_buffer = false;
2882 if (buf.isUnnamed())
2883 file = from_utf8(buf.fileName().onlyFileName());
2885 file = buf.fileName().displayName(30);
2886 docstring const text = bformat(
2887 _("Last view on document %1$s is being closed.\n"
2888 "Would you like to close or hide the document?\n"
2890 "Hidden documents can be displayed back through\n"
2891 "the menu: View->Hidden->...\n"
2893 "To remove this question, set your preference in:\n"
2894 " Tools->Preferences->Look&Feel->UserInterface\n"
2896 int ret = Alert::prompt(_("Close or hide document?"),
2897 text, 0, 1, _("&Close"), _("&Hide"));
2898 close_buffer = (ret == 0);
2902 return closeWorkArea(wa, close_buffer);
2906 bool GuiView::closeBuffer()
2908 GuiWorkArea * wa = currentMainWorkArea();
2909 // coverity complained about this
2910 // it seems unnecessary, but perhaps is worth the check
2911 LASSERT(wa, return false);
2913 setCurrentWorkArea(wa);
2914 Buffer & buf = wa->bufferView().buffer();
2915 return closeWorkArea(wa, !buf.parent());
2919 void GuiView::writeSession() const {
2920 GuiWorkArea const * active_wa = currentMainWorkArea();
2921 for (int i = 0; i < d.splitter_->count(); ++i) {
2922 TabWorkArea * twa = d.tabWorkArea(i);
2923 for (int j = 0; j < twa->count(); ++j) {
2924 GuiWorkArea * wa = twa->workArea(j);
2925 Buffer & buf = wa->bufferView().buffer();
2926 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2932 bool GuiView::closeBufferAll()
2934 // Close the workareas in all other views
2935 QList<int> const ids = guiApp->viewIds();
2936 for (int i = 0; i != ids.size(); ++i) {
2937 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2941 // Close our own workareas
2942 if (!closeWorkAreaAll())
2945 // Now close the hidden buffers. We prevent hidden buffers from being
2946 // dirty, so we can just close them.
2947 theBufferList().closeAll();
2952 bool GuiView::closeWorkAreaAll()
2954 setCurrentWorkArea(currentMainWorkArea());
2956 // We might be in a situation that there is still a tabWorkArea, but
2957 // there are no tabs anymore. This can happen when we get here after a
2958 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2959 // many TabWorkArea's have no documents anymore.
2962 // We have to call count() each time, because it can happen that
2963 // more than one splitter will disappear in one iteration (bug 5998).
2964 while (d.splitter_->count() > empty_twa) {
2965 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2967 if (twa->count() == 0)
2970 setCurrentWorkArea(twa->currentWorkArea());
2971 if (!closeTabWorkArea(twa))
2979 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2984 Buffer & buf = wa->bufferView().buffer();
2986 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2987 Alert::warning(_("Close document"),
2988 _("Document could not be closed because it is being processed by LyX."));
2993 return closeBuffer(buf);
2995 if (!inMultiTabs(wa))
2996 if (!saveBufferIfNeeded(buf, true))
3004 bool GuiView::closeBuffer(Buffer & buf)
3006 // If we are in a close_event all children will be closed in some time,
3007 // so no need to do it here. This will ensure that the children end up
3008 // in the session file in the correct order. If we close the master
3009 // buffer, we can close or release the child buffers here too.
3010 bool success = true;
3012 ListOfBuffers clist = buf.getChildren();
3013 ListOfBuffers::const_iterator it = clist.begin();
3014 ListOfBuffers::const_iterator const bend = clist.end();
3015 for (; it != bend; ++it) {
3016 Buffer * child_buf = *it;
3017 if (theBufferList().isOthersChild(&buf, child_buf)) {
3018 child_buf->setParent(0);
3022 // FIXME: should we look in other tabworkareas?
3023 // ANSWER: I don't think so. I've tested, and if the child is
3024 // open in some other window, it closes without a problem.
3025 GuiWorkArea * child_wa = workArea(*child_buf);
3027 success = closeWorkArea(child_wa, true);
3031 // In this case the child buffer is open but hidden.
3032 // It therefore should not (MUST NOT) be dirty!
3033 LATTEST(child_buf->isClean());
3034 theBufferList().release(child_buf);
3039 // goto bookmark to update bookmark pit.
3040 // FIXME: we should update only the bookmarks related to this buffer!
3041 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3042 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3043 guiApp->gotoBookmark(i+1, false, false);
3045 if (saveBufferIfNeeded(buf, false)) {
3046 buf.removeAutosaveFile();
3047 theBufferList().release(&buf);
3051 // open all children again to avoid a crash because of dangling
3052 // pointers (bug 6603)
3058 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3060 while (twa == d.currentTabWorkArea()) {
3061 twa->setCurrentIndex(twa->count() - 1);
3063 GuiWorkArea * wa = twa->currentWorkArea();
3064 Buffer & b = wa->bufferView().buffer();
3066 // We only want to close the buffer if the same buffer is not visible
3067 // in another view, and if this is not a child and if we are closing
3068 // a view (not a tabgroup).
3069 bool const close_buffer =
3070 !inOtherView(b) && !b.parent() && closing_;
3072 if (!closeWorkArea(wa, close_buffer))
3079 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3081 if (buf.isClean() || buf.paragraphs().empty())
3084 // Switch to this Buffer.
3090 if (buf.isUnnamed()) {
3091 file = from_utf8(buf.fileName().onlyFileName());
3094 FileName filename = buf.fileName();
3096 file = filename.displayName(30);
3097 exists = filename.exists();
3100 // Bring this window to top before asking questions.
3105 if (hiding && buf.isUnnamed()) {
3106 docstring const text = bformat(_("The document %1$s has not been "
3107 "saved yet.\n\nDo you want to save "
3108 "the document?"), file);
3109 ret = Alert::prompt(_("Save new document?"),
3110 text, 0, 1, _("&Save"), _("&Cancel"));
3114 docstring const text = exists ?
3115 bformat(_("The document %1$s has unsaved changes."
3116 "\n\nDo you want to save the document or "
3117 "discard the changes?"), file) :
3118 bformat(_("The document %1$s has not been saved yet."
3119 "\n\nDo you want to save the document or "
3120 "discard it entirely?"), file);
3121 docstring const title = exists ?
3122 _("Save changed document?") : _("Save document?");
3123 ret = Alert::prompt(title, text, 0, 2,
3124 _("&Save"), _("&Discard"), _("&Cancel"));
3129 if (!saveBuffer(buf))
3133 // If we crash after this we could have no autosave file
3134 // but I guess this is really improbable (Jug).
3135 // Sometimes improbable things happen:
3136 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3137 // buf.removeAutosaveFile();
3139 // revert all changes
3150 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3152 Buffer & buf = wa->bufferView().buffer();
3154 for (int i = 0; i != d.splitter_->count(); ++i) {
3155 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3156 if (wa_ && wa_ != wa)
3159 return inOtherView(buf);
3163 bool GuiView::inOtherView(Buffer & buf)
3165 QList<int> const ids = guiApp->viewIds();
3167 for (int i = 0; i != ids.size(); ++i) {
3171 if (guiApp->view(ids[i]).workArea(buf))
3178 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3180 if (!documentBufferView())
3183 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3184 Buffer * const curbuf = &documentBufferView()->buffer();
3185 int nwa = twa->count();
3186 for (int i = 0; i < nwa; ++i) {
3187 if (&workArea(i)->bufferView().buffer() == curbuf) {
3189 if (np == NEXTBUFFER)
3190 next_index = (i == nwa - 1 ? 0 : i + 1);
3192 next_index = (i == 0 ? nwa - 1 : i - 1);
3194 twa->moveTab(i, next_index);
3196 setBuffer(&workArea(next_index)->bufferView().buffer());
3204 /// make sure the document is saved
3205 static bool ensureBufferClean(Buffer * buffer)
3207 LASSERT(buffer, return false);
3208 if (buffer->isClean() && !buffer->isUnnamed())
3211 docstring const file = buffer->fileName().displayName(30);
3214 if (!buffer->isUnnamed()) {
3215 text = bformat(_("The document %1$s has unsaved "
3216 "changes.\n\nDo you want to save "
3217 "the document?"), file);
3218 title = _("Save changed document?");
3221 text = bformat(_("The document %1$s has not been "
3222 "saved yet.\n\nDo you want to save "
3223 "the document?"), file);
3224 title = _("Save new document?");
3226 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3229 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3231 return buffer->isClean() && !buffer->isUnnamed();
3235 bool GuiView::reloadBuffer(Buffer & buf)
3237 currentBufferView()->cursor().reset();
3238 Buffer::ReadStatus status = buf.reload();
3239 return status == Buffer::ReadSuccess;
3243 void GuiView::checkExternallyModifiedBuffers()
3245 BufferList::iterator bit = theBufferList().begin();
3246 BufferList::iterator const bend = theBufferList().end();
3247 for (; bit != bend; ++bit) {
3248 Buffer * buf = *bit;
3249 if (buf->fileName().exists() && buf->isChecksumModified()) {
3250 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3251 " Reload now? Any local changes will be lost."),
3252 from_utf8(buf->absFileName()));
3253 int const ret = Alert::prompt(_("Reload externally changed document?"),
3254 text, 0, 1, _("&Reload"), _("&Cancel"));
3262 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3264 Buffer * buffer = documentBufferView()
3265 ? &(documentBufferView()->buffer()) : 0;
3267 switch (cmd.action()) {
3268 case LFUN_VC_REGISTER:
3269 if (!buffer || !ensureBufferClean(buffer))
3271 if (!buffer->lyxvc().inUse()) {
3272 if (buffer->lyxvc().registrer()) {
3273 reloadBuffer(*buffer);
3274 dr.clearMessageUpdate();
3279 case LFUN_VC_RENAME:
3280 case LFUN_VC_COPY: {
3281 if (!buffer || !ensureBufferClean(buffer))
3283 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3284 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3285 // Some changes are not yet committed.
3286 // We test here and not in getStatus(), since
3287 // this test is expensive.
3289 LyXVC::CommandResult ret =
3290 buffer->lyxvc().checkIn(log);
3292 if (ret == LyXVC::ErrorCommand ||
3293 ret == LyXVC::VCSuccess)
3294 reloadBuffer(*buffer);
3295 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3296 frontend::Alert::error(
3297 _("Revision control error."),
3298 _("Document could not be checked in."));
3302 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3303 LV_VC_RENAME : LV_VC_COPY;
3304 renameBuffer(*buffer, cmd.argument(), kind);
3309 case LFUN_VC_CHECK_IN:
3310 if (!buffer || !ensureBufferClean(buffer))
3312 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3314 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3316 // Only skip reloading if the checkin was cancelled or
3317 // an error occurred before the real checkin VCS command
3318 // was executed, since the VCS might have changed the
3319 // file even if it could not checkin successfully.
3320 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3321 reloadBuffer(*buffer);
3325 case LFUN_VC_CHECK_OUT:
3326 if (!buffer || !ensureBufferClean(buffer))
3328 if (buffer->lyxvc().inUse()) {
3329 dr.setMessage(buffer->lyxvc().checkOut());
3330 reloadBuffer(*buffer);
3334 case LFUN_VC_LOCKING_TOGGLE:
3335 LASSERT(buffer, return);
3336 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3338 if (buffer->lyxvc().inUse()) {
3339 string res = buffer->lyxvc().lockingToggle();
3341 frontend::Alert::error(_("Revision control error."),
3342 _("Error when setting the locking property."));
3345 reloadBuffer(*buffer);
3350 case LFUN_VC_REVERT:
3351 LASSERT(buffer, return);
3352 if (buffer->lyxvc().revert()) {
3353 reloadBuffer(*buffer);
3354 dr.clearMessageUpdate();
3358 case LFUN_VC_UNDO_LAST:
3359 LASSERT(buffer, return);
3360 buffer->lyxvc().undoLast();
3361 reloadBuffer(*buffer);
3362 dr.clearMessageUpdate();
3365 case LFUN_VC_REPO_UPDATE:
3366 LASSERT(buffer, return);
3367 if (ensureBufferClean(buffer)) {
3368 dr.setMessage(buffer->lyxvc().repoUpdate());
3369 checkExternallyModifiedBuffers();
3373 case LFUN_VC_COMMAND: {
3374 string flag = cmd.getArg(0);
3375 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3378 if (contains(flag, 'M')) {
3379 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3382 string path = cmd.getArg(1);
3383 if (contains(path, "$$p") && buffer)
3384 path = subst(path, "$$p", buffer->filePath());
3385 LYXERR(Debug::LYXVC, "Directory: " << path);
3387 if (!pp.isReadableDirectory()) {
3388 lyxerr << _("Directory is not accessible.") << endl;
3391 support::PathChanger p(pp);
3393 string command = cmd.getArg(2);
3394 if (command.empty())
3397 command = subst(command, "$$i", buffer->absFileName());
3398 command = subst(command, "$$p", buffer->filePath());
3400 command = subst(command, "$$m", to_utf8(message));
3401 LYXERR(Debug::LYXVC, "Command: " << command);
3403 one.startscript(Systemcall::Wait, command);
3407 if (contains(flag, 'I'))
3408 buffer->markDirty();
3409 if (contains(flag, 'R'))
3410 reloadBuffer(*buffer);
3415 case LFUN_VC_COMPARE: {
3416 if (cmd.argument().empty()) {
3417 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3421 string rev1 = cmd.getArg(0);
3426 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3429 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3430 f2 = buffer->absFileName();
3432 string rev2 = cmd.getArg(1);
3436 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3440 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3441 f1 << "\n" << f2 << "\n" );
3442 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3443 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3453 void GuiView::openChildDocument(string const & fname)
3455 LASSERT(documentBufferView(), return);
3456 Buffer & buffer = documentBufferView()->buffer();
3457 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3458 documentBufferView()->saveBookmark(false);
3460 if (theBufferList().exists(filename)) {
3461 child = theBufferList().getBuffer(filename);
3464 message(bformat(_("Opening child document %1$s..."),
3465 makeDisplayPath(filename.absFileName())));
3466 child = loadDocument(filename, false);
3468 // Set the parent name of the child document.
3469 // This makes insertion of citations and references in the child work,
3470 // when the target is in the parent or another child document.
3472 child->setParent(&buffer);
3476 bool GuiView::goToFileRow(string const & argument)
3480 size_t i = argument.find_last_of(' ');
3481 if (i != string::npos) {
3482 file_name = os::internal_path(trim(argument.substr(0, i)));
3483 istringstream is(argument.substr(i + 1));
3488 if (i == string::npos) {
3489 LYXERR0("Wrong argument: " << argument);
3493 string const abstmp = package().temp_dir().absFileName();
3494 string const realtmp = package().temp_dir().realPath();
3495 // We have to use os::path_prefix_is() here, instead of
3496 // simply prefixIs(), because the file name comes from
3497 // an external application and may need case adjustment.
3498 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3499 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3500 // Needed by inverse dvi search. If it is a file
3501 // in tmpdir, call the apropriated function.
3502 // If tmpdir is a symlink, we may have the real
3503 // path passed back, so we correct for that.
3504 if (!prefixIs(file_name, abstmp))
3505 file_name = subst(file_name, realtmp, abstmp);
3506 buf = theBufferList().getBufferFromTmp(file_name);
3508 // Must replace extension of the file to be .lyx
3509 // and get full path
3510 FileName const s = fileSearch(string(),
3511 support::changeExtension(file_name, ".lyx"), "lyx");
3512 // Either change buffer or load the file
3513 if (theBufferList().exists(s))
3514 buf = theBufferList().getBuffer(s);
3515 else if (s.exists()) {
3516 buf = loadDocument(s);
3521 _("File does not exist: %1$s"),
3522 makeDisplayPath(file_name)));
3528 _("No buffer for file: %1$s."),
3529 makeDisplayPath(file_name))
3534 bool success = documentBufferView()->setCursorFromRow(row);
3536 LYXERR(Debug::LATEX,
3537 "setCursorFromRow: invalid position for row " << row);
3538 frontend::Alert::error(_("Inverse Search Failed"),
3539 _("Invalid position requested by inverse search.\n"
3540 "You may need to update the viewed document."));
3546 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3548 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3549 menu->exec(QCursor::pos());
3554 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3555 Buffer const * orig, Buffer * clone, string const & format)
3557 Buffer::ExportStatus const status = func(format);
3559 // the cloning operation will have produced a clone of the entire set of
3560 // documents, starting from the master. so we must delete those.
3561 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3563 busyBuffers.remove(orig);
3568 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3569 Buffer const * orig, Buffer * clone, string const & format)
3571 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3573 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3577 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3578 Buffer const * orig, Buffer * clone, string const & format)
3580 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3582 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3586 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3587 Buffer const * orig, Buffer * clone, string const & format)
3589 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3591 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3595 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3596 string const & argument,
3597 Buffer const * used_buffer,
3598 docstring const & msg,
3599 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3600 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3601 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3607 string format = argument;
3609 format = used_buffer->params().getDefaultOutputFormat();
3610 processing_format = format;
3612 progress_->clearMessages();
3615 #if EXPORT_in_THREAD
3617 GuiViewPrivate::busyBuffers.insert(used_buffer);
3618 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3619 if (!cloned_buffer) {
3620 Alert::error(_("Export Error"),
3621 _("Error cloning the Buffer."));
3624 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3629 setPreviewFuture(f);
3630 last_export_format = used_buffer->params().bufferFormat();
3633 // We are asynchronous, so we don't know here anything about the success
3636 Buffer::ExportStatus status;
3638 status = (used_buffer->*syncFunc)(format, false);
3639 } else if (previewFunc) {
3640 status = (used_buffer->*previewFunc)(format);
3643 handleExportStatus(gv_, status, format);
3645 return (status == Buffer::ExportSuccess
3646 || status == Buffer::PreviewSuccess);
3650 Buffer::ExportStatus status;
3652 status = (used_buffer->*syncFunc)(format, true);
3653 } else if (previewFunc) {
3654 status = (used_buffer->*previewFunc)(format);
3657 handleExportStatus(gv_, status, format);
3659 return (status == Buffer::ExportSuccess
3660 || status == Buffer::PreviewSuccess);
3664 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3666 BufferView * bv = currentBufferView();
3667 LASSERT(bv, return);
3669 // Let the current BufferView dispatch its own actions.
3670 bv->dispatch(cmd, dr);
3671 if (dr.dispatched())
3674 // Try with the document BufferView dispatch if any.
3675 BufferView * doc_bv = documentBufferView();
3676 if (doc_bv && doc_bv != bv) {
3677 doc_bv->dispatch(cmd, dr);
3678 if (dr.dispatched())
3682 // Then let the current Cursor dispatch its own actions.
3683 bv->cursor().dispatch(cmd);
3685 // update completion. We do it here and not in
3686 // processKeySym to avoid another redraw just for a
3687 // changed inline completion
3688 if (cmd.origin() == FuncRequest::KEYBOARD) {
3689 if (cmd.action() == LFUN_SELF_INSERT
3690 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3691 updateCompletion(bv->cursor(), true, true);
3692 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3693 updateCompletion(bv->cursor(), false, true);
3695 updateCompletion(bv->cursor(), false, false);
3698 dr = bv->cursor().result();
3702 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3704 BufferView * bv = currentBufferView();
3705 // By default we won't need any update.
3706 dr.screenUpdate(Update::None);
3707 // assume cmd will be dispatched
3708 dr.dispatched(true);
3710 Buffer * doc_buffer = documentBufferView()
3711 ? &(documentBufferView()->buffer()) : 0;
3713 if (cmd.origin() == FuncRequest::TOC) {
3714 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3715 // FIXME: do we need to pass a DispatchResult object here?
3716 toc->doDispatch(bv->cursor(), cmd);
3720 string const argument = to_utf8(cmd.argument());
3722 switch(cmd.action()) {
3723 case LFUN_BUFFER_CHILD_OPEN:
3724 openChildDocument(to_utf8(cmd.argument()));
3727 case LFUN_BUFFER_IMPORT:
3728 importDocument(to_utf8(cmd.argument()));
3731 case LFUN_MASTER_BUFFER_EXPORT:
3733 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3735 case LFUN_BUFFER_EXPORT: {
3738 // GCC only sees strfwd.h when building merged
3739 if (::lyx::operator==(cmd.argument(), "custom")) {
3740 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3741 // so the following test should not be needed.
3742 // In principle, we could try to switch to such a view...
3743 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3744 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3748 string const dest = cmd.getArg(1);
3749 FileName target_dir;
3750 if (!dest.empty() && FileName::isAbsolute(dest))
3751 target_dir = FileName(support::onlyPath(dest));
3753 target_dir = doc_buffer->fileName().onlyPath();
3755 string const format = (argument.empty() || argument == "default") ?
3756 doc_buffer->params().getDefaultOutputFormat() : argument;
3758 if ((dest.empty() && doc_buffer->isUnnamed())
3759 || !target_dir.isDirWritable()) {
3760 exportBufferAs(*doc_buffer, from_utf8(format));
3763 /* TODO/Review: Is it a problem to also export the children?
3764 See the update_unincluded flag */
3765 d.asyncBufferProcessing(format,
3768 &GuiViewPrivate::exportAndDestroy,
3770 0, cmd.allowAsync());
3771 // TODO Inform user about success
3775 case LFUN_BUFFER_EXPORT_AS: {
3776 LASSERT(doc_buffer, break);
3777 docstring f = cmd.argument();
3779 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3780 exportBufferAs(*doc_buffer, f);
3784 case LFUN_BUFFER_UPDATE: {
3785 d.asyncBufferProcessing(argument,
3788 &GuiViewPrivate::compileAndDestroy,
3790 0, cmd.allowAsync());
3793 case LFUN_BUFFER_VIEW: {
3794 d.asyncBufferProcessing(argument,
3796 _("Previewing ..."),
3797 &GuiViewPrivate::previewAndDestroy,
3799 &Buffer::preview, cmd.allowAsync());
3802 case LFUN_MASTER_BUFFER_UPDATE: {
3803 d.asyncBufferProcessing(argument,
3804 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3806 &GuiViewPrivate::compileAndDestroy,
3808 0, cmd.allowAsync());
3811 case LFUN_MASTER_BUFFER_VIEW: {
3812 d.asyncBufferProcessing(argument,
3813 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3815 &GuiViewPrivate::previewAndDestroy,
3816 0, &Buffer::preview, cmd.allowAsync());
3819 case LFUN_EXPORT_CANCEL: {
3820 Systemcall::killscript();
3823 case LFUN_BUFFER_SWITCH: {
3824 string const file_name = to_utf8(cmd.argument());
3825 if (!FileName::isAbsolute(file_name)) {
3827 dr.setMessage(_("Absolute filename expected."));
3831 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3834 dr.setMessage(_("Document not loaded"));
3838 // Do we open or switch to the buffer in this view ?
3839 if (workArea(*buffer)
3840 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3845 // Look for the buffer in other views
3846 QList<int> const ids = guiApp->viewIds();
3848 for (; i != ids.size(); ++i) {
3849 GuiView & gv = guiApp->view(ids[i]);
3850 if (gv.workArea(*buffer)) {
3852 gv.activateWindow();
3854 gv.setBuffer(buffer);
3859 // If necessary, open a new window as a last resort
3860 if (i == ids.size()) {
3861 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3867 case LFUN_BUFFER_NEXT:
3868 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3871 case LFUN_BUFFER_MOVE_NEXT:
3872 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3875 case LFUN_BUFFER_PREVIOUS:
3876 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3879 case LFUN_BUFFER_MOVE_PREVIOUS:
3880 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3883 case LFUN_BUFFER_CHKTEX:
3884 LASSERT(doc_buffer, break);
3885 doc_buffer->runChktex();
3888 case LFUN_COMMAND_EXECUTE: {
3889 command_execute_ = true;
3890 minibuffer_focus_ = true;
3893 case LFUN_DROP_LAYOUTS_CHOICE:
3894 d.layout_->showPopup();
3897 case LFUN_MENU_OPEN:
3898 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3899 menu->exec(QCursor::pos());
3902 case LFUN_FILE_INSERT:
3903 insertLyXFile(cmd.argument());
3906 case LFUN_FILE_INSERT_PLAINTEXT:
3907 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3908 string const fname = to_utf8(cmd.argument());
3909 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3910 dr.setMessage(_("Absolute filename expected."));
3914 FileName filename(fname);
3915 if (fname.empty()) {
3916 FileDialog dlg(qt_("Select file to insert"));
3918 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3919 QStringList(qt_("All Files (*)")));
3921 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3922 dr.setMessage(_("Canceled."));
3926 filename.set(fromqstr(result.second));
3930 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3931 bv->dispatch(new_cmd, dr);
3936 case LFUN_BUFFER_RELOAD: {
3937 LASSERT(doc_buffer, break);
3940 bool drop = (cmd.argument() == "dump");
3943 if (!drop && !doc_buffer->isClean()) {
3944 docstring const file =
3945 makeDisplayPath(doc_buffer->absFileName(), 20);
3946 if (doc_buffer->notifiesExternalModification()) {
3947 docstring text = _("The current version will be lost. "
3948 "Are you sure you want to load the version on disk "
3949 "of the document %1$s?");
3950 ret = Alert::prompt(_("Reload saved document?"),
3951 bformat(text, file), 1, 1,
3952 _("&Reload"), _("&Cancel"));
3954 docstring text = _("Any changes will be lost. "
3955 "Are you sure you want to revert to the saved version "
3956 "of the document %1$s?");
3957 ret = Alert::prompt(_("Revert to saved document?"),
3958 bformat(text, file), 1, 1,
3959 _("&Revert"), _("&Cancel"));
3964 doc_buffer->markClean();
3965 reloadBuffer(*doc_buffer);
3966 dr.forceBufferUpdate();
3971 case LFUN_BUFFER_WRITE:
3972 LASSERT(doc_buffer, break);
3973 saveBuffer(*doc_buffer);
3976 case LFUN_BUFFER_WRITE_AS:
3977 LASSERT(doc_buffer, break);
3978 renameBuffer(*doc_buffer, cmd.argument());
3981 case LFUN_BUFFER_WRITE_ALL: {
3982 Buffer * first = theBufferList().first();
3985 message(_("Saving all documents..."));
3986 // We cannot use a for loop as the buffer list cycles.
3989 if (!b->isClean()) {
3991 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3993 b = theBufferList().next(b);
3994 } while (b != first);
3995 dr.setMessage(_("All documents saved."));
3999 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4000 LASSERT(doc_buffer, break);
4001 doc_buffer->clearExternalModification();
4004 case LFUN_BUFFER_CLOSE:
4008 case LFUN_BUFFER_CLOSE_ALL:
4012 case LFUN_DEVEL_MODE_TOGGLE:
4013 devel_mode_ = !devel_mode_;
4015 dr.setMessage(_("Developer mode is now enabled."));
4017 dr.setMessage(_("Developer mode is now disabled."));
4020 case LFUN_TOOLBAR_TOGGLE: {
4021 string const name = cmd.getArg(0);
4022 if (GuiToolbar * t = toolbar(name))
4027 case LFUN_TOOLBAR_MOVABLE: {
4028 string const name = cmd.getArg(0);
4030 // toggle (all) toolbars movablility
4031 toolbarsMovable_ = !toolbarsMovable_;
4032 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4033 GuiToolbar * tb = toolbar(ti.name);
4034 if (tb && tb->isMovable() != toolbarsMovable_)
4035 // toggle toolbar movablity if it does not fit lock
4036 // (all) toolbars positions state silent = true, since
4037 // status bar notifications are slow
4040 if (toolbarsMovable_)
4041 dr.setMessage(_("Toolbars unlocked."));
4043 dr.setMessage(_("Toolbars locked."));
4044 } else if (GuiToolbar * t = toolbar(name)) {
4045 // toggle current toolbar movablity
4047 // update lock (all) toolbars positions
4048 updateLockToolbars();
4053 case LFUN_ICON_SIZE: {
4054 QSize size = d.iconSize(cmd.argument());
4056 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4057 size.width(), size.height()));
4061 case LFUN_DIALOG_UPDATE: {
4062 string const name = to_utf8(cmd.argument());
4063 if (name == "prefs" || name == "document")
4064 updateDialog(name, string());
4065 else if (name == "paragraph")
4066 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4067 else if (currentBufferView()) {
4068 Inset * inset = currentBufferView()->editedInset(name);
4069 // Can only update a dialog connected to an existing inset
4071 // FIXME: get rid of this indirection; GuiView ask the inset
4072 // if he is kind enough to update itself...
4073 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4074 //FIXME: pass DispatchResult here?
4075 inset->dispatch(currentBufferView()->cursor(), fr);
4081 case LFUN_DIALOG_TOGGLE: {
4082 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4083 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4084 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4088 case LFUN_DIALOG_DISCONNECT_INSET:
4089 disconnectDialog(to_utf8(cmd.argument()));
4092 case LFUN_DIALOG_HIDE: {
4093 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4097 case LFUN_DIALOG_SHOW: {
4098 string const name = cmd.getArg(0);
4099 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4101 if (name == "character") {
4102 sdata = freefont2string();
4104 showDialog("character", sdata);
4105 } else if (name == "latexlog") {
4106 // gettatus checks that
4107 LATTEST(doc_buffer);
4108 Buffer::LogType type;
4109 string const logfile = doc_buffer->logName(&type);
4111 case Buffer::latexlog:
4114 case Buffer::buildlog:
4115 sdata = "literate ";
4118 sdata += Lexer::quoteString(logfile);
4119 showDialog("log", sdata);
4120 } else if (name == "vclog") {
4121 // getStatus checks that
4122 LATTEST(doc_buffer);
4123 string const sdata2 = "vc " +
4124 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4125 showDialog("log", sdata2);
4126 } else if (name == "symbols") {
4127 sdata = bv->cursor().getEncoding()->name();
4129 showDialog("symbols", sdata);
4131 } else if (name == "prefs" && isFullScreen()) {
4132 lfunUiToggle("fullscreen");
4133 showDialog("prefs", sdata);
4135 showDialog(name, sdata);
4140 dr.setMessage(cmd.argument());
4143 case LFUN_UI_TOGGLE: {
4144 string arg = cmd.getArg(0);
4145 if (!lfunUiToggle(arg)) {
4146 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4147 dr.setMessage(bformat(msg, from_utf8(arg)));
4149 // Make sure the keyboard focus stays in the work area.
4154 case LFUN_VIEW_SPLIT: {
4155 LASSERT(doc_buffer, break);
4156 string const orientation = cmd.getArg(0);
4157 d.splitter_->setOrientation(orientation == "vertical"
4158 ? Qt::Vertical : Qt::Horizontal);
4159 TabWorkArea * twa = addTabWorkArea();
4160 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4161 setCurrentWorkArea(wa);
4164 case LFUN_TAB_GROUP_CLOSE:
4165 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4166 closeTabWorkArea(twa);
4167 d.current_work_area_ = 0;
4168 twa = d.currentTabWorkArea();
4169 // Switch to the next GuiWorkArea in the found TabWorkArea.
4171 // Make sure the work area is up to date.
4172 setCurrentWorkArea(twa->currentWorkArea());
4174 setCurrentWorkArea(0);
4179 case LFUN_VIEW_CLOSE:
4180 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4181 closeWorkArea(twa->currentWorkArea());
4182 d.current_work_area_ = 0;
4183 twa = d.currentTabWorkArea();
4184 // Switch to the next GuiWorkArea in the found TabWorkArea.
4186 // Make sure the work area is up to date.
4187 setCurrentWorkArea(twa->currentWorkArea());
4189 setCurrentWorkArea(0);
4194 case LFUN_COMPLETION_INLINE:
4195 if (d.current_work_area_)
4196 d.current_work_area_->completer().showInline();
4199 case LFUN_COMPLETION_POPUP:
4200 if (d.current_work_area_)
4201 d.current_work_area_->completer().showPopup();
4206 if (d.current_work_area_)
4207 d.current_work_area_->completer().tab();
4210 case LFUN_COMPLETION_CANCEL:
4211 if (d.current_work_area_) {
4212 if (d.current_work_area_->completer().popupVisible())
4213 d.current_work_area_->completer().hidePopup();
4215 d.current_work_area_->completer().hideInline();
4219 case LFUN_COMPLETION_ACCEPT:
4220 if (d.current_work_area_)
4221 d.current_work_area_->completer().activate();
4224 case LFUN_BUFFER_ZOOM_IN:
4225 case LFUN_BUFFER_ZOOM_OUT:
4226 case LFUN_BUFFER_ZOOM: {
4227 if (cmd.argument().empty()) {
4228 if (cmd.action() == LFUN_BUFFER_ZOOM)
4230 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4235 if (cmd.action() == LFUN_BUFFER_ZOOM)
4236 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4237 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4238 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4240 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4243 // Actual zoom value: default zoom + fractional extra value
4244 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4245 if (zoom < static_cast<int>(zoom_min_))
4248 lyxrc.currentZoom = zoom;
4250 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4251 lyxrc.currentZoom, lyxrc.defaultZoom));
4253 // The global QPixmapCache is used in GuiPainter to cache text
4254 // painting so we must reset it.
4255 QPixmapCache::clear();
4256 guiApp->fontLoader().update();
4257 dr.screenUpdate(Update::Force | Update::FitCursor);
4261 case LFUN_VC_REGISTER:
4262 case LFUN_VC_RENAME:
4264 case LFUN_VC_CHECK_IN:
4265 case LFUN_VC_CHECK_OUT:
4266 case LFUN_VC_REPO_UPDATE:
4267 case LFUN_VC_LOCKING_TOGGLE:
4268 case LFUN_VC_REVERT:
4269 case LFUN_VC_UNDO_LAST:
4270 case LFUN_VC_COMMAND:
4271 case LFUN_VC_COMPARE:
4272 dispatchVC(cmd, dr);
4275 case LFUN_SERVER_GOTO_FILE_ROW:
4276 if(goToFileRow(to_utf8(cmd.argument())))
4277 dr.screenUpdate(Update::Force | Update::FitCursor);
4280 case LFUN_LYX_ACTIVATE:
4284 case LFUN_FORWARD_SEARCH: {
4285 // it seems safe to assume we have a document buffer, since
4286 // getStatus wants one.
4287 LATTEST(doc_buffer);
4288 Buffer const * doc_master = doc_buffer->masterBuffer();
4289 FileName const path(doc_master->temppath());
4290 string const texname = doc_master->isChild(doc_buffer)
4291 ? DocFileName(changeExtension(
4292 doc_buffer->absFileName(),
4293 "tex")).mangledFileName()
4294 : doc_buffer->latexName();
4295 string const fulltexname =
4296 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4297 string const mastername =
4298 removeExtension(doc_master->latexName());
4299 FileName const dviname(addName(path.absFileName(),
4300 addExtension(mastername, "dvi")));
4301 FileName const pdfname(addName(path.absFileName(),
4302 addExtension(mastername, "pdf")));
4303 bool const have_dvi = dviname.exists();
4304 bool const have_pdf = pdfname.exists();
4305 if (!have_dvi && !have_pdf) {
4306 dr.setMessage(_("Please, preview the document first."));
4309 string outname = dviname.onlyFileName();
4310 string command = lyxrc.forward_search_dvi;
4311 if (!have_dvi || (have_pdf &&
4312 pdfname.lastModified() > dviname.lastModified())) {
4313 outname = pdfname.onlyFileName();
4314 command = lyxrc.forward_search_pdf;
4317 DocIterator cur = bv->cursor();
4318 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4319 LYXERR(Debug::ACTION, "Forward search: row:" << row
4321 if (row == -1 || command.empty()) {
4322 dr.setMessage(_("Couldn't proceed."));
4325 string texrow = convert<string>(row);
4327 command = subst(command, "$$n", texrow);
4328 command = subst(command, "$$f", fulltexname);
4329 command = subst(command, "$$t", texname);
4330 command = subst(command, "$$o", outname);
4332 PathChanger p(path);
4334 one.startscript(Systemcall::DontWait, command);
4338 case LFUN_SPELLING_CONTINUOUSLY:
4339 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4340 dr.screenUpdate(Update::Force);
4344 // The LFUN must be for one of BufferView, Buffer or Cursor;
4346 dispatchToBufferView(cmd, dr);
4350 // Part of automatic menu appearance feature.
4351 if (isFullScreen()) {
4352 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4356 // Need to update bv because many LFUNs here might have destroyed it
4357 bv = currentBufferView();
4359 // Clear non-empty selections
4360 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4362 Cursor & cur = bv->cursor();
4363 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4364 cur.clearSelection();
4370 bool GuiView::lfunUiToggle(string const & ui_component)
4372 if (ui_component == "scrollbar") {
4373 // hide() is of no help
4374 if (d.current_work_area_->verticalScrollBarPolicy() ==
4375 Qt::ScrollBarAlwaysOff)
4377 d.current_work_area_->setVerticalScrollBarPolicy(
4378 Qt::ScrollBarAsNeeded);
4380 d.current_work_area_->setVerticalScrollBarPolicy(
4381 Qt::ScrollBarAlwaysOff);
4382 } else if (ui_component == "statusbar") {
4383 statusBar()->setVisible(!statusBar()->isVisible());
4384 } else if (ui_component == "menubar") {
4385 menuBar()->setVisible(!menuBar()->isVisible());
4387 if (ui_component == "frame") {
4389 getContentsMargins(&l, &t, &r, &b);
4390 //are the frames in default state?
4391 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4393 setContentsMargins(-2, -2, -2, -2);
4395 setContentsMargins(0, 0, 0, 0);
4398 if (ui_component == "fullscreen") {
4406 void GuiView::toggleFullScreen()
4408 if (isFullScreen()) {
4409 for (int i = 0; i != d.splitter_->count(); ++i)
4410 d.tabWorkArea(i)->setFullScreen(false);
4411 setContentsMargins(0, 0, 0, 0);
4412 setWindowState(windowState() ^ Qt::WindowFullScreen);
4415 statusBar()->show();
4418 hideDialogs("prefs", 0);
4419 for (int i = 0; i != d.splitter_->count(); ++i)
4420 d.tabWorkArea(i)->setFullScreen(true);
4421 setContentsMargins(-2, -2, -2, -2);
4423 setWindowState(windowState() ^ Qt::WindowFullScreen);
4424 if (lyxrc.full_screen_statusbar)
4425 statusBar()->hide();
4426 if (lyxrc.full_screen_menubar)
4428 if (lyxrc.full_screen_toolbars) {
4429 ToolbarMap::iterator end = d.toolbars_.end();
4430 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4435 // give dialogs like the TOC a chance to adapt
4440 Buffer const * GuiView::updateInset(Inset const * inset)
4445 Buffer const * inset_buffer = &(inset->buffer());
4447 for (int i = 0; i != d.splitter_->count(); ++i) {
4448 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4451 Buffer const * buffer = &(wa->bufferView().buffer());
4452 if (inset_buffer == buffer)
4453 wa->scheduleRedraw(true);
4455 return inset_buffer;
4459 void GuiView::restartCaret()
4461 /* When we move around, or type, it's nice to be able to see
4462 * the caret immediately after the keypress.
4464 if (d.current_work_area_)
4465 d.current_work_area_->startBlinkingCaret();
4467 // Take this occasion to update the other GUI elements.
4473 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4475 if (d.current_work_area_)
4476 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4481 // This list should be kept in sync with the list of insets in
4482 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4483 // dialog should have the same name as the inset.
4484 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4485 // docs in LyXAction.cpp.
4487 char const * const dialognames[] = {
4489 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4490 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4491 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4492 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4493 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4494 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4495 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4496 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4498 char const * const * const end_dialognames =
4499 dialognames + (sizeof(dialognames) / sizeof(char *));
4503 cmpCStr(char const * name) : name_(name) {}
4504 bool operator()(char const * other) {
4505 return strcmp(other, name_) == 0;
4512 bool isValidName(string const & name)
4514 return find_if(dialognames, end_dialognames,
4515 cmpCStr(name.c_str())) != end_dialognames;
4521 void GuiView::resetDialogs()
4523 // Make sure that no LFUN uses any GuiView.
4524 guiApp->setCurrentView(0);
4528 constructToolbars();
4529 guiApp->menus().fillMenuBar(menuBar(), this, false);
4530 d.layout_->updateContents(true);
4531 // Now update controls with current buffer.
4532 guiApp->setCurrentView(this);
4538 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4540 if (!isValidName(name))
4543 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4545 if (it != d.dialogs_.end()) {
4547 it->second->hideView();
4548 return it->second.get();
4551 Dialog * dialog = build(name);
4552 d.dialogs_[name].reset(dialog);
4553 if (lyxrc.allow_geometry_session)
4554 dialog->restoreSession();
4561 void GuiView::showDialog(string const & name, string const & sdata,
4564 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4568 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4574 const string name = fromqstr(qname);
4575 const string sdata = fromqstr(qdata);
4579 Dialog * dialog = findOrBuild(name, false);
4581 bool const visible = dialog->isVisibleView();
4582 dialog->showData(sdata);
4583 if (inset && currentBufferView())
4584 currentBufferView()->editInset(name, inset);
4585 // We only set the focus to the new dialog if it was not yet
4586 // visible in order not to change the existing previous behaviour
4588 // activateWindow is needed for floating dockviews
4589 dialog->asQWidget()->raise();
4590 dialog->asQWidget()->activateWindow();
4591 dialog->asQWidget()->setFocus();
4595 catch (ExceptionMessage const & ex) {
4603 bool GuiView::isDialogVisible(string const & name) const
4605 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4606 if (it == d.dialogs_.end())
4608 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4612 void GuiView::hideDialog(string const & name, Inset * inset)
4614 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4615 if (it == d.dialogs_.end())
4619 if (!currentBufferView())
4621 if (inset != currentBufferView()->editedInset(name))
4625 Dialog * const dialog = it->second.get();
4626 if (dialog->isVisibleView())
4628 if (currentBufferView())
4629 currentBufferView()->editInset(name, 0);
4633 void GuiView::disconnectDialog(string const & name)
4635 if (!isValidName(name))
4637 if (currentBufferView())
4638 currentBufferView()->editInset(name, 0);
4642 void GuiView::hideAll() const
4644 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4645 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4647 for(; it != end; ++it)
4648 it->second->hideView();
4652 void GuiView::updateDialogs()
4654 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4655 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4657 for(; it != end; ++it) {
4658 Dialog * dialog = it->second.get();
4660 if (dialog->needBufferOpen() && !documentBufferView())
4661 hideDialog(fromqstr(dialog->name()), 0);
4662 else if (dialog->isVisibleView())
4663 dialog->checkStatus();
4670 Dialog * createDialog(GuiView & lv, string const & name);
4672 // will be replaced by a proper factory...
4673 Dialog * createGuiAbout(GuiView & lv);
4674 Dialog * createGuiBibtex(GuiView & lv);
4675 Dialog * createGuiChanges(GuiView & lv);
4676 Dialog * createGuiCharacter(GuiView & lv);
4677 Dialog * createGuiCitation(GuiView & lv);
4678 Dialog * createGuiCompare(GuiView & lv);
4679 Dialog * createGuiCompareHistory(GuiView & lv);
4680 Dialog * createGuiDelimiter(GuiView & lv);
4681 Dialog * createGuiDocument(GuiView & lv);
4682 Dialog * createGuiErrorList(GuiView & lv);
4683 Dialog * createGuiExternal(GuiView & lv);
4684 Dialog * createGuiGraphics(GuiView & lv);
4685 Dialog * createGuiInclude(GuiView & lv);
4686 Dialog * createGuiIndex(GuiView & lv);
4687 Dialog * createGuiListings(GuiView & lv);
4688 Dialog * createGuiLog(GuiView & lv);
4689 Dialog * createGuiMathMatrix(GuiView & lv);
4690 Dialog * createGuiNote(GuiView & lv);
4691 Dialog * createGuiParagraph(GuiView & lv);
4692 Dialog * createGuiPhantom(GuiView & lv);
4693 Dialog * createGuiPreferences(GuiView & lv);
4694 Dialog * createGuiPrint(GuiView & lv);
4695 Dialog * createGuiPrintindex(GuiView & lv);
4696 Dialog * createGuiRef(GuiView & lv);
4697 Dialog * createGuiSearch(GuiView & lv);
4698 Dialog * createGuiSearchAdv(GuiView & lv);
4699 Dialog * createGuiSendTo(GuiView & lv);
4700 Dialog * createGuiShowFile(GuiView & lv);
4701 Dialog * createGuiSpellchecker(GuiView & lv);
4702 Dialog * createGuiSymbols(GuiView & lv);
4703 Dialog * createGuiTabularCreate(GuiView & lv);
4704 Dialog * createGuiTexInfo(GuiView & lv);
4705 Dialog * createGuiToc(GuiView & lv);
4706 Dialog * createGuiThesaurus(GuiView & lv);
4707 Dialog * createGuiViewSource(GuiView & lv);
4708 Dialog * createGuiWrap(GuiView & lv);
4709 Dialog * createGuiProgressView(GuiView & lv);
4713 Dialog * GuiView::build(string const & name)
4715 LASSERT(isValidName(name), return 0);
4717 Dialog * dialog = createDialog(*this, name);
4721 if (name == "aboutlyx")
4722 return createGuiAbout(*this);
4723 if (name == "bibtex")
4724 return createGuiBibtex(*this);
4725 if (name == "changes")
4726 return createGuiChanges(*this);
4727 if (name == "character")
4728 return createGuiCharacter(*this);
4729 if (name == "citation")
4730 return createGuiCitation(*this);
4731 if (name == "compare")
4732 return createGuiCompare(*this);
4733 if (name == "comparehistory")
4734 return createGuiCompareHistory(*this);
4735 if (name == "document")
4736 return createGuiDocument(*this);
4737 if (name == "errorlist")
4738 return createGuiErrorList(*this);
4739 if (name == "external")
4740 return createGuiExternal(*this);
4742 return createGuiShowFile(*this);
4743 if (name == "findreplace")
4744 return createGuiSearch(*this);
4745 if (name == "findreplaceadv")
4746 return createGuiSearchAdv(*this);
4747 if (name == "graphics")
4748 return createGuiGraphics(*this);
4749 if (name == "include")
4750 return createGuiInclude(*this);
4751 if (name == "index")
4752 return createGuiIndex(*this);
4753 if (name == "index_print")
4754 return createGuiPrintindex(*this);
4755 if (name == "listings")
4756 return createGuiListings(*this);
4758 return createGuiLog(*this);
4759 if (name == "mathdelimiter")
4760 return createGuiDelimiter(*this);
4761 if (name == "mathmatrix")
4762 return createGuiMathMatrix(*this);
4764 return createGuiNote(*this);
4765 if (name == "paragraph")
4766 return createGuiParagraph(*this);
4767 if (name == "phantom")
4768 return createGuiPhantom(*this);
4769 if (name == "prefs")
4770 return createGuiPreferences(*this);
4772 return createGuiRef(*this);
4773 if (name == "sendto")
4774 return createGuiSendTo(*this);
4775 if (name == "spellchecker")
4776 return createGuiSpellchecker(*this);
4777 if (name == "symbols")
4778 return createGuiSymbols(*this);
4779 if (name == "tabularcreate")
4780 return createGuiTabularCreate(*this);
4781 if (name == "texinfo")
4782 return createGuiTexInfo(*this);
4783 if (name == "thesaurus")
4784 return createGuiThesaurus(*this);
4786 return createGuiToc(*this);
4787 if (name == "view-source")
4788 return createGuiViewSource(*this);
4790 return createGuiWrap(*this);
4791 if (name == "progress")
4792 return createGuiProgressView(*this);
4798 SEMenu::SEMenu(QWidget * parent)
4800 QAction * action = addAction(qt_("Disable Shell Escape"));
4801 connect(action, SIGNAL(triggered()),
4802 parent, SLOT(disableShellEscape()));
4805 } // namespace frontend
4808 #include "moc_GuiView.cpp"