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 htext = qt_("The Document\nProcessor[[welcome banner]]");
161 /// The text to be written on top of the pixmap
162 QString const text = lyx_version ?
163 qt_("version ") + lyx_version : qt_("unknown version");
164 #if QT_VERSION >= 0x050000
165 QString imagedir = "images/";
166 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
167 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
168 if (svgRenderer.isValid()) {
169 splash_ = QPixmap(splashSize());
170 QPainter painter(&splash_);
171 svgRenderer.render(&painter);
172 splash_.setDevicePixelRatio(pixelRatio());
174 splash_ = getPixmap("images/", "banner", "png");
177 splash_ = getPixmap("images/", "banner", "svgz,png");
180 QPainter pain(&splash_);
181 pain.setPen(QColor(0, 0, 0));
182 qreal const fsize = fontSize();
183 QPointF const position = textPosition(false);
184 QPointF const hposition = textPosition(true);
185 QRectF const hrect(hposition, splashSize());
187 "widget pixel ratio: " << pixelRatio() <<
188 " splash pixel ratio: " << splashPixelRatio() <<
189 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
191 // The font used to display the version info
192 font.setStyleHint(QFont::SansSerif);
193 font.setWeight(QFont::Bold);
194 font.setPointSizeF(fsize);
196 pain.drawText(position, text);
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Normal);
200 font.setPointSize(20);
202 pain.drawText(hrect, Qt::AlignLeft, htext);
203 setFocusPolicy(Qt::StrongFocus);
206 void paintEvent(QPaintEvent *)
208 int const w = width_;
209 int const h = height_;
210 int const x = (width() - w) / 2;
211 int const y = (height() - h) / 2;
213 "widget pixel ratio: " << pixelRatio() <<
214 " splash pixel ratio: " << splashPixelRatio() <<
215 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
217 pain.drawPixmap(x, y, w, h, splash_);
220 void keyPressEvent(QKeyEvent * ev)
223 setKeySymbol(&sym, ev);
225 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
237 /// Current ratio between physical pixels and device-independent pixels
238 double pixelRatio() const {
239 #if QT_VERSION >= 0x050000
240 return qt_scale_factor * devicePixelRatio();
246 qreal fontSize() const {
247 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
250 QPointF textPosition(bool const heading) const {
251 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
252 : QPointF(width_/2 - 18, height_/2 + 45);
255 QSize splashSize() const {
257 static_cast<unsigned int>(width_ * pixelRatio()),
258 static_cast<unsigned int>(height_ * pixelRatio()));
261 /// Ratio between physical pixels and device-independent pixels of splash image
262 double splashPixelRatio() const {
263 #if QT_VERSION >= 0x050000
264 return splash_.devicePixelRatio();
272 /// Toolbar store providing access to individual toolbars by name.
273 typedef map<string, GuiToolbar *> ToolbarMap;
275 typedef shared_ptr<Dialog> DialogPtr;
280 class GuiView::GuiViewPrivate
283 GuiViewPrivate(GuiViewPrivate const &);
284 void operator=(GuiViewPrivate const &);
286 GuiViewPrivate(GuiView * gv)
287 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
288 layout_(0), autosave_timeout_(5000),
291 // hardcode here the platform specific icon size
292 smallIconSize = 16; // scaling problems
293 normalIconSize = 20; // ok, default if iconsize.png is missing
294 bigIconSize = 26; // better for some math icons
295 hugeIconSize = 32; // better for hires displays
298 // if it exists, use width of iconsize.png as normal size
299 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
300 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
302 QImage image(toqstr(fn.absFileName()));
303 if (image.width() < int(smallIconSize))
304 normalIconSize = smallIconSize;
305 else if (image.width() > int(giantIconSize))
306 normalIconSize = giantIconSize;
308 normalIconSize = image.width();
311 splitter_ = new QSplitter;
312 bg_widget_ = new BackgroundWidget(400, 250);
313 stack_widget_ = new QStackedWidget;
314 stack_widget_->addWidget(bg_widget_);
315 stack_widget_->addWidget(splitter_);
318 // TODO cleanup, remove the singleton, handle multiple Windows?
319 progress_ = ProgressInterface::instance();
320 if (!dynamic_cast<GuiProgress*>(progress_)) {
321 progress_ = new GuiProgress; // TODO who deletes it
322 ProgressInterface::setInstance(progress_);
325 dynamic_cast<GuiProgress*>(progress_),
326 SIGNAL(updateStatusBarMessage(QString const&)),
327 gv, SLOT(updateStatusBarMessage(QString const&)));
329 dynamic_cast<GuiProgress*>(progress_),
330 SIGNAL(clearMessageText()),
331 gv, SLOT(clearMessageText()));
338 delete stack_widget_;
343 stack_widget_->setCurrentWidget(bg_widget_);
344 bg_widget_->setUpdatesEnabled(true);
345 bg_widget_->setFocus();
348 int tabWorkAreaCount()
350 return splitter_->count();
353 TabWorkArea * tabWorkArea(int i)
355 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
358 TabWorkArea * currentTabWorkArea()
360 int areas = tabWorkAreaCount();
362 // The first TabWorkArea is always the first one, if any.
363 return tabWorkArea(0);
365 for (int i = 0; i != areas; ++i) {
366 TabWorkArea * twa = tabWorkArea(i);
367 if (current_main_work_area_ == twa->currentWorkArea())
371 // None has the focus so we just take the first one.
372 return tabWorkArea(0);
375 int countWorkAreasOf(Buffer & buf)
377 int areas = tabWorkAreaCount();
379 for (int i = 0; i != areas; ++i) {
380 TabWorkArea * twa = tabWorkArea(i);
381 if (twa->workArea(buf))
387 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
389 if (processing_thread_watcher_.isRunning()) {
390 // we prefer to cancel this preview in order to keep a snappy
394 processing_thread_watcher_.setFuture(f);
397 QSize iconSize(docstring const & icon_size)
400 if (icon_size == "small")
401 size = smallIconSize;
402 else if (icon_size == "normal")
403 size = normalIconSize;
404 else if (icon_size == "big")
406 else if (icon_size == "huge")
408 else if (icon_size == "giant")
409 size = giantIconSize;
411 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
413 if (size < smallIconSize)
414 size = smallIconSize;
416 return QSize(size, size);
419 QSize iconSize(QString const & icon_size)
421 return iconSize(qstring_to_ucs4(icon_size));
424 string & iconSize(QSize const & qsize)
426 LATTEST(qsize.width() == qsize.height());
428 static string icon_size;
430 unsigned int size = qsize.width();
432 if (size < smallIconSize)
433 size = smallIconSize;
435 if (size == smallIconSize)
437 else if (size == normalIconSize)
438 icon_size = "normal";
439 else if (size == bigIconSize)
441 else if (size == hugeIconSize)
443 else if (size == giantIconSize)
446 icon_size = convert<string>(size);
453 GuiWorkArea * current_work_area_;
454 GuiWorkArea * current_main_work_area_;
455 QSplitter * splitter_;
456 QStackedWidget * stack_widget_;
457 BackgroundWidget * bg_widget_;
459 ToolbarMap toolbars_;
460 ProgressInterface* progress_;
461 /// The main layout box.
463 * \warning Don't Delete! The layout box is actually owned by
464 * whichever toolbar contains it. All the GuiView class needs is a
465 * means of accessing it.
467 * FIXME: replace that with a proper model so that we are not limited
468 * to only one dialog.
473 map<string, DialogPtr> dialogs_;
475 unsigned int smallIconSize;
476 unsigned int normalIconSize;
477 unsigned int bigIconSize;
478 unsigned int hugeIconSize;
479 unsigned int giantIconSize;
481 QTimer statusbar_timer_;
482 /// auto-saving of buffers
483 Timeout autosave_timeout_;
484 /// flag against a race condition due to multiclicks, see bug #1119
488 TocModels toc_models_;
491 QFutureWatcher<docstring> autosave_watcher_;
492 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
494 string last_export_format;
495 string processing_format;
497 static QSet<Buffer const *> busyBuffers;
498 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
499 Buffer * buffer, string const & format);
500 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
501 Buffer * buffer, string const & format);
502 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
503 Buffer * buffer, string const & format);
504 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
507 static Buffer::ExportStatus runAndDestroy(const T& func,
508 Buffer const * orig, Buffer * buffer, string const & format);
510 // TODO syncFunc/previewFunc: use bind
511 bool asyncBufferProcessing(string const & argument,
512 Buffer const * used_buffer,
513 docstring const & msg,
514 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
515 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
516 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
519 QVector<GuiWorkArea*> guiWorkAreas();
522 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
525 GuiView::GuiView(int id)
526 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
527 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
530 connect(this, SIGNAL(bufferViewChanged()),
531 this, SLOT(onBufferViewChanged()));
533 // GuiToolbars *must* be initialised before the menu bar.
534 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
537 // set ourself as the current view. This is needed for the menu bar
538 // filling, at least for the static special menu item on Mac. Otherwise
539 // they are greyed out.
540 guiApp->setCurrentView(this);
542 // Fill up the menu bar.
543 guiApp->menus().fillMenuBar(menuBar(), this, true);
545 setCentralWidget(d.stack_widget_);
547 // Start autosave timer
548 if (lyxrc.autosave) {
549 // The connection is closed when this is destroyed.
550 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
551 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
552 d.autosave_timeout_.start();
554 connect(&d.statusbar_timer_, SIGNAL(timeout()),
555 this, SLOT(clearMessage()));
557 // We don't want to keep the window in memory if it is closed.
558 setAttribute(Qt::WA_DeleteOnClose, true);
560 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
561 // QIcon::fromTheme was introduced in Qt 4.6
562 #if (QT_VERSION >= 0x040600)
563 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
564 // since the icon is provided in the application bundle. We use a themed
565 // version when available and use the bundled one as fallback.
566 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
568 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
574 // use tabbed dock area for multiple docks
575 // (such as "source" and "messages")
576 setDockOptions(QMainWindow::ForceTabbedDocks);
579 setAcceptDrops(true);
581 // add busy indicator to statusbar
582 QLabel * busylabel = new QLabel(statusBar());
583 statusBar()->addPermanentWidget(busylabel);
584 search_mode mode = theGuiApp()->imageSearchMode();
585 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
586 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
587 busylabel->setMovie(busyanim);
591 connect(&d.processing_thread_watcher_, SIGNAL(started()),
592 busylabel, SLOT(show()));
593 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
594 busylabel, SLOT(hide()));
596 QFontMetrics const fm(statusBar()->fontMetrics());
597 int const iconheight = max(int(d.normalIconSize), fm.height());
598 QSize const iconsize(iconheight, iconheight);
600 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
601 shell_escape_ = new QLabel(statusBar());
602 shell_escape_->setPixmap(shellescape);
603 shell_escape_->setScaledContents(true);
604 shell_escape_->setAlignment(Qt::AlignCenter);
605 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
606 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
607 "external commands for this document. "
608 "Right click to change."));
609 SEMenu * menu = new SEMenu(this);
610 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
611 menu, SLOT(showMenu(QPoint)));
612 shell_escape_->hide();
613 statusBar()->addPermanentWidget(shell_escape_);
615 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
616 read_only_ = new QLabel(statusBar());
617 read_only_->setPixmap(readonly);
618 read_only_->setScaledContents(true);
619 read_only_->setAlignment(Qt::AlignCenter);
621 statusBar()->addPermanentWidget(read_only_);
623 version_control_ = new QLabel(statusBar());
624 version_control_->setAlignment(Qt::AlignCenter);
625 version_control_->setFrameStyle(QFrame::StyledPanel);
626 version_control_->hide();
627 statusBar()->addPermanentWidget(version_control_);
629 statusBar()->setSizeGripEnabled(true);
632 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
633 SLOT(autoSaveThreadFinished()));
635 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
636 SLOT(processingThreadStarted()));
637 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
638 SLOT(processingThreadFinished()));
640 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
641 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
643 // set custom application bars context menu, e.g. tool bar and menu bar
644 setContextMenuPolicy(Qt::CustomContextMenu);
645 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
646 SLOT(toolBarPopup(const QPoint &)));
648 // Forbid too small unresizable window because it can happen
649 // with some window manager under X11.
650 setMinimumSize(300, 200);
652 if (lyxrc.allow_geometry_session) {
653 // Now take care of session management.
658 // no session handling, default to a sane size.
659 setGeometry(50, 50, 690, 510);
662 // clear session data if any.
664 settings.remove("views");
674 void GuiView::disableShellEscape()
676 BufferView * bv = documentBufferView();
679 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
680 bv->buffer().params().shell_escape = false;
681 bv->processUpdateFlags(Update::Force);
685 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
687 QVector<GuiWorkArea*> areas;
688 for (int i = 0; i < tabWorkAreaCount(); i++) {
689 TabWorkArea* ta = tabWorkArea(i);
690 for (int u = 0; u < ta->count(); u++) {
691 areas << ta->workArea(u);
697 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
698 string const & format)
700 docstring const fmt = theFormats().prettyName(format);
703 case Buffer::ExportSuccess:
704 msg = bformat(_("Successful export to format: %1$s"), fmt);
706 case Buffer::ExportCancel:
707 msg = _("Document export cancelled.");
709 case Buffer::ExportError:
710 case Buffer::ExportNoPathToFormat:
711 case Buffer::ExportTexPathHasSpaces:
712 case Buffer::ExportConverterError:
713 msg = bformat(_("Error while exporting format: %1$s"), fmt);
715 case Buffer::PreviewSuccess:
716 msg = bformat(_("Successful preview of format: %1$s"), fmt);
718 case Buffer::PreviewError:
719 msg = bformat(_("Error while previewing format: %1$s"), fmt);
721 case Buffer::ExportKilled:
722 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
729 void GuiView::processingThreadStarted()
734 void GuiView::processingThreadFinished()
736 QFutureWatcher<Buffer::ExportStatus> const * watcher =
737 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
739 Buffer::ExportStatus const status = watcher->result();
740 handleExportStatus(this, status, d.processing_format);
743 BufferView const * const bv = currentBufferView();
744 if (bv && !bv->buffer().errorList("Export").empty()) {
749 bool const error = (status != Buffer::ExportSuccess &&
750 status != Buffer::PreviewSuccess &&
751 status != Buffer::ExportCancel);
753 ErrorList & el = bv->buffer().errorList(d.last_export_format);
754 // at this point, we do not know if buffer-view or
755 // master-buffer-view was called. If there was an export error,
756 // and the current buffer's error log is empty, we guess that
757 // it must be master-buffer-view that was called so we set
759 errors(d.last_export_format, el.empty());
764 void GuiView::autoSaveThreadFinished()
766 QFutureWatcher<docstring> const * watcher =
767 static_cast<QFutureWatcher<docstring> const *>(sender());
768 message(watcher->result());
773 void GuiView::saveLayout() const
776 settings.setValue("zoom_ratio", zoom_ratio_);
777 settings.setValue("devel_mode", devel_mode_);
778 settings.beginGroup("views");
779 settings.beginGroup(QString::number(id_));
780 #if defined(Q_WS_X11) || defined(QPA_XCB)
781 settings.setValue("pos", pos());
782 settings.setValue("size", size());
784 settings.setValue("geometry", saveGeometry());
786 settings.setValue("layout", saveState(0));
787 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
791 void GuiView::saveUISettings() const
795 // Save the toolbar private states
796 ToolbarMap::iterator end = d.toolbars_.end();
797 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
798 it->second->saveSession(settings);
799 // Now take care of all other dialogs
800 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
801 for (; it!= d.dialogs_.end(); ++it)
802 it->second->saveSession(settings);
806 bool GuiView::restoreLayout()
809 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
810 // Actual zoom value: default zoom + fractional offset
811 int zoom = lyxrc.defaultZoom * zoom_ratio_;
812 if (zoom < static_cast<int>(zoom_min_))
814 lyxrc.currentZoom = zoom;
815 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
816 settings.beginGroup("views");
817 settings.beginGroup(QString::number(id_));
818 QString const icon_key = "icon_size";
819 if (!settings.contains(icon_key))
822 //code below is skipped when when ~/.config/LyX is (re)created
823 setIconSize(d.iconSize(settings.value(icon_key).toString()));
825 #if defined(Q_WS_X11) || defined(QPA_XCB)
826 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
827 QSize size = settings.value("size", QSize(690, 510)).toSize();
831 // Work-around for bug #6034: the window ends up in an undetermined
832 // state when trying to restore a maximized window when it is
833 // already maximized.
834 if (!(windowState() & Qt::WindowMaximized))
835 if (!restoreGeometry(settings.value("geometry").toByteArray()))
836 setGeometry(50, 50, 690, 510);
838 // Make sure layout is correctly oriented.
839 setLayoutDirection(qApp->layoutDirection());
841 // Allow the toc and view-source dock widget to be restored if needed.
843 if ((dialog = findOrBuild("toc", true)))
844 // see bug 5082. At least setup title and enabled state.
845 // Visibility will be adjusted by restoreState below.
846 dialog->prepareView();
847 if ((dialog = findOrBuild("view-source", true)))
848 dialog->prepareView();
849 if ((dialog = findOrBuild("progress", true)))
850 dialog->prepareView();
852 if (!restoreState(settings.value("layout").toByteArray(), 0))
855 // init the toolbars that have not been restored
856 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
857 Toolbars::Infos::iterator end = guiApp->toolbars().end();
858 for (; cit != end; ++cit) {
859 GuiToolbar * tb = toolbar(cit->name);
860 if (tb && !tb->isRestored())
861 initToolbar(cit->name);
864 // update lock (all) toolbars positions
865 updateLockToolbars();
872 GuiToolbar * GuiView::toolbar(string const & name)
874 ToolbarMap::iterator it = d.toolbars_.find(name);
875 if (it != d.toolbars_.end())
878 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
883 void GuiView::updateLockToolbars()
885 toolbarsMovable_ = false;
886 for (ToolbarInfo const & info : guiApp->toolbars()) {
887 GuiToolbar * tb = toolbar(info.name);
888 if (tb && tb->isMovable())
889 toolbarsMovable_ = true;
894 void GuiView::constructToolbars()
896 ToolbarMap::iterator it = d.toolbars_.begin();
897 for (; it != d.toolbars_.end(); ++it)
901 // I don't like doing this here, but the standard toolbar
902 // destroys this object when it's destroyed itself (vfr)
903 d.layout_ = new LayoutBox(*this);
904 d.stack_widget_->addWidget(d.layout_);
905 d.layout_->move(0,0);
907 // extracts the toolbars from the backend
908 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
909 Toolbars::Infos::iterator end = guiApp->toolbars().end();
910 for (; cit != end; ++cit)
911 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
915 void GuiView::initToolbars()
917 // extracts the toolbars from the backend
918 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
919 Toolbars::Infos::iterator end = guiApp->toolbars().end();
920 for (; cit != end; ++cit)
921 initToolbar(cit->name);
925 void GuiView::initToolbar(string const & name)
927 GuiToolbar * tb = toolbar(name);
930 int const visibility = guiApp->toolbars().defaultVisibility(name);
931 bool newline = !(visibility & Toolbars::SAMEROW);
932 tb->setVisible(false);
933 tb->setVisibility(visibility);
935 if (visibility & Toolbars::TOP) {
937 addToolBarBreak(Qt::TopToolBarArea);
938 addToolBar(Qt::TopToolBarArea, tb);
941 if (visibility & Toolbars::BOTTOM) {
943 addToolBarBreak(Qt::BottomToolBarArea);
944 addToolBar(Qt::BottomToolBarArea, tb);
947 if (visibility & Toolbars::LEFT) {
949 addToolBarBreak(Qt::LeftToolBarArea);
950 addToolBar(Qt::LeftToolBarArea, tb);
953 if (visibility & Toolbars::RIGHT) {
955 addToolBarBreak(Qt::RightToolBarArea);
956 addToolBar(Qt::RightToolBarArea, tb);
959 if (visibility & Toolbars::ON)
960 tb->setVisible(true);
962 tb->setMovable(true);
966 TocModels & GuiView::tocModels()
968 return d.toc_models_;
972 void GuiView::setFocus()
974 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
975 QMainWindow::setFocus();
979 bool GuiView::hasFocus() const
981 if (currentWorkArea())
982 return currentWorkArea()->hasFocus();
983 if (currentMainWorkArea())
984 return currentMainWorkArea()->hasFocus();
985 return d.bg_widget_->hasFocus();
989 void GuiView::focusInEvent(QFocusEvent * e)
991 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
992 QMainWindow::focusInEvent(e);
993 // Make sure guiApp points to the correct view.
994 guiApp->setCurrentView(this);
995 if (currentWorkArea())
996 currentWorkArea()->setFocus();
997 else if (currentMainWorkArea())
998 currentMainWorkArea()->setFocus();
1000 d.bg_widget_->setFocus();
1004 void GuiView::showEvent(QShowEvent * e)
1006 LYXERR(Debug::GUI, "Passed Geometry "
1007 << size().height() << "x" << size().width()
1008 << "+" << pos().x() << "+" << pos().y());
1010 if (d.splitter_->count() == 0)
1011 // No work area, switch to the background widget.
1015 QMainWindow::showEvent(e);
1019 bool GuiView::closeScheduled()
1026 bool GuiView::prepareAllBuffersForLogout()
1028 Buffer * first = theBufferList().first();
1032 // First, iterate over all buffers and ask the users if unsaved
1033 // changes should be saved.
1034 // We cannot use a for loop as the buffer list cycles.
1037 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1039 b = theBufferList().next(b);
1040 } while (b != first);
1042 // Next, save session state
1043 // When a view/window was closed before without quitting LyX, there
1044 // are already entries in the lastOpened list.
1045 theSession().lastOpened().clear();
1052 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1053 ** is responsibility of the container (e.g., dialog)
1055 void GuiView::closeEvent(QCloseEvent * close_event)
1057 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1059 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1060 Alert::warning(_("Exit LyX"),
1061 _("LyX could not be closed because documents are being processed by LyX."));
1062 close_event->setAccepted(false);
1066 // If the user pressed the x (so we didn't call closeView
1067 // programmatically), we want to clear all existing entries.
1069 theSession().lastOpened().clear();
1074 // it can happen that this event arrives without selecting the view,
1075 // e.g. when clicking the close button on a background window.
1077 if (!closeWorkAreaAll()) {
1079 close_event->ignore();
1083 // Make sure that nothing will use this to be closed View.
1084 guiApp->unregisterView(this);
1086 if (isFullScreen()) {
1087 // Switch off fullscreen before closing.
1092 // Make sure the timer time out will not trigger a statusbar update.
1093 d.statusbar_timer_.stop();
1095 // Saving fullscreen requires additional tweaks in the toolbar code.
1096 // It wouldn't also work under linux natively.
1097 if (lyxrc.allow_geometry_session) {
1102 close_event->accept();
1106 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1108 if (event->mimeData()->hasUrls())
1110 /// \todo Ask lyx-devel is this is enough:
1111 /// if (event->mimeData()->hasFormat("text/plain"))
1112 /// event->acceptProposedAction();
1116 void GuiView::dropEvent(QDropEvent * event)
1118 QList<QUrl> files = event->mimeData()->urls();
1119 if (files.isEmpty())
1122 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1123 for (int i = 0; i != files.size(); ++i) {
1124 string const file = os::internal_path(fromqstr(
1125 files.at(i).toLocalFile()));
1129 string const ext = support::getExtension(file);
1130 vector<const Format *> found_formats;
1132 // Find all formats that have the correct extension.
1133 vector<const Format *> const & import_formats
1134 = theConverters().importableFormats();
1135 vector<const Format *>::const_iterator it = import_formats.begin();
1136 for (; it != import_formats.end(); ++it)
1137 if ((*it)->hasExtension(ext))
1138 found_formats.push_back(*it);
1141 if (found_formats.size() >= 1) {
1142 if (found_formats.size() > 1) {
1143 //FIXME: show a dialog to choose the correct importable format
1144 LYXERR(Debug::FILES,
1145 "Multiple importable formats found, selecting first");
1147 string const arg = found_formats[0]->name() + " " + file;
1148 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1151 //FIXME: do we have to explicitly check whether it's a lyx file?
1152 LYXERR(Debug::FILES,
1153 "No formats found, trying to open it as a lyx file");
1154 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1156 // add the functions to the queue
1157 guiApp->addToFuncRequestQueue(cmd);
1160 // now process the collected functions. We perform the events
1161 // asynchronously. This prevents potential problems in case the
1162 // BufferView is closed within an event.
1163 guiApp->processFuncRequestQueueAsync();
1167 void GuiView::message(docstring const & str)
1169 if (ForkedProcess::iAmAChild())
1172 // call is moved to GUI-thread by GuiProgress
1173 d.progress_->appendMessage(toqstr(str));
1177 void GuiView::clearMessageText()
1179 message(docstring());
1183 void GuiView::updateStatusBarMessage(QString const & str)
1185 statusBar()->showMessage(str);
1186 d.statusbar_timer_.stop();
1187 d.statusbar_timer_.start(3000);
1191 void GuiView::clearMessage()
1193 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1194 // the hasFocus function mostly returns false, even if the focus is on
1195 // a workarea in this view.
1199 d.statusbar_timer_.stop();
1203 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1205 if (wa != d.current_work_area_
1206 || wa->bufferView().buffer().isInternal())
1208 Buffer const & buf = wa->bufferView().buffer();
1209 // Set the windows title
1210 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1211 if (buf.notifiesExternalModification()) {
1212 title = bformat(_("%1$s (modified externally)"), title);
1213 // If the external modification status has changed, then maybe the status of
1214 // buffer-save has changed too.
1218 title += from_ascii(" - LyX");
1220 setWindowTitle(toqstr(title));
1221 // Sets the path for the window: this is used by OSX to
1222 // allow a context click on the title bar showing a menu
1223 // with the path up to the file
1224 setWindowFilePath(toqstr(buf.absFileName()));
1225 // Tell Qt whether the current document is changed
1226 setWindowModified(!buf.isClean());
1228 if (buf.params().shell_escape)
1229 shell_escape_->show();
1231 shell_escape_->hide();
1233 if (buf.hasReadonlyFlag())
1238 if (buf.lyxvc().inUse()) {
1239 version_control_->show();
1240 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1242 version_control_->hide();
1246 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1248 if (d.current_work_area_)
1249 // disconnect the current work area from all slots
1250 QObject::disconnect(d.current_work_area_, 0, this, 0);
1252 disconnectBufferView();
1253 connectBufferView(wa->bufferView());
1254 connectBuffer(wa->bufferView().buffer());
1255 d.current_work_area_ = wa;
1256 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1257 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1258 QObject::connect(wa, SIGNAL(busy(bool)),
1259 this, SLOT(setBusy(bool)));
1260 // connection of a signal to a signal
1261 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1262 this, SIGNAL(bufferViewChanged()));
1263 Q_EMIT updateWindowTitle(wa);
1264 Q_EMIT bufferViewChanged();
1268 void GuiView::onBufferViewChanged()
1271 // Buffer-dependent dialogs must be updated. This is done here because
1272 // some dialogs require buffer()->text.
1277 void GuiView::on_lastWorkAreaRemoved()
1280 // We already are in a close event. Nothing more to do.
1283 if (d.splitter_->count() > 1)
1284 // We have a splitter so don't close anything.
1287 // Reset and updates the dialogs.
1288 Q_EMIT bufferViewChanged();
1293 if (lyxrc.open_buffers_in_tabs)
1294 // Nothing more to do, the window should stay open.
1297 if (guiApp->viewIds().size() > 1) {
1303 // On Mac we also close the last window because the application stay
1304 // resident in memory. On other platforms we don't close the last
1305 // window because this would quit the application.
1311 void GuiView::updateStatusBar()
1313 // let the user see the explicit message
1314 if (d.statusbar_timer_.isActive())
1321 void GuiView::showMessage()
1325 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1326 if (msg.isEmpty()) {
1327 BufferView const * bv = currentBufferView();
1329 msg = toqstr(bv->cursor().currentState(devel_mode_));
1331 msg = qt_("Welcome to LyX!");
1333 statusBar()->showMessage(msg);
1337 bool GuiView::event(QEvent * e)
1341 // Useful debug code:
1342 //case QEvent::ActivationChange:
1343 //case QEvent::WindowDeactivate:
1344 //case QEvent::Paint:
1345 //case QEvent::Enter:
1346 //case QEvent::Leave:
1347 //case QEvent::HoverEnter:
1348 //case QEvent::HoverLeave:
1349 //case QEvent::HoverMove:
1350 //case QEvent::StatusTip:
1351 //case QEvent::DragEnter:
1352 //case QEvent::DragLeave:
1353 //case QEvent::Drop:
1356 case QEvent::WindowActivate: {
1357 GuiView * old_view = guiApp->currentView();
1358 if (this == old_view) {
1360 return QMainWindow::event(e);
1362 if (old_view && old_view->currentBufferView()) {
1363 // save current selection to the selection buffer to allow
1364 // middle-button paste in this window.
1365 cap::saveSelection(old_view->currentBufferView()->cursor());
1367 guiApp->setCurrentView(this);
1368 if (d.current_work_area_)
1369 on_currentWorkAreaChanged(d.current_work_area_);
1373 return QMainWindow::event(e);
1376 case QEvent::ShortcutOverride: {
1378 if (isFullScreen() && menuBar()->isHidden()) {
1379 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1380 // FIXME: we should also try to detect special LyX shortcut such as
1381 // Alt-P and Alt-M. Right now there is a hack in
1382 // GuiWorkArea::processKeySym() that hides again the menubar for
1384 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1386 return QMainWindow::event(e);
1389 return QMainWindow::event(e);
1393 return QMainWindow::event(e);
1397 void GuiView::resetWindowTitle()
1399 setWindowTitle(qt_("LyX"));
1402 bool GuiView::focusNextPrevChild(bool /*next*/)
1409 bool GuiView::busy() const
1415 void GuiView::setBusy(bool busy)
1417 bool const busy_before = busy_ > 0;
1418 busy ? ++busy_ : --busy_;
1419 if ((busy_ > 0) == busy_before)
1420 // busy state didn't change
1424 QApplication::setOverrideCursor(Qt::WaitCursor);
1427 QApplication::restoreOverrideCursor();
1432 void GuiView::resetCommandExecute()
1434 command_execute_ = false;
1439 double GuiView::pixelRatio() const
1441 #if QT_VERSION >= 0x050000
1442 return qt_scale_factor * devicePixelRatio();
1449 GuiWorkArea * GuiView::workArea(int index)
1451 if (TabWorkArea * twa = d.currentTabWorkArea())
1452 if (index < twa->count())
1453 return twa->workArea(index);
1458 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1460 if (currentWorkArea()
1461 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1462 return currentWorkArea();
1463 if (TabWorkArea * twa = d.currentTabWorkArea())
1464 return twa->workArea(buffer);
1469 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1471 // Automatically create a TabWorkArea if there are none yet.
1472 TabWorkArea * tab_widget = d.splitter_->count()
1473 ? d.currentTabWorkArea() : addTabWorkArea();
1474 return tab_widget->addWorkArea(buffer, *this);
1478 TabWorkArea * GuiView::addTabWorkArea()
1480 TabWorkArea * twa = new TabWorkArea;
1481 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1482 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1483 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1484 this, SLOT(on_lastWorkAreaRemoved()));
1486 d.splitter_->addWidget(twa);
1487 d.stack_widget_->setCurrentWidget(d.splitter_);
1492 GuiWorkArea const * GuiView::currentWorkArea() const
1494 return d.current_work_area_;
1498 GuiWorkArea * GuiView::currentWorkArea()
1500 return d.current_work_area_;
1504 GuiWorkArea const * GuiView::currentMainWorkArea() const
1506 if (!d.currentTabWorkArea())
1508 return d.currentTabWorkArea()->currentWorkArea();
1512 GuiWorkArea * GuiView::currentMainWorkArea()
1514 if (!d.currentTabWorkArea())
1516 return d.currentTabWorkArea()->currentWorkArea();
1520 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1522 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1524 d.current_work_area_ = 0;
1526 Q_EMIT bufferViewChanged();
1530 // FIXME: I've no clue why this is here and why it accesses
1531 // theGuiApp()->currentView, which might be 0 (bug 6464).
1532 // See also 27525 (vfr).
1533 if (theGuiApp()->currentView() == this
1534 && theGuiApp()->currentView()->currentWorkArea() == wa)
1537 if (currentBufferView())
1538 cap::saveSelection(currentBufferView()->cursor());
1540 theGuiApp()->setCurrentView(this);
1541 d.current_work_area_ = wa;
1543 // We need to reset this now, because it will need to be
1544 // right if the tabWorkArea gets reset in the for loop. We
1545 // will change it back if we aren't in that case.
1546 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1547 d.current_main_work_area_ = wa;
1549 for (int i = 0; i != d.splitter_->count(); ++i) {
1550 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1551 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1552 << ", Current main wa: " << currentMainWorkArea());
1557 d.current_main_work_area_ = old_cmwa;
1559 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1560 on_currentWorkAreaChanged(wa);
1561 BufferView & bv = wa->bufferView();
1562 bv.cursor().fixIfBroken();
1564 wa->setUpdatesEnabled(true);
1565 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1569 void GuiView::removeWorkArea(GuiWorkArea * wa)
1571 LASSERT(wa, return);
1572 if (wa == d.current_work_area_) {
1574 disconnectBufferView();
1575 d.current_work_area_ = 0;
1576 d.current_main_work_area_ = 0;
1579 bool found_twa = false;
1580 for (int i = 0; i != d.splitter_->count(); ++i) {
1581 TabWorkArea * twa = d.tabWorkArea(i);
1582 if (twa->removeWorkArea(wa)) {
1583 // Found in this tab group, and deleted the GuiWorkArea.
1585 if (twa->count() != 0) {
1586 if (d.current_work_area_ == 0)
1587 // This means that we are closing the current GuiWorkArea, so
1588 // switch to the next GuiWorkArea in the found TabWorkArea.
1589 setCurrentWorkArea(twa->currentWorkArea());
1591 // No more WorkAreas in this tab group, so delete it.
1598 // It is not a tabbed work area (i.e., the search work area), so it
1599 // should be deleted by other means.
1600 LASSERT(found_twa, return);
1602 if (d.current_work_area_ == 0) {
1603 if (d.splitter_->count() != 0) {
1604 TabWorkArea * twa = d.currentTabWorkArea();
1605 setCurrentWorkArea(twa->currentWorkArea());
1607 // No more work areas, switch to the background widget.
1608 setCurrentWorkArea(0);
1614 LayoutBox * GuiView::getLayoutDialog() const
1620 void GuiView::updateLayoutList()
1623 d.layout_->updateContents(false);
1627 void GuiView::updateToolbars()
1629 ToolbarMap::iterator end = d.toolbars_.end();
1630 if (d.current_work_area_) {
1632 if (d.current_work_area_->bufferView().cursor().inMathed()
1633 && !d.current_work_area_->bufferView().cursor().inRegexped())
1634 context |= Toolbars::MATH;
1635 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1636 context |= Toolbars::TABLE;
1637 if (currentBufferView()->buffer().areChangesPresent()
1638 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1639 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1640 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1641 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1642 context |= Toolbars::REVIEW;
1643 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1644 context |= Toolbars::MATHMACROTEMPLATE;
1645 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1646 context |= Toolbars::IPA;
1647 if (command_execute_)
1648 context |= Toolbars::MINIBUFFER;
1649 if (minibuffer_focus_) {
1650 context |= Toolbars::MINIBUFFER_FOCUS;
1651 minibuffer_focus_ = false;
1654 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1655 it->second->update(context);
1657 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1658 it->second->update();
1662 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1664 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1665 LASSERT(newBuffer, return);
1667 GuiWorkArea * wa = workArea(*newBuffer);
1670 newBuffer->masterBuffer()->updateBuffer();
1672 wa = addWorkArea(*newBuffer);
1673 // scroll to the position when the BufferView was last closed
1674 if (lyxrc.use_lastfilepos) {
1675 LastFilePosSection::FilePos filepos =
1676 theSession().lastFilePos().load(newBuffer->fileName());
1677 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1680 //Disconnect the old buffer...there's no new one.
1683 connectBuffer(*newBuffer);
1684 connectBufferView(wa->bufferView());
1686 setCurrentWorkArea(wa);
1690 void GuiView::connectBuffer(Buffer & buf)
1692 buf.setGuiDelegate(this);
1696 void GuiView::disconnectBuffer()
1698 if (d.current_work_area_)
1699 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1703 void GuiView::connectBufferView(BufferView & bv)
1705 bv.setGuiDelegate(this);
1709 void GuiView::disconnectBufferView()
1711 if (d.current_work_area_)
1712 d.current_work_area_->bufferView().setGuiDelegate(0);
1716 void GuiView::errors(string const & error_type, bool from_master)
1718 BufferView const * const bv = currentBufferView();
1722 ErrorList const & el = from_master ?
1723 bv->buffer().masterBuffer()->errorList(error_type) :
1724 bv->buffer().errorList(error_type);
1729 string err = error_type;
1731 err = "from_master|" + error_type;
1732 showDialog("errorlist", err);
1736 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1738 d.toc_models_.updateItem(toqstr(type), dit);
1742 void GuiView::structureChanged()
1744 // This is called from the Buffer, which has no way to ensure that cursors
1745 // in BufferView remain valid.
1746 if (documentBufferView())
1747 documentBufferView()->cursor().sanitize();
1748 // FIXME: This is slightly expensive, though less than the tocBackend update
1749 // (#9880). This also resets the view in the Toc Widget (#6675).
1750 d.toc_models_.reset(documentBufferView());
1751 // Navigator needs more than a simple update in this case. It needs to be
1753 updateDialog("toc", "");
1757 void GuiView::updateDialog(string const & name, string const & sdata)
1759 if (!isDialogVisible(name))
1762 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1763 if (it == d.dialogs_.end())
1766 Dialog * const dialog = it->second.get();
1767 if (dialog->isVisibleView())
1768 dialog->initialiseParams(sdata);
1772 BufferView * GuiView::documentBufferView()
1774 return currentMainWorkArea()
1775 ? ¤tMainWorkArea()->bufferView()
1780 BufferView const * GuiView::documentBufferView() const
1782 return currentMainWorkArea()
1783 ? ¤tMainWorkArea()->bufferView()
1788 BufferView * GuiView::currentBufferView()
1790 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1794 BufferView const * GuiView::currentBufferView() const
1796 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1800 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1801 Buffer const * orig, Buffer * clone)
1803 bool const success = clone->autoSave();
1805 busyBuffers.remove(orig);
1807 ? _("Automatic save done.")
1808 : _("Automatic save failed!");
1812 void GuiView::autoSave()
1814 LYXERR(Debug::INFO, "Running autoSave()");
1816 Buffer * buffer = documentBufferView()
1817 ? &documentBufferView()->buffer() : 0;
1819 resetAutosaveTimers();
1823 GuiViewPrivate::busyBuffers.insert(buffer);
1824 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1825 buffer, buffer->cloneBufferOnly());
1826 d.autosave_watcher_.setFuture(f);
1827 resetAutosaveTimers();
1831 void GuiView::resetAutosaveTimers()
1834 d.autosave_timeout_.restart();
1838 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1841 Buffer * buf = currentBufferView()
1842 ? ¤tBufferView()->buffer() : 0;
1843 Buffer * doc_buffer = documentBufferView()
1844 ? &(documentBufferView()->buffer()) : 0;
1847 /* In LyX/Mac, when a dialog is open, the menus of the
1848 application can still be accessed without giving focus to
1849 the main window. In this case, we want to disable the menu
1850 entries that are buffer-related.
1851 This code must not be used on Linux and Windows, since it
1852 would disable buffer-related entries when hovering over the
1853 menu (see bug #9574).
1855 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1861 // Check whether we need a buffer
1862 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1863 // no, exit directly
1864 flag.message(from_utf8(N_("Command not allowed with"
1865 "out any document open")));
1866 flag.setEnabled(false);
1870 if (cmd.origin() == FuncRequest::TOC) {
1871 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1872 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1873 flag.setEnabled(false);
1877 switch(cmd.action()) {
1878 case LFUN_BUFFER_IMPORT:
1881 case LFUN_MASTER_BUFFER_EXPORT:
1883 && (doc_buffer->parent() != 0
1884 || doc_buffer->hasChildren())
1885 && !d.processing_thread_watcher_.isRunning()
1886 // this launches a dialog, which would be in the wrong Buffer
1887 && !(::lyx::operator==(cmd.argument(), "custom"));
1890 case LFUN_MASTER_BUFFER_UPDATE:
1891 case LFUN_MASTER_BUFFER_VIEW:
1893 && (doc_buffer->parent() != 0
1894 || doc_buffer->hasChildren())
1895 && !d.processing_thread_watcher_.isRunning();
1898 case LFUN_BUFFER_UPDATE:
1899 case LFUN_BUFFER_VIEW: {
1900 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1904 string format = to_utf8(cmd.argument());
1905 if (cmd.argument().empty())
1906 format = doc_buffer->params().getDefaultOutputFormat();
1907 enable = doc_buffer->params().isExportable(format, true);
1911 case LFUN_BUFFER_RELOAD:
1912 enable = doc_buffer && !doc_buffer->isUnnamed()
1913 && doc_buffer->fileName().exists()
1914 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1917 case LFUN_BUFFER_CHILD_OPEN:
1918 enable = doc_buffer != 0;
1921 case LFUN_BUFFER_WRITE:
1922 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1925 //FIXME: This LFUN should be moved to GuiApplication.
1926 case LFUN_BUFFER_WRITE_ALL: {
1927 // We enable the command only if there are some modified buffers
1928 Buffer * first = theBufferList().first();
1933 // We cannot use a for loop as the buffer list is a cycle.
1935 if (!b->isClean()) {
1939 b = theBufferList().next(b);
1940 } while (b != first);
1944 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1945 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1948 case LFUN_BUFFER_EXPORT: {
1949 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1953 return doc_buffer->getStatus(cmd, flag);
1957 case LFUN_BUFFER_EXPORT_AS:
1958 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1963 case LFUN_BUFFER_WRITE_AS:
1964 enable = doc_buffer != 0;
1967 case LFUN_EXPORT_CANCEL:
1968 enable = d.processing_thread_watcher_.isRunning();
1971 case LFUN_BUFFER_CLOSE:
1972 case LFUN_VIEW_CLOSE:
1973 enable = doc_buffer != 0;
1976 case LFUN_BUFFER_CLOSE_ALL:
1977 enable = theBufferList().last() != theBufferList().first();
1980 case LFUN_BUFFER_CHKTEX: {
1981 // hide if we have no checktex command
1982 if (lyxrc.chktex_command.empty()) {
1983 flag.setUnknown(true);
1987 if (!doc_buffer || !doc_buffer->params().isLatex()
1988 || d.processing_thread_watcher_.isRunning()) {
1989 // grey out, don't hide
1997 case LFUN_VIEW_SPLIT:
1998 if (cmd.getArg(0) == "vertical")
1999 enable = doc_buffer && (d.splitter_->count() == 1 ||
2000 d.splitter_->orientation() == Qt::Vertical);
2002 enable = doc_buffer && (d.splitter_->count() == 1 ||
2003 d.splitter_->orientation() == Qt::Horizontal);
2006 case LFUN_TAB_GROUP_CLOSE:
2007 enable = d.tabWorkAreaCount() > 1;
2010 case LFUN_DEVEL_MODE_TOGGLE:
2011 flag.setOnOff(devel_mode_);
2014 case LFUN_TOOLBAR_TOGGLE: {
2015 string const name = cmd.getArg(0);
2016 if (GuiToolbar * t = toolbar(name))
2017 flag.setOnOff(t->isVisible());
2020 docstring const msg =
2021 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2027 case LFUN_TOOLBAR_MOVABLE: {
2028 string const name = cmd.getArg(0);
2029 // use negation since locked == !movable
2031 // toolbar name * locks all toolbars
2032 flag.setOnOff(!toolbarsMovable_);
2033 else if (GuiToolbar * t = toolbar(name))
2034 flag.setOnOff(!(t->isMovable()));
2037 docstring const msg =
2038 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2044 case LFUN_ICON_SIZE:
2045 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2048 case LFUN_DROP_LAYOUTS_CHOICE:
2052 case LFUN_UI_TOGGLE:
2053 flag.setOnOff(isFullScreen());
2056 case LFUN_DIALOG_DISCONNECT_INSET:
2059 case LFUN_DIALOG_HIDE:
2060 // FIXME: should we check if the dialog is shown?
2063 case LFUN_DIALOG_TOGGLE:
2064 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2067 case LFUN_DIALOG_SHOW: {
2068 string const name = cmd.getArg(0);
2070 enable = name == "aboutlyx"
2071 || name == "file" //FIXME: should be removed.
2073 || name == "texinfo"
2074 || name == "progress"
2075 || name == "compare";
2076 else if (name == "character" || name == "symbols"
2077 || name == "mathdelimiter" || name == "mathmatrix") {
2078 if (!buf || buf->isReadonly())
2081 Cursor const & cur = currentBufferView()->cursor();
2082 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2085 else if (name == "latexlog")
2086 enable = FileName(doc_buffer->logName()).isReadableFile();
2087 else if (name == "spellchecker")
2088 enable = theSpellChecker()
2089 && !doc_buffer->isReadonly()
2090 && !doc_buffer->text().empty();
2091 else if (name == "vclog")
2092 enable = doc_buffer->lyxvc().inUse();
2096 case LFUN_DIALOG_UPDATE: {
2097 string const name = cmd.getArg(0);
2099 enable = name == "prefs";
2103 case LFUN_COMMAND_EXECUTE:
2105 case LFUN_MENU_OPEN:
2106 // Nothing to check.
2109 case LFUN_COMPLETION_INLINE:
2110 if (!d.current_work_area_
2111 || !d.current_work_area_->completer().inlinePossible(
2112 currentBufferView()->cursor()))
2116 case LFUN_COMPLETION_POPUP:
2117 if (!d.current_work_area_
2118 || !d.current_work_area_->completer().popupPossible(
2119 currentBufferView()->cursor()))
2124 if (!d.current_work_area_
2125 || !d.current_work_area_->completer().inlinePossible(
2126 currentBufferView()->cursor()))
2130 case LFUN_COMPLETION_ACCEPT:
2131 if (!d.current_work_area_
2132 || (!d.current_work_area_->completer().popupVisible()
2133 && !d.current_work_area_->completer().inlineVisible()
2134 && !d.current_work_area_->completer().completionAvailable()))
2138 case LFUN_COMPLETION_CANCEL:
2139 if (!d.current_work_area_
2140 || (!d.current_work_area_->completer().popupVisible()
2141 && !d.current_work_area_->completer().inlineVisible()))
2145 case LFUN_BUFFER_ZOOM_OUT:
2146 case LFUN_BUFFER_ZOOM_IN: {
2147 // only diff between these two is that the default for ZOOM_OUT
2149 bool const neg_zoom =
2150 convert<int>(cmd.argument()) < 0 ||
2151 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2152 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2153 docstring const msg =
2154 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2158 enable = doc_buffer;
2162 case LFUN_BUFFER_ZOOM: {
2163 bool const less_than_min_zoom =
2164 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2165 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2166 docstring const msg =
2167 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2172 enable = doc_buffer;
2176 case LFUN_BUFFER_MOVE_NEXT:
2177 case LFUN_BUFFER_MOVE_PREVIOUS:
2178 // we do not cycle when moving
2179 case LFUN_BUFFER_NEXT:
2180 case LFUN_BUFFER_PREVIOUS:
2181 // because we cycle, it doesn't matter whether on first or last
2182 enable = (d.currentTabWorkArea()->count() > 1);
2184 case LFUN_BUFFER_SWITCH:
2185 // toggle on the current buffer, but do not toggle off
2186 // the other ones (is that a good idea?)
2188 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2189 flag.setOnOff(true);
2192 case LFUN_VC_REGISTER:
2193 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2195 case LFUN_VC_RENAME:
2196 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2199 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2201 case LFUN_VC_CHECK_IN:
2202 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2204 case LFUN_VC_CHECK_OUT:
2205 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2207 case LFUN_VC_LOCKING_TOGGLE:
2208 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2209 && doc_buffer->lyxvc().lockingToggleEnabled();
2210 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2212 case LFUN_VC_REVERT:
2213 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2214 && !doc_buffer->hasReadonlyFlag();
2216 case LFUN_VC_UNDO_LAST:
2217 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2219 case LFUN_VC_REPO_UPDATE:
2220 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2222 case LFUN_VC_COMMAND: {
2223 if (cmd.argument().empty())
2225 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2229 case LFUN_VC_COMPARE:
2230 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2233 case LFUN_SERVER_GOTO_FILE_ROW:
2234 case LFUN_LYX_ACTIVATE:
2236 case LFUN_FORWARD_SEARCH:
2237 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2240 case LFUN_FILE_INSERT_PLAINTEXT:
2241 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2242 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2245 case LFUN_SPELLING_CONTINUOUSLY:
2246 flag.setOnOff(lyxrc.spellcheck_continuously);
2254 flag.setEnabled(false);
2260 static FileName selectTemplateFile()
2262 FileDialog dlg(qt_("Select template file"));
2263 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2264 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2266 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2267 QStringList(qt_("LyX Documents (*.lyx)")));
2269 if (result.first == FileDialog::Later)
2271 if (result.second.isEmpty())
2273 return FileName(fromqstr(result.second));
2277 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2281 Buffer * newBuffer = 0;
2283 newBuffer = checkAndLoadLyXFile(filename);
2284 } catch (ExceptionMessage const & e) {
2291 message(_("Document not loaded."));
2295 setBuffer(newBuffer);
2296 newBuffer->errors("Parse");
2299 theSession().lastFiles().add(filename);
2300 theSession().writeFile();
2307 void GuiView::openDocument(string const & fname)
2309 string initpath = lyxrc.document_path;
2311 if (documentBufferView()) {
2312 string const trypath = documentBufferView()->buffer().filePath();
2313 // If directory is writeable, use this as default.
2314 if (FileName(trypath).isDirWritable())
2320 if (fname.empty()) {
2321 FileDialog dlg(qt_("Select document to open"));
2322 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2323 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2325 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2326 FileDialog::Result result =
2327 dlg.open(toqstr(initpath), filter);
2329 if (result.first == FileDialog::Later)
2332 filename = fromqstr(result.second);
2334 // check selected filename
2335 if (filename.empty()) {
2336 message(_("Canceled."));
2342 // get absolute path of file and add ".lyx" to the filename if
2344 FileName const fullname =
2345 fileSearch(string(), filename, "lyx", support::may_not_exist);
2346 if (!fullname.empty())
2347 filename = fullname.absFileName();
2349 if (!fullname.onlyPath().isDirectory()) {
2350 Alert::warning(_("Invalid filename"),
2351 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2352 from_utf8(fullname.absFileName())));
2356 // if the file doesn't exist and isn't already open (bug 6645),
2357 // let the user create one
2358 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2359 !LyXVC::file_not_found_hook(fullname)) {
2360 // the user specifically chose this name. Believe him.
2361 Buffer * const b = newFile(filename, string(), true);
2367 docstring const disp_fn = makeDisplayPath(filename);
2368 message(bformat(_("Opening document %1$s..."), disp_fn));
2371 Buffer * buf = loadDocument(fullname);
2373 str2 = bformat(_("Document %1$s opened."), disp_fn);
2374 if (buf->lyxvc().inUse())
2375 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2376 " " + _("Version control detected.");
2378 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2383 // FIXME: clean that
2384 static bool import(GuiView * lv, FileName const & filename,
2385 string const & format, ErrorList & errorList)
2387 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2389 string loader_format;
2390 vector<string> loaders = theConverters().loaders();
2391 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2392 vector<string>::const_iterator it = loaders.begin();
2393 vector<string>::const_iterator en = loaders.end();
2394 for (; it != en; ++it) {
2395 if (!theConverters().isReachable(format, *it))
2398 string const tofile =
2399 support::changeExtension(filename.absFileName(),
2400 theFormats().extension(*it));
2401 if (theConverters().convert(0, filename, FileName(tofile),
2402 filename, format, *it, errorList) != Converters::SUCCESS)
2404 loader_format = *it;
2407 if (loader_format.empty()) {
2408 frontend::Alert::error(_("Couldn't import file"),
2409 bformat(_("No information for importing the format %1$s."),
2410 theFormats().prettyName(format)));
2414 loader_format = format;
2416 if (loader_format == "lyx") {
2417 Buffer * buf = lv->loadDocument(lyxfile);
2421 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2425 bool as_paragraphs = loader_format == "textparagraph";
2426 string filename2 = (loader_format == format) ? filename.absFileName()
2427 : support::changeExtension(filename.absFileName(),
2428 theFormats().extension(loader_format));
2429 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2431 guiApp->setCurrentView(lv);
2432 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2439 void GuiView::importDocument(string const & argument)
2442 string filename = split(argument, format, ' ');
2444 LYXERR(Debug::INFO, format << " file: " << filename);
2446 // need user interaction
2447 if (filename.empty()) {
2448 string initpath = lyxrc.document_path;
2449 if (documentBufferView()) {
2450 string const trypath = documentBufferView()->buffer().filePath();
2451 // If directory is writeable, use this as default.
2452 if (FileName(trypath).isDirWritable())
2456 docstring const text = bformat(_("Select %1$s file to import"),
2457 theFormats().prettyName(format));
2459 FileDialog dlg(toqstr(text));
2460 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2461 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2463 docstring filter = theFormats().prettyName(format);
2466 filter += from_utf8(theFormats().extensions(format));
2469 FileDialog::Result result =
2470 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2472 if (result.first == FileDialog::Later)
2475 filename = fromqstr(result.second);
2477 // check selected filename
2478 if (filename.empty())
2479 message(_("Canceled."));
2482 if (filename.empty())
2485 // get absolute path of file
2486 FileName const fullname(support::makeAbsPath(filename));
2488 // Can happen if the user entered a path into the dialog
2490 if (fullname.onlyFileName().empty()) {
2491 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2492 "Aborting import."),
2493 from_utf8(fullname.absFileName()));
2494 frontend::Alert::error(_("File name error"), msg);
2495 message(_("Canceled."));
2500 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2502 // Check if the document already is open
2503 Buffer * buf = theBufferList().getBuffer(lyxfile);
2506 if (!closeBuffer()) {
2507 message(_("Canceled."));
2512 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2514 // if the file exists already, and we didn't do
2515 // -i lyx thefile.lyx, warn
2516 if (lyxfile.exists() && fullname != lyxfile) {
2518 docstring text = bformat(_("The document %1$s already exists.\n\n"
2519 "Do you want to overwrite that document?"), displaypath);
2520 int const ret = Alert::prompt(_("Overwrite document?"),
2521 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2524 message(_("Canceled."));
2529 message(bformat(_("Importing %1$s..."), displaypath));
2530 ErrorList errorList;
2531 if (import(this, fullname, format, errorList))
2532 message(_("imported."));
2534 message(_("file not imported!"));
2536 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2540 void GuiView::newDocument(string const & filename, bool from_template)
2542 FileName initpath(lyxrc.document_path);
2543 if (documentBufferView()) {
2544 FileName const trypath(documentBufferView()->buffer().filePath());
2545 // If directory is writeable, use this as default.
2546 if (trypath.isDirWritable())
2550 string templatefile;
2551 if (from_template) {
2552 templatefile = selectTemplateFile().absFileName();
2553 if (templatefile.empty())
2558 if (filename.empty())
2559 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2561 b = newFile(filename, templatefile, true);
2566 // If no new document could be created, it is unsure
2567 // whether there is a valid BufferView.
2568 if (currentBufferView())
2569 // Ensure the cursor is correctly positioned on screen.
2570 currentBufferView()->showCursor();
2574 void GuiView::insertLyXFile(docstring const & fname)
2576 BufferView * bv = documentBufferView();
2581 FileName filename(to_utf8(fname));
2582 if (filename.empty()) {
2583 // Launch a file browser
2585 string initpath = lyxrc.document_path;
2586 string const trypath = bv->buffer().filePath();
2587 // If directory is writeable, use this as default.
2588 if (FileName(trypath).isDirWritable())
2592 FileDialog dlg(qt_("Select LyX document to insert"));
2593 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2594 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2596 FileDialog::Result result = dlg.open(toqstr(initpath),
2597 QStringList(qt_("LyX Documents (*.lyx)")));
2599 if (result.first == FileDialog::Later)
2603 filename.set(fromqstr(result.second));
2605 // check selected filename
2606 if (filename.empty()) {
2607 // emit message signal.
2608 message(_("Canceled."));
2613 bv->insertLyXFile(filename);
2614 bv->buffer().errors("Parse");
2618 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2620 FileName fname = b.fileName();
2621 FileName const oldname = fname;
2623 if (!newname.empty()) {
2625 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2627 // Switch to this Buffer.
2630 // No argument? Ask user through dialog.
2632 FileDialog dlg(qt_("Choose a filename to save document as"));
2633 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2634 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2636 if (!isLyXFileName(fname.absFileName()))
2637 fname.changeExtension(".lyx");
2639 FileDialog::Result result =
2640 dlg.save(toqstr(fname.onlyPath().absFileName()),
2641 QStringList(qt_("LyX Documents (*.lyx)")),
2642 toqstr(fname.onlyFileName()));
2644 if (result.first == FileDialog::Later)
2647 fname.set(fromqstr(result.second));
2652 if (!isLyXFileName(fname.absFileName()))
2653 fname.changeExtension(".lyx");
2656 // fname is now the new Buffer location.
2658 // if there is already a Buffer open with this name, we do not want
2659 // to have another one. (the second test makes sure we're not just
2660 // trying to overwrite ourselves, which is fine.)
2661 if (theBufferList().exists(fname) && fname != oldname
2662 && theBufferList().getBuffer(fname) != &b) {
2663 docstring const text =
2664 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2665 "Please close it before attempting to overwrite it.\n"
2666 "Do you want to choose a new filename?"),
2667 from_utf8(fname.absFileName()));
2668 int const ret = Alert::prompt(_("Chosen File Already Open"),
2669 text, 0, 1, _("&Rename"), _("&Cancel"));
2671 case 0: return renameBuffer(b, docstring(), kind);
2672 case 1: return false;
2677 bool const existsLocal = fname.exists();
2678 bool const existsInVC = LyXVC::fileInVC(fname);
2679 if (existsLocal || existsInVC) {
2680 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2681 if (kind != LV_WRITE_AS && existsInVC) {
2682 // renaming to a name that is already in VC
2684 docstring text = bformat(_("The document %1$s "
2685 "is already registered.\n\n"
2686 "Do you want to choose a new name?"),
2688 docstring const title = (kind == LV_VC_RENAME) ?
2689 _("Rename document?") : _("Copy document?");
2690 docstring const button = (kind == LV_VC_RENAME) ?
2691 _("&Rename") : _("&Copy");
2692 int const ret = Alert::prompt(title, text, 0, 1,
2693 button, _("&Cancel"));
2695 case 0: return renameBuffer(b, docstring(), kind);
2696 case 1: return false;
2701 docstring text = bformat(_("The document %1$s "
2702 "already exists.\n\n"
2703 "Do you want to overwrite that document?"),
2705 int const ret = Alert::prompt(_("Overwrite document?"),
2706 text, 0, 2, _("&Overwrite"),
2707 _("&Rename"), _("&Cancel"));
2710 case 1: return renameBuffer(b, docstring(), kind);
2711 case 2: return false;
2717 case LV_VC_RENAME: {
2718 string msg = b.lyxvc().rename(fname);
2721 message(from_utf8(msg));
2725 string msg = b.lyxvc().copy(fname);
2728 message(from_utf8(msg));
2734 // LyXVC created the file already in case of LV_VC_RENAME or
2735 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2736 // relative paths of included stuff right if we moved e.g. from
2737 // /a/b.lyx to /a/c/b.lyx.
2739 bool const saved = saveBuffer(b, fname);
2746 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2748 FileName fname = b.fileName();
2750 FileDialog dlg(qt_("Choose a filename to export the document as"));
2751 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2754 QString const anyformat = qt_("Guess from extension (*.*)");
2757 vector<Format const *> export_formats;
2758 for (Format const & f : theFormats())
2759 if (f.documentFormat())
2760 export_formats.push_back(&f);
2761 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2762 map<QString, string> fmap;
2765 for (Format const * f : export_formats) {
2766 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2767 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2769 from_ascii(f->extension())));
2770 types << loc_filter;
2771 fmap[loc_filter] = f->name();
2772 if (from_ascii(f->name()) == iformat) {
2773 filter = loc_filter;
2774 ext = f->extension();
2777 string ofname = fname.onlyFileName();
2779 ofname = support::changeExtension(ofname, ext);
2780 FileDialog::Result result =
2781 dlg.save(toqstr(fname.onlyPath().absFileName()),
2785 if (result.first != FileDialog::Chosen)
2789 fname.set(fromqstr(result.second));
2790 if (filter == anyformat)
2791 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2793 fmt_name = fmap[filter];
2794 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2795 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2797 if (fmt_name.empty() || fname.empty())
2800 // fname is now the new Buffer location.
2801 if (FileName(fname).exists()) {
2802 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2803 docstring text = bformat(_("The document %1$s already "
2804 "exists.\n\nDo you want to "
2805 "overwrite that document?"),
2807 int const ret = Alert::prompt(_("Overwrite document?"),
2808 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2811 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2812 case 2: return false;
2816 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2819 return dr.dispatched();
2823 bool GuiView::saveBuffer(Buffer & b)
2825 return saveBuffer(b, FileName());
2829 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2831 if (workArea(b) && workArea(b)->inDialogMode())
2834 if (fn.empty() && b.isUnnamed())
2835 return renameBuffer(b, docstring());
2837 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2839 theSession().lastFiles().add(b.fileName());
2840 theSession().writeFile();
2844 // Switch to this Buffer.
2847 // FIXME: we don't tell the user *WHY* the save failed !!
2848 docstring const file = makeDisplayPath(b.absFileName(), 30);
2849 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2850 "Do you want to rename the document and "
2851 "try again?"), file);
2852 int const ret = Alert::prompt(_("Rename and save?"),
2853 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2856 if (!renameBuffer(b, docstring()))
2865 return saveBuffer(b, fn);
2869 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2871 return closeWorkArea(wa, false);
2875 // We only want to close the buffer if it is not visible in other workareas
2876 // of the same view, nor in other views, and if this is not a child
2877 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2879 Buffer & buf = wa->bufferView().buffer();
2881 bool last_wa = d.countWorkAreasOf(buf) == 1
2882 && !inOtherView(buf) && !buf.parent();
2884 bool close_buffer = last_wa;
2887 if (lyxrc.close_buffer_with_last_view == "yes")
2889 else if (lyxrc.close_buffer_with_last_view == "no")
2890 close_buffer = false;
2893 if (buf.isUnnamed())
2894 file = from_utf8(buf.fileName().onlyFileName());
2896 file = buf.fileName().displayName(30);
2897 docstring const text = bformat(
2898 _("Last view on document %1$s is being closed.\n"
2899 "Would you like to close or hide the document?\n"
2901 "Hidden documents can be displayed back through\n"
2902 "the menu: View->Hidden->...\n"
2904 "To remove this question, set your preference in:\n"
2905 " Tools->Preferences->Look&Feel->UserInterface\n"
2907 int ret = Alert::prompt(_("Close or hide document?"),
2908 text, 0, 1, _("&Close"), _("&Hide"));
2909 close_buffer = (ret == 0);
2913 return closeWorkArea(wa, close_buffer);
2917 bool GuiView::closeBuffer()
2919 GuiWorkArea * wa = currentMainWorkArea();
2920 // coverity complained about this
2921 // it seems unnecessary, but perhaps is worth the check
2922 LASSERT(wa, return false);
2924 setCurrentWorkArea(wa);
2925 Buffer & buf = wa->bufferView().buffer();
2926 return closeWorkArea(wa, !buf.parent());
2930 void GuiView::writeSession() const {
2931 GuiWorkArea const * active_wa = currentMainWorkArea();
2932 for (int i = 0; i < d.splitter_->count(); ++i) {
2933 TabWorkArea * twa = d.tabWorkArea(i);
2934 for (int j = 0; j < twa->count(); ++j) {
2935 GuiWorkArea * wa = twa->workArea(j);
2936 Buffer & buf = wa->bufferView().buffer();
2937 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2943 bool GuiView::closeBufferAll()
2945 // Close the workareas in all other views
2946 QList<int> const ids = guiApp->viewIds();
2947 for (int i = 0; i != ids.size(); ++i) {
2948 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2952 // Close our own workareas
2953 if (!closeWorkAreaAll())
2956 // Now close the hidden buffers. We prevent hidden buffers from being
2957 // dirty, so we can just close them.
2958 theBufferList().closeAll();
2963 bool GuiView::closeWorkAreaAll()
2965 setCurrentWorkArea(currentMainWorkArea());
2967 // We might be in a situation that there is still a tabWorkArea, but
2968 // there are no tabs anymore. This can happen when we get here after a
2969 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2970 // many TabWorkArea's have no documents anymore.
2973 // We have to call count() each time, because it can happen that
2974 // more than one splitter will disappear in one iteration (bug 5998).
2975 while (d.splitter_->count() > empty_twa) {
2976 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2978 if (twa->count() == 0)
2981 setCurrentWorkArea(twa->currentWorkArea());
2982 if (!closeTabWorkArea(twa))
2990 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2995 Buffer & buf = wa->bufferView().buffer();
2997 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2998 Alert::warning(_("Close document"),
2999 _("Document could not be closed because it is being processed by LyX."));
3004 return closeBuffer(buf);
3006 if (!inMultiTabs(wa))
3007 if (!saveBufferIfNeeded(buf, true))
3015 bool GuiView::closeBuffer(Buffer & buf)
3017 // If we are in a close_event all children will be closed in some time,
3018 // so no need to do it here. This will ensure that the children end up
3019 // in the session file in the correct order. If we close the master
3020 // buffer, we can close or release the child buffers here too.
3021 bool success = true;
3023 ListOfBuffers clist = buf.getChildren();
3024 ListOfBuffers::const_iterator it = clist.begin();
3025 ListOfBuffers::const_iterator const bend = clist.end();
3026 for (; it != bend; ++it) {
3027 Buffer * child_buf = *it;
3028 if (theBufferList().isOthersChild(&buf, child_buf)) {
3029 child_buf->setParent(0);
3033 // FIXME: should we look in other tabworkareas?
3034 // ANSWER: I don't think so. I've tested, and if the child is
3035 // open in some other window, it closes without a problem.
3036 GuiWorkArea * child_wa = workArea(*child_buf);
3038 success = closeWorkArea(child_wa, true);
3042 // In this case the child buffer is open but hidden.
3043 // It therefore should not (MUST NOT) be dirty!
3044 LATTEST(child_buf->isClean());
3045 theBufferList().release(child_buf);
3050 // goto bookmark to update bookmark pit.
3051 // FIXME: we should update only the bookmarks related to this buffer!
3052 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3053 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3054 guiApp->gotoBookmark(i+1, false, false);
3056 if (saveBufferIfNeeded(buf, false)) {
3057 buf.removeAutosaveFile();
3058 theBufferList().release(&buf);
3062 // open all children again to avoid a crash because of dangling
3063 // pointers (bug 6603)
3069 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3071 while (twa == d.currentTabWorkArea()) {
3072 twa->setCurrentIndex(twa->count() - 1);
3074 GuiWorkArea * wa = twa->currentWorkArea();
3075 Buffer & b = wa->bufferView().buffer();
3077 // We only want to close the buffer if the same buffer is not visible
3078 // in another view, and if this is not a child and if we are closing
3079 // a view (not a tabgroup).
3080 bool const close_buffer =
3081 !inOtherView(b) && !b.parent() && closing_;
3083 if (!closeWorkArea(wa, close_buffer))
3090 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3092 if (buf.isClean() || buf.paragraphs().empty())
3095 // Switch to this Buffer.
3101 if (buf.isUnnamed()) {
3102 file = from_utf8(buf.fileName().onlyFileName());
3105 FileName filename = buf.fileName();
3107 file = filename.displayName(30);
3108 exists = filename.exists();
3111 // Bring this window to top before asking questions.
3116 if (hiding && buf.isUnnamed()) {
3117 docstring const text = bformat(_("The document %1$s has not been "
3118 "saved yet.\n\nDo you want to save "
3119 "the document?"), file);
3120 ret = Alert::prompt(_("Save new document?"),
3121 text, 0, 1, _("&Save"), _("&Cancel"));
3125 docstring const text = exists ?
3126 bformat(_("The document %1$s has unsaved changes."
3127 "\n\nDo you want to save the document or "
3128 "discard the changes?"), file) :
3129 bformat(_("The document %1$s has not been saved yet."
3130 "\n\nDo you want to save the document or "
3131 "discard it entirely?"), file);
3132 docstring const title = exists ?
3133 _("Save changed document?") : _("Save document?");
3134 ret = Alert::prompt(title, text, 0, 2,
3135 _("&Save"), _("&Discard"), _("&Cancel"));
3140 if (!saveBuffer(buf))
3144 // If we crash after this we could have no autosave file
3145 // but I guess this is really improbable (Jug).
3146 // Sometimes improbable things happen:
3147 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3148 // buf.removeAutosaveFile();
3150 // revert all changes
3161 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3163 Buffer & buf = wa->bufferView().buffer();
3165 for (int i = 0; i != d.splitter_->count(); ++i) {
3166 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3167 if (wa_ && wa_ != wa)
3170 return inOtherView(buf);
3174 bool GuiView::inOtherView(Buffer & buf)
3176 QList<int> const ids = guiApp->viewIds();
3178 for (int i = 0; i != ids.size(); ++i) {
3182 if (guiApp->view(ids[i]).workArea(buf))
3189 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3191 if (!documentBufferView())
3194 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3195 Buffer * const curbuf = &documentBufferView()->buffer();
3196 int nwa = twa->count();
3197 for (int i = 0; i < nwa; ++i) {
3198 if (&workArea(i)->bufferView().buffer() == curbuf) {
3200 if (np == NEXTBUFFER)
3201 next_index = (i == nwa - 1 ? 0 : i + 1);
3203 next_index = (i == 0 ? nwa - 1 : i - 1);
3205 twa->moveTab(i, next_index);
3207 setBuffer(&workArea(next_index)->bufferView().buffer());
3215 /// make sure the document is saved
3216 static bool ensureBufferClean(Buffer * buffer)
3218 LASSERT(buffer, return false);
3219 if (buffer->isClean() && !buffer->isUnnamed())
3222 docstring const file = buffer->fileName().displayName(30);
3225 if (!buffer->isUnnamed()) {
3226 text = bformat(_("The document %1$s has unsaved "
3227 "changes.\n\nDo you want to save "
3228 "the document?"), file);
3229 title = _("Save changed document?");
3232 text = bformat(_("The document %1$s has not been "
3233 "saved yet.\n\nDo you want to save "
3234 "the document?"), file);
3235 title = _("Save new document?");
3237 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3240 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3242 return buffer->isClean() && !buffer->isUnnamed();
3246 bool GuiView::reloadBuffer(Buffer & buf)
3248 currentBufferView()->cursor().reset();
3249 Buffer::ReadStatus status = buf.reload();
3250 return status == Buffer::ReadSuccess;
3254 void GuiView::checkExternallyModifiedBuffers()
3256 BufferList::iterator bit = theBufferList().begin();
3257 BufferList::iterator const bend = theBufferList().end();
3258 for (; bit != bend; ++bit) {
3259 Buffer * buf = *bit;
3260 if (buf->fileName().exists() && buf->isChecksumModified()) {
3261 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3262 " Reload now? Any local changes will be lost."),
3263 from_utf8(buf->absFileName()));
3264 int const ret = Alert::prompt(_("Reload externally changed document?"),
3265 text, 0, 1, _("&Reload"), _("&Cancel"));
3273 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3275 Buffer * buffer = documentBufferView()
3276 ? &(documentBufferView()->buffer()) : 0;
3278 switch (cmd.action()) {
3279 case LFUN_VC_REGISTER:
3280 if (!buffer || !ensureBufferClean(buffer))
3282 if (!buffer->lyxvc().inUse()) {
3283 if (buffer->lyxvc().registrer()) {
3284 reloadBuffer(*buffer);
3285 dr.clearMessageUpdate();
3290 case LFUN_VC_RENAME:
3291 case LFUN_VC_COPY: {
3292 if (!buffer || !ensureBufferClean(buffer))
3294 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3295 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3296 // Some changes are not yet committed.
3297 // We test here and not in getStatus(), since
3298 // this test is expensive.
3300 LyXVC::CommandResult ret =
3301 buffer->lyxvc().checkIn(log);
3303 if (ret == LyXVC::ErrorCommand ||
3304 ret == LyXVC::VCSuccess)
3305 reloadBuffer(*buffer);
3306 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3307 frontend::Alert::error(
3308 _("Revision control error."),
3309 _("Document could not be checked in."));
3313 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3314 LV_VC_RENAME : LV_VC_COPY;
3315 renameBuffer(*buffer, cmd.argument(), kind);
3320 case LFUN_VC_CHECK_IN:
3321 if (!buffer || !ensureBufferClean(buffer))
3323 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3325 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3327 // Only skip reloading if the checkin was cancelled or
3328 // an error occurred before the real checkin VCS command
3329 // was executed, since the VCS might have changed the
3330 // file even if it could not checkin successfully.
3331 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3332 reloadBuffer(*buffer);
3336 case LFUN_VC_CHECK_OUT:
3337 if (!buffer || !ensureBufferClean(buffer))
3339 if (buffer->lyxvc().inUse()) {
3340 dr.setMessage(buffer->lyxvc().checkOut());
3341 reloadBuffer(*buffer);
3345 case LFUN_VC_LOCKING_TOGGLE:
3346 LASSERT(buffer, return);
3347 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3349 if (buffer->lyxvc().inUse()) {
3350 string res = buffer->lyxvc().lockingToggle();
3352 frontend::Alert::error(_("Revision control error."),
3353 _("Error when setting the locking property."));
3356 reloadBuffer(*buffer);
3361 case LFUN_VC_REVERT:
3362 LASSERT(buffer, return);
3363 if (buffer->lyxvc().revert()) {
3364 reloadBuffer(*buffer);
3365 dr.clearMessageUpdate();
3369 case LFUN_VC_UNDO_LAST:
3370 LASSERT(buffer, return);
3371 buffer->lyxvc().undoLast();
3372 reloadBuffer(*buffer);
3373 dr.clearMessageUpdate();
3376 case LFUN_VC_REPO_UPDATE:
3377 LASSERT(buffer, return);
3378 if (ensureBufferClean(buffer)) {
3379 dr.setMessage(buffer->lyxvc().repoUpdate());
3380 checkExternallyModifiedBuffers();
3384 case LFUN_VC_COMMAND: {
3385 string flag = cmd.getArg(0);
3386 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3389 if (contains(flag, 'M')) {
3390 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3393 string path = cmd.getArg(1);
3394 if (contains(path, "$$p") && buffer)
3395 path = subst(path, "$$p", buffer->filePath());
3396 LYXERR(Debug::LYXVC, "Directory: " << path);
3398 if (!pp.isReadableDirectory()) {
3399 lyxerr << _("Directory is not accessible.") << endl;
3402 support::PathChanger p(pp);
3404 string command = cmd.getArg(2);
3405 if (command.empty())
3408 command = subst(command, "$$i", buffer->absFileName());
3409 command = subst(command, "$$p", buffer->filePath());
3411 command = subst(command, "$$m", to_utf8(message));
3412 LYXERR(Debug::LYXVC, "Command: " << command);
3414 one.startscript(Systemcall::Wait, command);
3418 if (contains(flag, 'I'))
3419 buffer->markDirty();
3420 if (contains(flag, 'R'))
3421 reloadBuffer(*buffer);
3426 case LFUN_VC_COMPARE: {
3427 if (cmd.argument().empty()) {
3428 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3432 string rev1 = cmd.getArg(0);
3437 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3440 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3441 f2 = buffer->absFileName();
3443 string rev2 = cmd.getArg(1);
3447 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3451 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3452 f1 << "\n" << f2 << "\n" );
3453 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3454 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3464 void GuiView::openChildDocument(string const & fname)
3466 LASSERT(documentBufferView(), return);
3467 Buffer & buffer = documentBufferView()->buffer();
3468 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3469 documentBufferView()->saveBookmark(false);
3471 if (theBufferList().exists(filename)) {
3472 child = theBufferList().getBuffer(filename);
3475 message(bformat(_("Opening child document %1$s..."),
3476 makeDisplayPath(filename.absFileName())));
3477 child = loadDocument(filename, false);
3479 // Set the parent name of the child document.
3480 // This makes insertion of citations and references in the child work,
3481 // when the target is in the parent or another child document.
3483 child->setParent(&buffer);
3487 bool GuiView::goToFileRow(string const & argument)
3491 size_t i = argument.find_last_of(' ');
3492 if (i != string::npos) {
3493 file_name = os::internal_path(trim(argument.substr(0, i)));
3494 istringstream is(argument.substr(i + 1));
3499 if (i == string::npos) {
3500 LYXERR0("Wrong argument: " << argument);
3504 string const abstmp = package().temp_dir().absFileName();
3505 string const realtmp = package().temp_dir().realPath();
3506 // We have to use os::path_prefix_is() here, instead of
3507 // simply prefixIs(), because the file name comes from
3508 // an external application and may need case adjustment.
3509 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3510 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3511 // Needed by inverse dvi search. If it is a file
3512 // in tmpdir, call the apropriated function.
3513 // If tmpdir is a symlink, we may have the real
3514 // path passed back, so we correct for that.
3515 if (!prefixIs(file_name, abstmp))
3516 file_name = subst(file_name, realtmp, abstmp);
3517 buf = theBufferList().getBufferFromTmp(file_name);
3519 // Must replace extension of the file to be .lyx
3520 // and get full path
3521 FileName const s = fileSearch(string(),
3522 support::changeExtension(file_name, ".lyx"), "lyx");
3523 // Either change buffer or load the file
3524 if (theBufferList().exists(s))
3525 buf = theBufferList().getBuffer(s);
3526 else if (s.exists()) {
3527 buf = loadDocument(s);
3532 _("File does not exist: %1$s"),
3533 makeDisplayPath(file_name)));
3539 _("No buffer for file: %1$s."),
3540 makeDisplayPath(file_name))
3545 bool success = documentBufferView()->setCursorFromRow(row);
3547 LYXERR(Debug::LATEX,
3548 "setCursorFromRow: invalid position for row " << row);
3549 frontend::Alert::error(_("Inverse Search Failed"),
3550 _("Invalid position requested by inverse search.\n"
3551 "You may need to update the viewed document."));
3557 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3559 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3560 menu->exec(QCursor::pos());
3565 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3566 Buffer const * orig, Buffer * clone, string const & format)
3568 Buffer::ExportStatus const status = func(format);
3570 // the cloning operation will have produced a clone of the entire set of
3571 // documents, starting from the master. so we must delete those.
3572 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3574 busyBuffers.remove(orig);
3579 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3580 Buffer const * orig, Buffer * clone, string const & format)
3582 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3584 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3588 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3589 Buffer const * orig, Buffer * clone, string const & format)
3591 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3593 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3597 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3598 Buffer const * orig, Buffer * clone, string const & format)
3600 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3602 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3606 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3607 string const & argument,
3608 Buffer const * used_buffer,
3609 docstring const & msg,
3610 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3611 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3612 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3618 string format = argument;
3620 format = used_buffer->params().getDefaultOutputFormat();
3621 processing_format = format;
3623 progress_->clearMessages();
3626 #if EXPORT_in_THREAD
3628 GuiViewPrivate::busyBuffers.insert(used_buffer);
3629 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3630 if (!cloned_buffer) {
3631 Alert::error(_("Export Error"),
3632 _("Error cloning the Buffer."));
3635 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3640 setPreviewFuture(f);
3641 last_export_format = used_buffer->params().bufferFormat();
3644 // We are asynchronous, so we don't know here anything about the success
3647 Buffer::ExportStatus status;
3649 status = (used_buffer->*syncFunc)(format, false);
3650 } else if (previewFunc) {
3651 status = (used_buffer->*previewFunc)(format);
3654 handleExportStatus(gv_, status, format);
3656 return (status == Buffer::ExportSuccess
3657 || status == Buffer::PreviewSuccess);
3661 Buffer::ExportStatus status;
3663 status = (used_buffer->*syncFunc)(format, true);
3664 } else if (previewFunc) {
3665 status = (used_buffer->*previewFunc)(format);
3668 handleExportStatus(gv_, status, format);
3670 return (status == Buffer::ExportSuccess
3671 || status == Buffer::PreviewSuccess);
3675 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3677 BufferView * bv = currentBufferView();
3678 LASSERT(bv, return);
3680 // Let the current BufferView dispatch its own actions.
3681 bv->dispatch(cmd, dr);
3682 if (dr.dispatched())
3685 // Try with the document BufferView dispatch if any.
3686 BufferView * doc_bv = documentBufferView();
3687 if (doc_bv && doc_bv != bv) {
3688 doc_bv->dispatch(cmd, dr);
3689 if (dr.dispatched())
3693 // Then let the current Cursor dispatch its own actions.
3694 bv->cursor().dispatch(cmd);
3696 // update completion. We do it here and not in
3697 // processKeySym to avoid another redraw just for a
3698 // changed inline completion
3699 if (cmd.origin() == FuncRequest::KEYBOARD) {
3700 if (cmd.action() == LFUN_SELF_INSERT
3701 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3702 updateCompletion(bv->cursor(), true, true);
3703 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3704 updateCompletion(bv->cursor(), false, true);
3706 updateCompletion(bv->cursor(), false, false);
3709 dr = bv->cursor().result();
3713 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3715 BufferView * bv = currentBufferView();
3716 // By default we won't need any update.
3717 dr.screenUpdate(Update::None);
3718 // assume cmd will be dispatched
3719 dr.dispatched(true);
3721 Buffer * doc_buffer = documentBufferView()
3722 ? &(documentBufferView()->buffer()) : 0;
3724 if (cmd.origin() == FuncRequest::TOC) {
3725 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3726 // FIXME: do we need to pass a DispatchResult object here?
3727 toc->doDispatch(bv->cursor(), cmd);
3731 string const argument = to_utf8(cmd.argument());
3733 switch(cmd.action()) {
3734 case LFUN_BUFFER_CHILD_OPEN:
3735 openChildDocument(to_utf8(cmd.argument()));
3738 case LFUN_BUFFER_IMPORT:
3739 importDocument(to_utf8(cmd.argument()));
3742 case LFUN_MASTER_BUFFER_EXPORT:
3744 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3746 case LFUN_BUFFER_EXPORT: {
3749 // GCC only sees strfwd.h when building merged
3750 if (::lyx::operator==(cmd.argument(), "custom")) {
3751 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3752 // so the following test should not be needed.
3753 // In principle, we could try to switch to such a view...
3754 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3755 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3759 string const dest = cmd.getArg(1);
3760 FileName target_dir;
3761 if (!dest.empty() && FileName::isAbsolute(dest))
3762 target_dir = FileName(support::onlyPath(dest));
3764 target_dir = doc_buffer->fileName().onlyPath();
3766 string const format = (argument.empty() || argument == "default") ?
3767 doc_buffer->params().getDefaultOutputFormat() : argument;
3769 if ((dest.empty() && doc_buffer->isUnnamed())
3770 || !target_dir.isDirWritable()) {
3771 exportBufferAs(*doc_buffer, from_utf8(format));
3774 /* TODO/Review: Is it a problem to also export the children?
3775 See the update_unincluded flag */
3776 d.asyncBufferProcessing(format,
3779 &GuiViewPrivate::exportAndDestroy,
3781 0, cmd.allowAsync());
3782 // TODO Inform user about success
3786 case LFUN_BUFFER_EXPORT_AS: {
3787 LASSERT(doc_buffer, break);
3788 docstring f = cmd.argument();
3790 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3791 exportBufferAs(*doc_buffer, f);
3795 case LFUN_BUFFER_UPDATE: {
3796 d.asyncBufferProcessing(argument,
3799 &GuiViewPrivate::compileAndDestroy,
3801 0, cmd.allowAsync());
3804 case LFUN_BUFFER_VIEW: {
3805 d.asyncBufferProcessing(argument,
3807 _("Previewing ..."),
3808 &GuiViewPrivate::previewAndDestroy,
3810 &Buffer::preview, cmd.allowAsync());
3813 case LFUN_MASTER_BUFFER_UPDATE: {
3814 d.asyncBufferProcessing(argument,
3815 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3817 &GuiViewPrivate::compileAndDestroy,
3819 0, cmd.allowAsync());
3822 case LFUN_MASTER_BUFFER_VIEW: {
3823 d.asyncBufferProcessing(argument,
3824 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3826 &GuiViewPrivate::previewAndDestroy,
3827 0, &Buffer::preview, cmd.allowAsync());
3830 case LFUN_EXPORT_CANCEL: {
3831 Systemcall::killscript();
3834 case LFUN_BUFFER_SWITCH: {
3835 string const file_name = to_utf8(cmd.argument());
3836 if (!FileName::isAbsolute(file_name)) {
3838 dr.setMessage(_("Absolute filename expected."));
3842 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3845 dr.setMessage(_("Document not loaded"));
3849 // Do we open or switch to the buffer in this view ?
3850 if (workArea(*buffer)
3851 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3856 // Look for the buffer in other views
3857 QList<int> const ids = guiApp->viewIds();
3859 for (; i != ids.size(); ++i) {
3860 GuiView & gv = guiApp->view(ids[i]);
3861 if (gv.workArea(*buffer)) {
3863 gv.activateWindow();
3865 gv.setBuffer(buffer);
3870 // If necessary, open a new window as a last resort
3871 if (i == ids.size()) {
3872 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3878 case LFUN_BUFFER_NEXT:
3879 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3882 case LFUN_BUFFER_MOVE_NEXT:
3883 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3886 case LFUN_BUFFER_PREVIOUS:
3887 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3890 case LFUN_BUFFER_MOVE_PREVIOUS:
3891 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3894 case LFUN_BUFFER_CHKTEX:
3895 LASSERT(doc_buffer, break);
3896 doc_buffer->runChktex();
3899 case LFUN_COMMAND_EXECUTE: {
3900 command_execute_ = true;
3901 minibuffer_focus_ = true;
3904 case LFUN_DROP_LAYOUTS_CHOICE:
3905 d.layout_->showPopup();
3908 case LFUN_MENU_OPEN:
3909 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3910 menu->exec(QCursor::pos());
3913 case LFUN_FILE_INSERT:
3914 insertLyXFile(cmd.argument());
3917 case LFUN_FILE_INSERT_PLAINTEXT:
3918 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3919 string const fname = to_utf8(cmd.argument());
3920 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3921 dr.setMessage(_("Absolute filename expected."));
3925 FileName filename(fname);
3926 if (fname.empty()) {
3927 FileDialog dlg(qt_("Select file to insert"));
3929 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3930 QStringList(qt_("All Files (*)")));
3932 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3933 dr.setMessage(_("Canceled."));
3937 filename.set(fromqstr(result.second));
3941 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3942 bv->dispatch(new_cmd, dr);
3947 case LFUN_BUFFER_RELOAD: {
3948 LASSERT(doc_buffer, break);
3951 bool drop = (cmd.argument() == "dump");
3954 if (!drop && !doc_buffer->isClean()) {
3955 docstring const file =
3956 makeDisplayPath(doc_buffer->absFileName(), 20);
3957 if (doc_buffer->notifiesExternalModification()) {
3958 docstring text = _("The current version will be lost. "
3959 "Are you sure you want to load the version on disk "
3960 "of the document %1$s?");
3961 ret = Alert::prompt(_("Reload saved document?"),
3962 bformat(text, file), 1, 1,
3963 _("&Reload"), _("&Cancel"));
3965 docstring text = _("Any changes will be lost. "
3966 "Are you sure you want to revert to the saved version "
3967 "of the document %1$s?");
3968 ret = Alert::prompt(_("Revert to saved document?"),
3969 bformat(text, file), 1, 1,
3970 _("&Revert"), _("&Cancel"));
3975 doc_buffer->markClean();
3976 reloadBuffer(*doc_buffer);
3977 dr.forceBufferUpdate();
3982 case LFUN_BUFFER_WRITE:
3983 LASSERT(doc_buffer, break);
3984 saveBuffer(*doc_buffer);
3987 case LFUN_BUFFER_WRITE_AS:
3988 LASSERT(doc_buffer, break);
3989 renameBuffer(*doc_buffer, cmd.argument());
3992 case LFUN_BUFFER_WRITE_ALL: {
3993 Buffer * first = theBufferList().first();
3996 message(_("Saving all documents..."));
3997 // We cannot use a for loop as the buffer list cycles.
4000 if (!b->isClean()) {
4002 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4004 b = theBufferList().next(b);
4005 } while (b != first);
4006 dr.setMessage(_("All documents saved."));
4010 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4011 LASSERT(doc_buffer, break);
4012 doc_buffer->clearExternalModification();
4015 case LFUN_BUFFER_CLOSE:
4019 case LFUN_BUFFER_CLOSE_ALL:
4023 case LFUN_DEVEL_MODE_TOGGLE:
4024 devel_mode_ = !devel_mode_;
4026 dr.setMessage(_("Developer mode is now enabled."));
4028 dr.setMessage(_("Developer mode is now disabled."));
4031 case LFUN_TOOLBAR_TOGGLE: {
4032 string const name = cmd.getArg(0);
4033 if (GuiToolbar * t = toolbar(name))
4038 case LFUN_TOOLBAR_MOVABLE: {
4039 string const name = cmd.getArg(0);
4041 // toggle (all) toolbars movablility
4042 toolbarsMovable_ = !toolbarsMovable_;
4043 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4044 GuiToolbar * tb = toolbar(ti.name);
4045 if (tb && tb->isMovable() != toolbarsMovable_)
4046 // toggle toolbar movablity if it does not fit lock
4047 // (all) toolbars positions state silent = true, since
4048 // status bar notifications are slow
4051 if (toolbarsMovable_)
4052 dr.setMessage(_("Toolbars unlocked."));
4054 dr.setMessage(_("Toolbars locked."));
4055 } else if (GuiToolbar * t = toolbar(name)) {
4056 // toggle current toolbar movablity
4058 // update lock (all) toolbars positions
4059 updateLockToolbars();
4064 case LFUN_ICON_SIZE: {
4065 QSize size = d.iconSize(cmd.argument());
4067 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4068 size.width(), size.height()));
4072 case LFUN_DIALOG_UPDATE: {
4073 string const name = to_utf8(cmd.argument());
4074 if (name == "prefs" || name == "document")
4075 updateDialog(name, string());
4076 else if (name == "paragraph")
4077 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4078 else if (currentBufferView()) {
4079 Inset * inset = currentBufferView()->editedInset(name);
4080 // Can only update a dialog connected to an existing inset
4082 // FIXME: get rid of this indirection; GuiView ask the inset
4083 // if he is kind enough to update itself...
4084 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4085 //FIXME: pass DispatchResult here?
4086 inset->dispatch(currentBufferView()->cursor(), fr);
4092 case LFUN_DIALOG_TOGGLE: {
4093 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4094 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4095 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4099 case LFUN_DIALOG_DISCONNECT_INSET:
4100 disconnectDialog(to_utf8(cmd.argument()));
4103 case LFUN_DIALOG_HIDE: {
4104 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4108 case LFUN_DIALOG_SHOW: {
4109 string const name = cmd.getArg(0);
4110 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4112 if (name == "character") {
4113 sdata = freefont2string();
4115 showDialog("character", sdata);
4116 } else if (name == "latexlog") {
4117 // gettatus checks that
4118 LATTEST(doc_buffer);
4119 Buffer::LogType type;
4120 string const logfile = doc_buffer->logName(&type);
4122 case Buffer::latexlog:
4125 case Buffer::buildlog:
4126 sdata = "literate ";
4129 sdata += Lexer::quoteString(logfile);
4130 showDialog("log", sdata);
4131 } else if (name == "vclog") {
4132 // getStatus checks that
4133 LATTEST(doc_buffer);
4134 string const sdata2 = "vc " +
4135 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4136 showDialog("log", sdata2);
4137 } else if (name == "symbols") {
4138 sdata = bv->cursor().getEncoding()->name();
4140 showDialog("symbols", sdata);
4142 } else if (name == "prefs" && isFullScreen()) {
4143 lfunUiToggle("fullscreen");
4144 showDialog("prefs", sdata);
4146 showDialog(name, sdata);
4151 dr.setMessage(cmd.argument());
4154 case LFUN_UI_TOGGLE: {
4155 string arg = cmd.getArg(0);
4156 if (!lfunUiToggle(arg)) {
4157 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4158 dr.setMessage(bformat(msg, from_utf8(arg)));
4160 // Make sure the keyboard focus stays in the work area.
4165 case LFUN_VIEW_SPLIT: {
4166 LASSERT(doc_buffer, break);
4167 string const orientation = cmd.getArg(0);
4168 d.splitter_->setOrientation(orientation == "vertical"
4169 ? Qt::Vertical : Qt::Horizontal);
4170 TabWorkArea * twa = addTabWorkArea();
4171 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4172 setCurrentWorkArea(wa);
4175 case LFUN_TAB_GROUP_CLOSE:
4176 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4177 closeTabWorkArea(twa);
4178 d.current_work_area_ = 0;
4179 twa = d.currentTabWorkArea();
4180 // Switch to the next GuiWorkArea in the found TabWorkArea.
4182 // Make sure the work area is up to date.
4183 setCurrentWorkArea(twa->currentWorkArea());
4185 setCurrentWorkArea(0);
4190 case LFUN_VIEW_CLOSE:
4191 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4192 closeWorkArea(twa->currentWorkArea());
4193 d.current_work_area_ = 0;
4194 twa = d.currentTabWorkArea();
4195 // Switch to the next GuiWorkArea in the found TabWorkArea.
4197 // Make sure the work area is up to date.
4198 setCurrentWorkArea(twa->currentWorkArea());
4200 setCurrentWorkArea(0);
4205 case LFUN_COMPLETION_INLINE:
4206 if (d.current_work_area_)
4207 d.current_work_area_->completer().showInline();
4210 case LFUN_COMPLETION_POPUP:
4211 if (d.current_work_area_)
4212 d.current_work_area_->completer().showPopup();
4217 if (d.current_work_area_)
4218 d.current_work_area_->completer().tab();
4221 case LFUN_COMPLETION_CANCEL:
4222 if (d.current_work_area_) {
4223 if (d.current_work_area_->completer().popupVisible())
4224 d.current_work_area_->completer().hidePopup();
4226 d.current_work_area_->completer().hideInline();
4230 case LFUN_COMPLETION_ACCEPT:
4231 if (d.current_work_area_)
4232 d.current_work_area_->completer().activate();
4235 case LFUN_BUFFER_ZOOM_IN:
4236 case LFUN_BUFFER_ZOOM_OUT:
4237 case LFUN_BUFFER_ZOOM: {
4238 if (cmd.argument().empty()) {
4239 if (cmd.action() == LFUN_BUFFER_ZOOM)
4241 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4246 if (cmd.action() == LFUN_BUFFER_ZOOM)
4247 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4248 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4249 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4251 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4254 // Actual zoom value: default zoom + fractional extra value
4255 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4256 if (zoom < static_cast<int>(zoom_min_))
4259 lyxrc.currentZoom = zoom;
4261 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4262 lyxrc.currentZoom, lyxrc.defaultZoom));
4264 // The global QPixmapCache is used in GuiPainter to cache text
4265 // painting so we must reset it.
4266 QPixmapCache::clear();
4267 guiApp->fontLoader().update();
4268 dr.screenUpdate(Update::Force | Update::FitCursor);
4272 case LFUN_VC_REGISTER:
4273 case LFUN_VC_RENAME:
4275 case LFUN_VC_CHECK_IN:
4276 case LFUN_VC_CHECK_OUT:
4277 case LFUN_VC_REPO_UPDATE:
4278 case LFUN_VC_LOCKING_TOGGLE:
4279 case LFUN_VC_REVERT:
4280 case LFUN_VC_UNDO_LAST:
4281 case LFUN_VC_COMMAND:
4282 case LFUN_VC_COMPARE:
4283 dispatchVC(cmd, dr);
4286 case LFUN_SERVER_GOTO_FILE_ROW:
4287 if(goToFileRow(to_utf8(cmd.argument())))
4288 dr.screenUpdate(Update::Force | Update::FitCursor);
4291 case LFUN_LYX_ACTIVATE:
4295 case LFUN_FORWARD_SEARCH: {
4296 // it seems safe to assume we have a document buffer, since
4297 // getStatus wants one.
4298 LATTEST(doc_buffer);
4299 Buffer const * doc_master = doc_buffer->masterBuffer();
4300 FileName const path(doc_master->temppath());
4301 string const texname = doc_master->isChild(doc_buffer)
4302 ? DocFileName(changeExtension(
4303 doc_buffer->absFileName(),
4304 "tex")).mangledFileName()
4305 : doc_buffer->latexName();
4306 string const fulltexname =
4307 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4308 string const mastername =
4309 removeExtension(doc_master->latexName());
4310 FileName const dviname(addName(path.absFileName(),
4311 addExtension(mastername, "dvi")));
4312 FileName const pdfname(addName(path.absFileName(),
4313 addExtension(mastername, "pdf")));
4314 bool const have_dvi = dviname.exists();
4315 bool const have_pdf = pdfname.exists();
4316 if (!have_dvi && !have_pdf) {
4317 dr.setMessage(_("Please, preview the document first."));
4320 string outname = dviname.onlyFileName();
4321 string command = lyxrc.forward_search_dvi;
4322 if (!have_dvi || (have_pdf &&
4323 pdfname.lastModified() > dviname.lastModified())) {
4324 outname = pdfname.onlyFileName();
4325 command = lyxrc.forward_search_pdf;
4328 DocIterator cur = bv->cursor();
4329 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4330 LYXERR(Debug::ACTION, "Forward search: row:" << row
4332 if (row == -1 || command.empty()) {
4333 dr.setMessage(_("Couldn't proceed."));
4336 string texrow = convert<string>(row);
4338 command = subst(command, "$$n", texrow);
4339 command = subst(command, "$$f", fulltexname);
4340 command = subst(command, "$$t", texname);
4341 command = subst(command, "$$o", outname);
4343 PathChanger p(path);
4345 one.startscript(Systemcall::DontWait, command);
4349 case LFUN_SPELLING_CONTINUOUSLY:
4350 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4351 dr.screenUpdate(Update::Force);
4355 // The LFUN must be for one of BufferView, Buffer or Cursor;
4357 dispatchToBufferView(cmd, dr);
4361 // Part of automatic menu appearance feature.
4362 if (isFullScreen()) {
4363 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4367 // Need to update bv because many LFUNs here might have destroyed it
4368 bv = currentBufferView();
4370 // Clear non-empty selections
4371 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4373 Cursor & cur = bv->cursor();
4374 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4375 cur.clearSelection();
4381 bool GuiView::lfunUiToggle(string const & ui_component)
4383 if (ui_component == "scrollbar") {
4384 // hide() is of no help
4385 if (d.current_work_area_->verticalScrollBarPolicy() ==
4386 Qt::ScrollBarAlwaysOff)
4388 d.current_work_area_->setVerticalScrollBarPolicy(
4389 Qt::ScrollBarAsNeeded);
4391 d.current_work_area_->setVerticalScrollBarPolicy(
4392 Qt::ScrollBarAlwaysOff);
4393 } else if (ui_component == "statusbar") {
4394 statusBar()->setVisible(!statusBar()->isVisible());
4395 } else if (ui_component == "menubar") {
4396 menuBar()->setVisible(!menuBar()->isVisible());
4398 if (ui_component == "frame") {
4400 getContentsMargins(&l, &t, &r, &b);
4401 //are the frames in default state?
4402 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4404 setContentsMargins(-2, -2, -2, -2);
4406 setContentsMargins(0, 0, 0, 0);
4409 if (ui_component == "fullscreen") {
4417 void GuiView::toggleFullScreen()
4419 if (isFullScreen()) {
4420 for (int i = 0; i != d.splitter_->count(); ++i)
4421 d.tabWorkArea(i)->setFullScreen(false);
4422 setContentsMargins(0, 0, 0, 0);
4423 setWindowState(windowState() ^ Qt::WindowFullScreen);
4426 statusBar()->show();
4429 hideDialogs("prefs", 0);
4430 for (int i = 0; i != d.splitter_->count(); ++i)
4431 d.tabWorkArea(i)->setFullScreen(true);
4432 setContentsMargins(-2, -2, -2, -2);
4434 setWindowState(windowState() ^ Qt::WindowFullScreen);
4435 if (lyxrc.full_screen_statusbar)
4436 statusBar()->hide();
4437 if (lyxrc.full_screen_menubar)
4439 if (lyxrc.full_screen_toolbars) {
4440 ToolbarMap::iterator end = d.toolbars_.end();
4441 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4446 // give dialogs like the TOC a chance to adapt
4451 Buffer const * GuiView::updateInset(Inset const * inset)
4456 Buffer const * inset_buffer = &(inset->buffer());
4458 for (int i = 0; i != d.splitter_->count(); ++i) {
4459 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4462 Buffer const * buffer = &(wa->bufferView().buffer());
4463 if (inset_buffer == buffer)
4464 wa->scheduleRedraw(true);
4466 return inset_buffer;
4470 void GuiView::restartCaret()
4472 /* When we move around, or type, it's nice to be able to see
4473 * the caret immediately after the keypress.
4475 if (d.current_work_area_)
4476 d.current_work_area_->startBlinkingCaret();
4478 // Take this occasion to update the other GUI elements.
4484 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4486 if (d.current_work_area_)
4487 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4492 // This list should be kept in sync with the list of insets in
4493 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4494 // dialog should have the same name as the inset.
4495 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4496 // docs in LyXAction.cpp.
4498 char const * const dialognames[] = {
4500 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4501 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4502 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4503 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4504 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4505 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4506 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4507 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4509 char const * const * const end_dialognames =
4510 dialognames + (sizeof(dialognames) / sizeof(char *));
4514 cmpCStr(char const * name) : name_(name) {}
4515 bool operator()(char const * other) {
4516 return strcmp(other, name_) == 0;
4523 bool isValidName(string const & name)
4525 return find_if(dialognames, end_dialognames,
4526 cmpCStr(name.c_str())) != end_dialognames;
4532 void GuiView::resetDialogs()
4534 // Make sure that no LFUN uses any GuiView.
4535 guiApp->setCurrentView(0);
4539 constructToolbars();
4540 guiApp->menus().fillMenuBar(menuBar(), this, false);
4541 d.layout_->updateContents(true);
4542 // Now update controls with current buffer.
4543 guiApp->setCurrentView(this);
4549 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4551 if (!isValidName(name))
4554 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4556 if (it != d.dialogs_.end()) {
4558 it->second->hideView();
4559 return it->second.get();
4562 Dialog * dialog = build(name);
4563 d.dialogs_[name].reset(dialog);
4564 if (lyxrc.allow_geometry_session)
4565 dialog->restoreSession();
4572 void GuiView::showDialog(string const & name, string const & sdata,
4575 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4579 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4585 const string name = fromqstr(qname);
4586 const string sdata = fromqstr(qdata);
4590 Dialog * dialog = findOrBuild(name, false);
4592 bool const visible = dialog->isVisibleView();
4593 dialog->showData(sdata);
4594 if (inset && currentBufferView())
4595 currentBufferView()->editInset(name, inset);
4596 // We only set the focus to the new dialog if it was not yet
4597 // visible in order not to change the existing previous behaviour
4599 // activateWindow is needed for floating dockviews
4600 dialog->asQWidget()->raise();
4601 dialog->asQWidget()->activateWindow();
4602 dialog->asQWidget()->setFocus();
4606 catch (ExceptionMessage const & ex) {
4614 bool GuiView::isDialogVisible(string const & name) const
4616 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4617 if (it == d.dialogs_.end())
4619 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4623 void GuiView::hideDialog(string const & name, Inset * inset)
4625 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4626 if (it == d.dialogs_.end())
4630 if (!currentBufferView())
4632 if (inset != currentBufferView()->editedInset(name))
4636 Dialog * const dialog = it->second.get();
4637 if (dialog->isVisibleView())
4639 if (currentBufferView())
4640 currentBufferView()->editInset(name, 0);
4644 void GuiView::disconnectDialog(string const & name)
4646 if (!isValidName(name))
4648 if (currentBufferView())
4649 currentBufferView()->editInset(name, 0);
4653 void GuiView::hideAll() const
4655 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4656 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4658 for(; it != end; ++it)
4659 it->second->hideView();
4663 void GuiView::updateDialogs()
4665 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4666 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4668 for(; it != end; ++it) {
4669 Dialog * dialog = it->second.get();
4671 if (dialog->needBufferOpen() && !documentBufferView())
4672 hideDialog(fromqstr(dialog->name()), 0);
4673 else if (dialog->isVisibleView())
4674 dialog->checkStatus();
4681 Dialog * createDialog(GuiView & lv, string const & name);
4683 // will be replaced by a proper factory...
4684 Dialog * createGuiAbout(GuiView & lv);
4685 Dialog * createGuiBibtex(GuiView & lv);
4686 Dialog * createGuiChanges(GuiView & lv);
4687 Dialog * createGuiCharacter(GuiView & lv);
4688 Dialog * createGuiCitation(GuiView & lv);
4689 Dialog * createGuiCompare(GuiView & lv);
4690 Dialog * createGuiCompareHistory(GuiView & lv);
4691 Dialog * createGuiDelimiter(GuiView & lv);
4692 Dialog * createGuiDocument(GuiView & lv);
4693 Dialog * createGuiErrorList(GuiView & lv);
4694 Dialog * createGuiExternal(GuiView & lv);
4695 Dialog * createGuiGraphics(GuiView & lv);
4696 Dialog * createGuiInclude(GuiView & lv);
4697 Dialog * createGuiIndex(GuiView & lv);
4698 Dialog * createGuiListings(GuiView & lv);
4699 Dialog * createGuiLog(GuiView & lv);
4700 Dialog * createGuiMathMatrix(GuiView & lv);
4701 Dialog * createGuiNote(GuiView & lv);
4702 Dialog * createGuiParagraph(GuiView & lv);
4703 Dialog * createGuiPhantom(GuiView & lv);
4704 Dialog * createGuiPreferences(GuiView & lv);
4705 Dialog * createGuiPrint(GuiView & lv);
4706 Dialog * createGuiPrintindex(GuiView & lv);
4707 Dialog * createGuiRef(GuiView & lv);
4708 Dialog * createGuiSearch(GuiView & lv);
4709 Dialog * createGuiSearchAdv(GuiView & lv);
4710 Dialog * createGuiSendTo(GuiView & lv);
4711 Dialog * createGuiShowFile(GuiView & lv);
4712 Dialog * createGuiSpellchecker(GuiView & lv);
4713 Dialog * createGuiSymbols(GuiView & lv);
4714 Dialog * createGuiTabularCreate(GuiView & lv);
4715 Dialog * createGuiTexInfo(GuiView & lv);
4716 Dialog * createGuiToc(GuiView & lv);
4717 Dialog * createGuiThesaurus(GuiView & lv);
4718 Dialog * createGuiViewSource(GuiView & lv);
4719 Dialog * createGuiWrap(GuiView & lv);
4720 Dialog * createGuiProgressView(GuiView & lv);
4724 Dialog * GuiView::build(string const & name)
4726 LASSERT(isValidName(name), return 0);
4728 Dialog * dialog = createDialog(*this, name);
4732 if (name == "aboutlyx")
4733 return createGuiAbout(*this);
4734 if (name == "bibtex")
4735 return createGuiBibtex(*this);
4736 if (name == "changes")
4737 return createGuiChanges(*this);
4738 if (name == "character")
4739 return createGuiCharacter(*this);
4740 if (name == "citation")
4741 return createGuiCitation(*this);
4742 if (name == "compare")
4743 return createGuiCompare(*this);
4744 if (name == "comparehistory")
4745 return createGuiCompareHistory(*this);
4746 if (name == "document")
4747 return createGuiDocument(*this);
4748 if (name == "errorlist")
4749 return createGuiErrorList(*this);
4750 if (name == "external")
4751 return createGuiExternal(*this);
4753 return createGuiShowFile(*this);
4754 if (name == "findreplace")
4755 return createGuiSearch(*this);
4756 if (name == "findreplaceadv")
4757 return createGuiSearchAdv(*this);
4758 if (name == "graphics")
4759 return createGuiGraphics(*this);
4760 if (name == "include")
4761 return createGuiInclude(*this);
4762 if (name == "index")
4763 return createGuiIndex(*this);
4764 if (name == "index_print")
4765 return createGuiPrintindex(*this);
4766 if (name == "listings")
4767 return createGuiListings(*this);
4769 return createGuiLog(*this);
4770 if (name == "mathdelimiter")
4771 return createGuiDelimiter(*this);
4772 if (name == "mathmatrix")
4773 return createGuiMathMatrix(*this);
4775 return createGuiNote(*this);
4776 if (name == "paragraph")
4777 return createGuiParagraph(*this);
4778 if (name == "phantom")
4779 return createGuiPhantom(*this);
4780 if (name == "prefs")
4781 return createGuiPreferences(*this);
4783 return createGuiRef(*this);
4784 if (name == "sendto")
4785 return createGuiSendTo(*this);
4786 if (name == "spellchecker")
4787 return createGuiSpellchecker(*this);
4788 if (name == "symbols")
4789 return createGuiSymbols(*this);
4790 if (name == "tabularcreate")
4791 return createGuiTabularCreate(*this);
4792 if (name == "texinfo")
4793 return createGuiTexInfo(*this);
4794 if (name == "thesaurus")
4795 return createGuiThesaurus(*this);
4797 return createGuiToc(*this);
4798 if (name == "view-source")
4799 return createGuiViewSource(*this);
4801 return createGuiWrap(*this);
4802 if (name == "progress")
4803 return createGuiProgressView(*this);
4809 SEMenu::SEMenu(QWidget * parent)
4811 QAction * action = addAction(qt_("Disable Shell Escape"));
4812 connect(action, SIGNAL(triggered()),
4813 parent, SLOT(disableShellEscape()));
4816 } // namespace frontend
4819 #include "moc_GuiView.cpp"