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 QString const htextsize = qt_("20[[possibly adjust the welcome banner text size; float value allowed]]");
162 /// The text to be written on top of the pixmap
163 QString const text = lyx_version ?
164 qt_("version ") + lyx_version : qt_("unknown version");
165 #if QT_VERSION >= 0x050000
166 QString imagedir = "images/";
167 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
168 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
169 if (svgRenderer.isValid()) {
170 splash_ = QPixmap(splashSize());
171 QPainter painter(&splash_);
172 svgRenderer.render(&painter);
173 splash_.setDevicePixelRatio(pixelRatio());
175 splash_ = getPixmap("images/", "banner", "png");
178 splash_ = getPixmap("images/", "banner", "svgz,png");
181 QPainter pain(&splash_);
182 pain.setPen(QColor(0, 0, 0));
183 qreal const fsize = fontSize();
185 qreal hfsize = htextsize.toFloat(&ok);
188 QPointF const position = textPosition(false);
189 QPointF const hposition = textPosition(true);
190 QRectF const hrect(hposition, splashSize());
192 "widget pixel ratio: " << pixelRatio() <<
193 " splash pixel ratio: " << splashPixelRatio() <<
194 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Bold);
199 font.setPointSizeF(fsize);
201 pain.drawText(position, text);
202 // The font used to display the version info
203 font.setStyleHint(QFont::SansSerif);
204 font.setWeight(QFont::Normal);
205 font.setPointSizeF(hfsize);
207 pain.drawText(hrect, Qt::AlignLeft, htext);
208 setFocusPolicy(Qt::StrongFocus);
211 void paintEvent(QPaintEvent *)
213 int const w = width_;
214 int const h = height_;
215 int const x = (width() - w) / 2;
216 int const y = (height() - h) / 2;
218 "widget pixel ratio: " << pixelRatio() <<
219 " splash pixel ratio: " << splashPixelRatio() <<
220 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
222 pain.drawPixmap(x, y, w, h, splash_);
225 void keyPressEvent(QKeyEvent * ev)
228 setKeySymbol(&sym, ev);
230 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
242 /// Current ratio between physical pixels and device-independent pixels
243 double pixelRatio() const {
244 #if QT_VERSION >= 0x050000
245 return qt_scale_factor * devicePixelRatio();
251 qreal fontSize() const {
252 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
255 QPointF textPosition(bool const heading) const {
256 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
257 : QPointF(width_/2 - 18, height_/2 + 45);
260 QSize splashSize() const {
262 static_cast<unsigned int>(width_ * pixelRatio()),
263 static_cast<unsigned int>(height_ * pixelRatio()));
266 /// Ratio between physical pixels and device-independent pixels of splash image
267 double splashPixelRatio() const {
268 #if QT_VERSION >= 0x050000
269 return splash_.devicePixelRatio();
277 /// Toolbar store providing access to individual toolbars by name.
278 typedef map<string, GuiToolbar *> ToolbarMap;
280 typedef shared_ptr<Dialog> DialogPtr;
285 class GuiView::GuiViewPrivate
288 GuiViewPrivate(GuiViewPrivate const &);
289 void operator=(GuiViewPrivate const &);
291 GuiViewPrivate(GuiView * gv)
292 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
293 layout_(0), autosave_timeout_(5000),
296 // hardcode here the platform specific icon size
297 smallIconSize = 16; // scaling problems
298 normalIconSize = 20; // ok, default if iconsize.png is missing
299 bigIconSize = 26; // better for some math icons
300 hugeIconSize = 32; // better for hires displays
303 // if it exists, use width of iconsize.png as normal size
304 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
305 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
307 QImage image(toqstr(fn.absFileName()));
308 if (image.width() < int(smallIconSize))
309 normalIconSize = smallIconSize;
310 else if (image.width() > int(giantIconSize))
311 normalIconSize = giantIconSize;
313 normalIconSize = image.width();
316 splitter_ = new QSplitter;
317 bg_widget_ = new BackgroundWidget(400, 250);
318 stack_widget_ = new QStackedWidget;
319 stack_widget_->addWidget(bg_widget_);
320 stack_widget_->addWidget(splitter_);
323 // TODO cleanup, remove the singleton, handle multiple Windows?
324 progress_ = ProgressInterface::instance();
325 if (!dynamic_cast<GuiProgress*>(progress_)) {
326 progress_ = new GuiProgress; // TODO who deletes it
327 ProgressInterface::setInstance(progress_);
330 dynamic_cast<GuiProgress*>(progress_),
331 SIGNAL(updateStatusBarMessage(QString const&)),
332 gv, SLOT(updateStatusBarMessage(QString const&)));
334 dynamic_cast<GuiProgress*>(progress_),
335 SIGNAL(clearMessageText()),
336 gv, SLOT(clearMessageText()));
343 delete stack_widget_;
348 stack_widget_->setCurrentWidget(bg_widget_);
349 bg_widget_->setUpdatesEnabled(true);
350 bg_widget_->setFocus();
353 int tabWorkAreaCount()
355 return splitter_->count();
358 TabWorkArea * tabWorkArea(int i)
360 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
363 TabWorkArea * currentTabWorkArea()
365 int areas = tabWorkAreaCount();
367 // The first TabWorkArea is always the first one, if any.
368 return tabWorkArea(0);
370 for (int i = 0; i != areas; ++i) {
371 TabWorkArea * twa = tabWorkArea(i);
372 if (current_main_work_area_ == twa->currentWorkArea())
376 // None has the focus so we just take the first one.
377 return tabWorkArea(0);
380 int countWorkAreasOf(Buffer & buf)
382 int areas = tabWorkAreaCount();
384 for (int i = 0; i != areas; ++i) {
385 TabWorkArea * twa = tabWorkArea(i);
386 if (twa->workArea(buf))
392 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
394 if (processing_thread_watcher_.isRunning()) {
395 // we prefer to cancel this preview in order to keep a snappy
399 processing_thread_watcher_.setFuture(f);
402 QSize iconSize(docstring const & icon_size)
405 if (icon_size == "small")
406 size = smallIconSize;
407 else if (icon_size == "normal")
408 size = normalIconSize;
409 else if (icon_size == "big")
411 else if (icon_size == "huge")
413 else if (icon_size == "giant")
414 size = giantIconSize;
416 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
418 if (size < smallIconSize)
419 size = smallIconSize;
421 return QSize(size, size);
424 QSize iconSize(QString const & icon_size)
426 return iconSize(qstring_to_ucs4(icon_size));
429 string & iconSize(QSize const & qsize)
431 LATTEST(qsize.width() == qsize.height());
433 static string icon_size;
435 unsigned int size = qsize.width();
437 if (size < smallIconSize)
438 size = smallIconSize;
440 if (size == smallIconSize)
442 else if (size == normalIconSize)
443 icon_size = "normal";
444 else if (size == bigIconSize)
446 else if (size == hugeIconSize)
448 else if (size == giantIconSize)
451 icon_size = convert<string>(size);
458 GuiWorkArea * current_work_area_;
459 GuiWorkArea * current_main_work_area_;
460 QSplitter * splitter_;
461 QStackedWidget * stack_widget_;
462 BackgroundWidget * bg_widget_;
464 ToolbarMap toolbars_;
465 ProgressInterface* progress_;
466 /// The main layout box.
468 * \warning Don't Delete! The layout box is actually owned by
469 * whichever toolbar contains it. All the GuiView class needs is a
470 * means of accessing it.
472 * FIXME: replace that with a proper model so that we are not limited
473 * to only one dialog.
478 map<string, DialogPtr> dialogs_;
480 unsigned int smallIconSize;
481 unsigned int normalIconSize;
482 unsigned int bigIconSize;
483 unsigned int hugeIconSize;
484 unsigned int giantIconSize;
486 QTimer statusbar_timer_;
487 /// auto-saving of buffers
488 Timeout autosave_timeout_;
489 /// flag against a race condition due to multiclicks, see bug #1119
493 TocModels toc_models_;
496 QFutureWatcher<docstring> autosave_watcher_;
497 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
499 string last_export_format;
500 string processing_format;
502 static QSet<Buffer const *> busyBuffers;
503 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
504 Buffer * buffer, string const & format);
505 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
506 Buffer * buffer, string const & format);
507 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
508 Buffer * buffer, string const & format);
509 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
512 static Buffer::ExportStatus runAndDestroy(const T& func,
513 Buffer const * orig, Buffer * buffer, string const & format);
515 // TODO syncFunc/previewFunc: use bind
516 bool asyncBufferProcessing(string const & argument,
517 Buffer const * used_buffer,
518 docstring const & msg,
519 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
520 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
521 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
524 QVector<GuiWorkArea*> guiWorkAreas();
527 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
530 GuiView::GuiView(int id)
531 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
532 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
535 connect(this, SIGNAL(bufferViewChanged()),
536 this, SLOT(onBufferViewChanged()));
538 // GuiToolbars *must* be initialised before the menu bar.
539 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
542 // set ourself as the current view. This is needed for the menu bar
543 // filling, at least for the static special menu item on Mac. Otherwise
544 // they are greyed out.
545 guiApp->setCurrentView(this);
547 // Fill up the menu bar.
548 guiApp->menus().fillMenuBar(menuBar(), this, true);
550 setCentralWidget(d.stack_widget_);
552 // Start autosave timer
553 if (lyxrc.autosave) {
554 // The connection is closed when this is destroyed.
555 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
556 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
557 d.autosave_timeout_.start();
559 connect(&d.statusbar_timer_, SIGNAL(timeout()),
560 this, SLOT(clearMessage()));
562 // We don't want to keep the window in memory if it is closed.
563 setAttribute(Qt::WA_DeleteOnClose, true);
565 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
566 // QIcon::fromTheme was introduced in Qt 4.6
567 #if (QT_VERSION >= 0x040600)
568 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
569 // since the icon is provided in the application bundle. We use a themed
570 // version when available and use the bundled one as fallback.
571 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
573 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
579 // use tabbed dock area for multiple docks
580 // (such as "source" and "messages")
581 setDockOptions(QMainWindow::ForceTabbedDocks);
584 setAcceptDrops(true);
586 // add busy indicator to statusbar
587 QLabel * busylabel = new QLabel(statusBar());
588 statusBar()->addPermanentWidget(busylabel);
589 search_mode mode = theGuiApp()->imageSearchMode();
590 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
591 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
592 busylabel->setMovie(busyanim);
596 connect(&d.processing_thread_watcher_, SIGNAL(started()),
597 busylabel, SLOT(show()));
598 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
599 busylabel, SLOT(hide()));
601 QFontMetrics const fm(statusBar()->fontMetrics());
602 int const iconheight = max(int(d.normalIconSize), fm.height());
603 QSize const iconsize(iconheight, iconheight);
605 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
606 shell_escape_ = new QLabel(statusBar());
607 shell_escape_->setPixmap(shellescape);
608 shell_escape_->setScaledContents(true);
609 shell_escape_->setAlignment(Qt::AlignCenter);
610 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
611 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
612 "external commands for this document. "
613 "Right click to change."));
614 SEMenu * menu = new SEMenu(this);
615 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
616 menu, SLOT(showMenu(QPoint)));
617 shell_escape_->hide();
618 statusBar()->addPermanentWidget(shell_escape_);
620 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
621 read_only_ = new QLabel(statusBar());
622 read_only_->setPixmap(readonly);
623 read_only_->setScaledContents(true);
624 read_only_->setAlignment(Qt::AlignCenter);
626 statusBar()->addPermanentWidget(read_only_);
628 version_control_ = new QLabel(statusBar());
629 version_control_->setAlignment(Qt::AlignCenter);
630 version_control_->setFrameStyle(QFrame::StyledPanel);
631 version_control_->hide();
632 statusBar()->addPermanentWidget(version_control_);
634 statusBar()->setSizeGripEnabled(true);
637 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
638 SLOT(autoSaveThreadFinished()));
640 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
641 SLOT(processingThreadStarted()));
642 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
643 SLOT(processingThreadFinished()));
645 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
646 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
648 // set custom application bars context menu, e.g. tool bar and menu bar
649 setContextMenuPolicy(Qt::CustomContextMenu);
650 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
651 SLOT(toolBarPopup(const QPoint &)));
653 // Forbid too small unresizable window because it can happen
654 // with some window manager under X11.
655 setMinimumSize(300, 200);
657 if (lyxrc.allow_geometry_session) {
658 // Now take care of session management.
663 // no session handling, default to a sane size.
664 setGeometry(50, 50, 690, 510);
667 // clear session data if any.
669 settings.remove("views");
679 void GuiView::disableShellEscape()
681 BufferView * bv = documentBufferView();
684 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
685 bv->buffer().params().shell_escape = false;
686 bv->processUpdateFlags(Update::Force);
690 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
692 QVector<GuiWorkArea*> areas;
693 for (int i = 0; i < tabWorkAreaCount(); i++) {
694 TabWorkArea* ta = tabWorkArea(i);
695 for (int u = 0; u < ta->count(); u++) {
696 areas << ta->workArea(u);
702 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
703 string const & format)
705 docstring const fmt = theFormats().prettyName(format);
708 case Buffer::ExportSuccess:
709 msg = bformat(_("Successful export to format: %1$s"), fmt);
711 case Buffer::ExportCancel:
712 msg = _("Document export cancelled.");
714 case Buffer::ExportError:
715 case Buffer::ExportNoPathToFormat:
716 case Buffer::ExportTexPathHasSpaces:
717 case Buffer::ExportConverterError:
718 msg = bformat(_("Error while exporting format: %1$s"), fmt);
720 case Buffer::PreviewSuccess:
721 msg = bformat(_("Successful preview of format: %1$s"), fmt);
723 case Buffer::PreviewError:
724 msg = bformat(_("Error while previewing format: %1$s"), fmt);
726 case Buffer::ExportKilled:
727 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
734 void GuiView::processingThreadStarted()
739 void GuiView::processingThreadFinished()
741 QFutureWatcher<Buffer::ExportStatus> const * watcher =
742 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
744 Buffer::ExportStatus const status = watcher->result();
745 handleExportStatus(this, status, d.processing_format);
748 BufferView const * const bv = currentBufferView();
749 if (bv && !bv->buffer().errorList("Export").empty()) {
754 bool const error = (status != Buffer::ExportSuccess &&
755 status != Buffer::PreviewSuccess &&
756 status != Buffer::ExportCancel);
758 ErrorList & el = bv->buffer().errorList(d.last_export_format);
759 // at this point, we do not know if buffer-view or
760 // master-buffer-view was called. If there was an export error,
761 // and the current buffer's error log is empty, we guess that
762 // it must be master-buffer-view that was called so we set
764 errors(d.last_export_format, el.empty());
769 void GuiView::autoSaveThreadFinished()
771 QFutureWatcher<docstring> const * watcher =
772 static_cast<QFutureWatcher<docstring> const *>(sender());
773 message(watcher->result());
778 void GuiView::saveLayout() const
781 settings.setValue("zoom_ratio", zoom_ratio_);
782 settings.setValue("devel_mode", devel_mode_);
783 settings.beginGroup("views");
784 settings.beginGroup(QString::number(id_));
785 #if defined(Q_WS_X11) || defined(QPA_XCB)
786 settings.setValue("pos", pos());
787 settings.setValue("size", size());
789 settings.setValue("geometry", saveGeometry());
791 settings.setValue("layout", saveState(0));
792 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
796 void GuiView::saveUISettings() const
800 // Save the toolbar private states
801 ToolbarMap::iterator end = d.toolbars_.end();
802 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
803 it->second->saveSession(settings);
804 // Now take care of all other dialogs
805 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
806 for (; it!= d.dialogs_.end(); ++it)
807 it->second->saveSession(settings);
811 bool GuiView::restoreLayout()
814 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
815 // Actual zoom value: default zoom + fractional offset
816 int zoom = lyxrc.defaultZoom * zoom_ratio_;
817 if (zoom < static_cast<int>(zoom_min_))
819 lyxrc.currentZoom = zoom;
820 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
821 settings.beginGroup("views");
822 settings.beginGroup(QString::number(id_));
823 QString const icon_key = "icon_size";
824 if (!settings.contains(icon_key))
827 //code below is skipped when when ~/.config/LyX is (re)created
828 setIconSize(d.iconSize(settings.value(icon_key).toString()));
830 #if defined(Q_WS_X11) || defined(QPA_XCB)
831 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
832 QSize size = settings.value("size", QSize(690, 510)).toSize();
836 // Work-around for bug #6034: the window ends up in an undetermined
837 // state when trying to restore a maximized window when it is
838 // already maximized.
839 if (!(windowState() & Qt::WindowMaximized))
840 if (!restoreGeometry(settings.value("geometry").toByteArray()))
841 setGeometry(50, 50, 690, 510);
843 // Make sure layout is correctly oriented.
844 setLayoutDirection(qApp->layoutDirection());
846 // Allow the toc and view-source dock widget to be restored if needed.
848 if ((dialog = findOrBuild("toc", true)))
849 // see bug 5082. At least setup title and enabled state.
850 // Visibility will be adjusted by restoreState below.
851 dialog->prepareView();
852 if ((dialog = findOrBuild("view-source", true)))
853 dialog->prepareView();
854 if ((dialog = findOrBuild("progress", true)))
855 dialog->prepareView();
857 if (!restoreState(settings.value("layout").toByteArray(), 0))
860 // init the toolbars that have not been restored
861 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
862 Toolbars::Infos::iterator end = guiApp->toolbars().end();
863 for (; cit != end; ++cit) {
864 GuiToolbar * tb = toolbar(cit->name);
865 if (tb && !tb->isRestored())
866 initToolbar(cit->name);
869 // update lock (all) toolbars positions
870 updateLockToolbars();
877 GuiToolbar * GuiView::toolbar(string const & name)
879 ToolbarMap::iterator it = d.toolbars_.find(name);
880 if (it != d.toolbars_.end())
883 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
888 void GuiView::updateLockToolbars()
890 toolbarsMovable_ = false;
891 for (ToolbarInfo const & info : guiApp->toolbars()) {
892 GuiToolbar * tb = toolbar(info.name);
893 if (tb && tb->isMovable())
894 toolbarsMovable_ = true;
899 void GuiView::constructToolbars()
901 ToolbarMap::iterator it = d.toolbars_.begin();
902 for (; it != d.toolbars_.end(); ++it)
906 // I don't like doing this here, but the standard toolbar
907 // destroys this object when it's destroyed itself (vfr)
908 d.layout_ = new LayoutBox(*this);
909 d.stack_widget_->addWidget(d.layout_);
910 d.layout_->move(0,0);
912 // extracts the toolbars from the backend
913 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
914 Toolbars::Infos::iterator end = guiApp->toolbars().end();
915 for (; cit != end; ++cit)
916 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
920 void GuiView::initToolbars()
922 // extracts the toolbars from the backend
923 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
924 Toolbars::Infos::iterator end = guiApp->toolbars().end();
925 for (; cit != end; ++cit)
926 initToolbar(cit->name);
930 void GuiView::initToolbar(string const & name)
932 GuiToolbar * tb = toolbar(name);
935 int const visibility = guiApp->toolbars().defaultVisibility(name);
936 bool newline = !(visibility & Toolbars::SAMEROW);
937 tb->setVisible(false);
938 tb->setVisibility(visibility);
940 if (visibility & Toolbars::TOP) {
942 addToolBarBreak(Qt::TopToolBarArea);
943 addToolBar(Qt::TopToolBarArea, tb);
946 if (visibility & Toolbars::BOTTOM) {
948 addToolBarBreak(Qt::BottomToolBarArea);
949 addToolBar(Qt::BottomToolBarArea, tb);
952 if (visibility & Toolbars::LEFT) {
954 addToolBarBreak(Qt::LeftToolBarArea);
955 addToolBar(Qt::LeftToolBarArea, tb);
958 if (visibility & Toolbars::RIGHT) {
960 addToolBarBreak(Qt::RightToolBarArea);
961 addToolBar(Qt::RightToolBarArea, tb);
964 if (visibility & Toolbars::ON)
965 tb->setVisible(true);
967 tb->setMovable(true);
971 TocModels & GuiView::tocModels()
973 return d.toc_models_;
977 void GuiView::setFocus()
979 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
980 QMainWindow::setFocus();
984 bool GuiView::hasFocus() const
986 if (currentWorkArea())
987 return currentWorkArea()->hasFocus();
988 if (currentMainWorkArea())
989 return currentMainWorkArea()->hasFocus();
990 return d.bg_widget_->hasFocus();
994 void GuiView::focusInEvent(QFocusEvent * e)
996 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
997 QMainWindow::focusInEvent(e);
998 // Make sure guiApp points to the correct view.
999 guiApp->setCurrentView(this);
1000 if (currentWorkArea())
1001 currentWorkArea()->setFocus();
1002 else if (currentMainWorkArea())
1003 currentMainWorkArea()->setFocus();
1005 d.bg_widget_->setFocus();
1009 void GuiView::showEvent(QShowEvent * e)
1011 LYXERR(Debug::GUI, "Passed Geometry "
1012 << size().height() << "x" << size().width()
1013 << "+" << pos().x() << "+" << pos().y());
1015 if (d.splitter_->count() == 0)
1016 // No work area, switch to the background widget.
1020 QMainWindow::showEvent(e);
1024 bool GuiView::closeScheduled()
1031 bool GuiView::prepareAllBuffersForLogout()
1033 Buffer * first = theBufferList().first();
1037 // First, iterate over all buffers and ask the users if unsaved
1038 // changes should be saved.
1039 // We cannot use a for loop as the buffer list cycles.
1042 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1044 b = theBufferList().next(b);
1045 } while (b != first);
1047 // Next, save session state
1048 // When a view/window was closed before without quitting LyX, there
1049 // are already entries in the lastOpened list.
1050 theSession().lastOpened().clear();
1057 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1058 ** is responsibility of the container (e.g., dialog)
1060 void GuiView::closeEvent(QCloseEvent * close_event)
1062 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1064 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1065 Alert::warning(_("Exit LyX"),
1066 _("LyX could not be closed because documents are being processed by LyX."));
1067 close_event->setAccepted(false);
1071 // If the user pressed the x (so we didn't call closeView
1072 // programmatically), we want to clear all existing entries.
1074 theSession().lastOpened().clear();
1079 // it can happen that this event arrives without selecting the view,
1080 // e.g. when clicking the close button on a background window.
1082 if (!closeWorkAreaAll()) {
1084 close_event->ignore();
1088 // Make sure that nothing will use this to be closed View.
1089 guiApp->unregisterView(this);
1091 if (isFullScreen()) {
1092 // Switch off fullscreen before closing.
1097 // Make sure the timer time out will not trigger a statusbar update.
1098 d.statusbar_timer_.stop();
1100 // Saving fullscreen requires additional tweaks in the toolbar code.
1101 // It wouldn't also work under linux natively.
1102 if (lyxrc.allow_geometry_session) {
1107 close_event->accept();
1111 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1113 if (event->mimeData()->hasUrls())
1115 /// \todo Ask lyx-devel is this is enough:
1116 /// if (event->mimeData()->hasFormat("text/plain"))
1117 /// event->acceptProposedAction();
1121 void GuiView::dropEvent(QDropEvent * event)
1123 QList<QUrl> files = event->mimeData()->urls();
1124 if (files.isEmpty())
1127 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1128 for (int i = 0; i != files.size(); ++i) {
1129 string const file = os::internal_path(fromqstr(
1130 files.at(i).toLocalFile()));
1134 string const ext = support::getExtension(file);
1135 vector<const Format *> found_formats;
1137 // Find all formats that have the correct extension.
1138 vector<const Format *> const & import_formats
1139 = theConverters().importableFormats();
1140 vector<const Format *>::const_iterator it = import_formats.begin();
1141 for (; it != import_formats.end(); ++it)
1142 if ((*it)->hasExtension(ext))
1143 found_formats.push_back(*it);
1146 if (found_formats.size() >= 1) {
1147 if (found_formats.size() > 1) {
1148 //FIXME: show a dialog to choose the correct importable format
1149 LYXERR(Debug::FILES,
1150 "Multiple importable formats found, selecting first");
1152 string const arg = found_formats[0]->name() + " " + file;
1153 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1156 //FIXME: do we have to explicitly check whether it's a lyx file?
1157 LYXERR(Debug::FILES,
1158 "No formats found, trying to open it as a lyx file");
1159 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1161 // add the functions to the queue
1162 guiApp->addToFuncRequestQueue(cmd);
1165 // now process the collected functions. We perform the events
1166 // asynchronously. This prevents potential problems in case the
1167 // BufferView is closed within an event.
1168 guiApp->processFuncRequestQueueAsync();
1172 void GuiView::message(docstring const & str)
1174 if (ForkedProcess::iAmAChild())
1177 // call is moved to GUI-thread by GuiProgress
1178 d.progress_->appendMessage(toqstr(str));
1182 void GuiView::clearMessageText()
1184 message(docstring());
1188 void GuiView::updateStatusBarMessage(QString const & str)
1190 statusBar()->showMessage(str);
1191 d.statusbar_timer_.stop();
1192 d.statusbar_timer_.start(3000);
1196 void GuiView::clearMessage()
1198 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1199 // the hasFocus function mostly returns false, even if the focus is on
1200 // a workarea in this view.
1204 d.statusbar_timer_.stop();
1208 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1210 if (wa != d.current_work_area_
1211 || wa->bufferView().buffer().isInternal())
1213 Buffer const & buf = wa->bufferView().buffer();
1214 // Set the windows title
1215 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1216 if (buf.notifiesExternalModification()) {
1217 title = bformat(_("%1$s (modified externally)"), title);
1218 // If the external modification status has changed, then maybe the status of
1219 // buffer-save has changed too.
1223 title += from_ascii(" - LyX");
1225 setWindowTitle(toqstr(title));
1226 // Sets the path for the window: this is used by OSX to
1227 // allow a context click on the title bar showing a menu
1228 // with the path up to the file
1229 setWindowFilePath(toqstr(buf.absFileName()));
1230 // Tell Qt whether the current document is changed
1231 setWindowModified(!buf.isClean());
1233 if (buf.params().shell_escape)
1234 shell_escape_->show();
1236 shell_escape_->hide();
1238 if (buf.hasReadonlyFlag())
1243 if (buf.lyxvc().inUse()) {
1244 version_control_->show();
1245 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1247 version_control_->hide();
1251 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1253 if (d.current_work_area_)
1254 // disconnect the current work area from all slots
1255 QObject::disconnect(d.current_work_area_, 0, this, 0);
1257 disconnectBufferView();
1258 connectBufferView(wa->bufferView());
1259 connectBuffer(wa->bufferView().buffer());
1260 d.current_work_area_ = wa;
1261 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1262 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1263 QObject::connect(wa, SIGNAL(busy(bool)),
1264 this, SLOT(setBusy(bool)));
1265 // connection of a signal to a signal
1266 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1267 this, SIGNAL(bufferViewChanged()));
1268 Q_EMIT updateWindowTitle(wa);
1269 Q_EMIT bufferViewChanged();
1273 void GuiView::onBufferViewChanged()
1276 // Buffer-dependent dialogs must be updated. This is done here because
1277 // some dialogs require buffer()->text.
1282 void GuiView::on_lastWorkAreaRemoved()
1285 // We already are in a close event. Nothing more to do.
1288 if (d.splitter_->count() > 1)
1289 // We have a splitter so don't close anything.
1292 // Reset and updates the dialogs.
1293 Q_EMIT bufferViewChanged();
1298 if (lyxrc.open_buffers_in_tabs)
1299 // Nothing more to do, the window should stay open.
1302 if (guiApp->viewIds().size() > 1) {
1308 // On Mac we also close the last window because the application stay
1309 // resident in memory. On other platforms we don't close the last
1310 // window because this would quit the application.
1316 void GuiView::updateStatusBar()
1318 // let the user see the explicit message
1319 if (d.statusbar_timer_.isActive())
1326 void GuiView::showMessage()
1330 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1331 if (msg.isEmpty()) {
1332 BufferView const * bv = currentBufferView();
1334 msg = toqstr(bv->cursor().currentState(devel_mode_));
1336 msg = qt_("Welcome to LyX!");
1338 statusBar()->showMessage(msg);
1342 bool GuiView::event(QEvent * e)
1346 // Useful debug code:
1347 //case QEvent::ActivationChange:
1348 //case QEvent::WindowDeactivate:
1349 //case QEvent::Paint:
1350 //case QEvent::Enter:
1351 //case QEvent::Leave:
1352 //case QEvent::HoverEnter:
1353 //case QEvent::HoverLeave:
1354 //case QEvent::HoverMove:
1355 //case QEvent::StatusTip:
1356 //case QEvent::DragEnter:
1357 //case QEvent::DragLeave:
1358 //case QEvent::Drop:
1361 case QEvent::WindowActivate: {
1362 GuiView * old_view = guiApp->currentView();
1363 if (this == old_view) {
1365 return QMainWindow::event(e);
1367 if (old_view && old_view->currentBufferView()) {
1368 // save current selection to the selection buffer to allow
1369 // middle-button paste in this window.
1370 cap::saveSelection(old_view->currentBufferView()->cursor());
1372 guiApp->setCurrentView(this);
1373 if (d.current_work_area_)
1374 on_currentWorkAreaChanged(d.current_work_area_);
1378 return QMainWindow::event(e);
1381 case QEvent::ShortcutOverride: {
1383 if (isFullScreen() && menuBar()->isHidden()) {
1384 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1385 // FIXME: we should also try to detect special LyX shortcut such as
1386 // Alt-P and Alt-M. Right now there is a hack in
1387 // GuiWorkArea::processKeySym() that hides again the menubar for
1389 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1391 return QMainWindow::event(e);
1394 return QMainWindow::event(e);
1398 return QMainWindow::event(e);
1402 void GuiView::resetWindowTitle()
1404 setWindowTitle(qt_("LyX"));
1407 bool GuiView::focusNextPrevChild(bool /*next*/)
1414 bool GuiView::busy() const
1420 void GuiView::setBusy(bool busy)
1422 bool const busy_before = busy_ > 0;
1423 busy ? ++busy_ : --busy_;
1424 if ((busy_ > 0) == busy_before)
1425 // busy state didn't change
1429 QApplication::setOverrideCursor(Qt::WaitCursor);
1432 QApplication::restoreOverrideCursor();
1437 void GuiView::resetCommandExecute()
1439 command_execute_ = false;
1444 double GuiView::pixelRatio() const
1446 #if QT_VERSION >= 0x050000
1447 return qt_scale_factor * devicePixelRatio();
1454 GuiWorkArea * GuiView::workArea(int index)
1456 if (TabWorkArea * twa = d.currentTabWorkArea())
1457 if (index < twa->count())
1458 return twa->workArea(index);
1463 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1465 if (currentWorkArea()
1466 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1467 return currentWorkArea();
1468 if (TabWorkArea * twa = d.currentTabWorkArea())
1469 return twa->workArea(buffer);
1474 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1476 // Automatically create a TabWorkArea if there are none yet.
1477 TabWorkArea * tab_widget = d.splitter_->count()
1478 ? d.currentTabWorkArea() : addTabWorkArea();
1479 return tab_widget->addWorkArea(buffer, *this);
1483 TabWorkArea * GuiView::addTabWorkArea()
1485 TabWorkArea * twa = new TabWorkArea;
1486 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1487 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1488 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1489 this, SLOT(on_lastWorkAreaRemoved()));
1491 d.splitter_->addWidget(twa);
1492 d.stack_widget_->setCurrentWidget(d.splitter_);
1497 GuiWorkArea const * GuiView::currentWorkArea() const
1499 return d.current_work_area_;
1503 GuiWorkArea * GuiView::currentWorkArea()
1505 return d.current_work_area_;
1509 GuiWorkArea const * GuiView::currentMainWorkArea() const
1511 if (!d.currentTabWorkArea())
1513 return d.currentTabWorkArea()->currentWorkArea();
1517 GuiWorkArea * GuiView::currentMainWorkArea()
1519 if (!d.currentTabWorkArea())
1521 return d.currentTabWorkArea()->currentWorkArea();
1525 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1527 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1529 d.current_work_area_ = 0;
1531 Q_EMIT bufferViewChanged();
1535 // FIXME: I've no clue why this is here and why it accesses
1536 // theGuiApp()->currentView, which might be 0 (bug 6464).
1537 // See also 27525 (vfr).
1538 if (theGuiApp()->currentView() == this
1539 && theGuiApp()->currentView()->currentWorkArea() == wa)
1542 if (currentBufferView())
1543 cap::saveSelection(currentBufferView()->cursor());
1545 theGuiApp()->setCurrentView(this);
1546 d.current_work_area_ = wa;
1548 // We need to reset this now, because it will need to be
1549 // right if the tabWorkArea gets reset in the for loop. We
1550 // will change it back if we aren't in that case.
1551 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1552 d.current_main_work_area_ = wa;
1554 for (int i = 0; i != d.splitter_->count(); ++i) {
1555 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1556 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1557 << ", Current main wa: " << currentMainWorkArea());
1562 d.current_main_work_area_ = old_cmwa;
1564 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1565 on_currentWorkAreaChanged(wa);
1566 BufferView & bv = wa->bufferView();
1567 bv.cursor().fixIfBroken();
1569 wa->setUpdatesEnabled(true);
1570 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1574 void GuiView::removeWorkArea(GuiWorkArea * wa)
1576 LASSERT(wa, return);
1577 if (wa == d.current_work_area_) {
1579 disconnectBufferView();
1580 d.current_work_area_ = 0;
1581 d.current_main_work_area_ = 0;
1584 bool found_twa = false;
1585 for (int i = 0; i != d.splitter_->count(); ++i) {
1586 TabWorkArea * twa = d.tabWorkArea(i);
1587 if (twa->removeWorkArea(wa)) {
1588 // Found in this tab group, and deleted the GuiWorkArea.
1590 if (twa->count() != 0) {
1591 if (d.current_work_area_ == 0)
1592 // This means that we are closing the current GuiWorkArea, so
1593 // switch to the next GuiWorkArea in the found TabWorkArea.
1594 setCurrentWorkArea(twa->currentWorkArea());
1596 // No more WorkAreas in this tab group, so delete it.
1603 // It is not a tabbed work area (i.e., the search work area), so it
1604 // should be deleted by other means.
1605 LASSERT(found_twa, return);
1607 if (d.current_work_area_ == 0) {
1608 if (d.splitter_->count() != 0) {
1609 TabWorkArea * twa = d.currentTabWorkArea();
1610 setCurrentWorkArea(twa->currentWorkArea());
1612 // No more work areas, switch to the background widget.
1613 setCurrentWorkArea(0);
1619 LayoutBox * GuiView::getLayoutDialog() const
1625 void GuiView::updateLayoutList()
1628 d.layout_->updateContents(false);
1632 void GuiView::updateToolbars()
1634 ToolbarMap::iterator end = d.toolbars_.end();
1635 if (d.current_work_area_) {
1637 if (d.current_work_area_->bufferView().cursor().inMathed()
1638 && !d.current_work_area_->bufferView().cursor().inRegexped())
1639 context |= Toolbars::MATH;
1640 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1641 context |= Toolbars::TABLE;
1642 if (currentBufferView()->buffer().areChangesPresent()
1643 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1644 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1645 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1646 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1647 context |= Toolbars::REVIEW;
1648 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1649 context |= Toolbars::MATHMACROTEMPLATE;
1650 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1651 context |= Toolbars::IPA;
1652 if (command_execute_)
1653 context |= Toolbars::MINIBUFFER;
1654 if (minibuffer_focus_) {
1655 context |= Toolbars::MINIBUFFER_FOCUS;
1656 minibuffer_focus_ = false;
1659 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1660 it->second->update(context);
1662 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1663 it->second->update();
1667 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1669 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1670 LASSERT(newBuffer, return);
1672 GuiWorkArea * wa = workArea(*newBuffer);
1675 newBuffer->masterBuffer()->updateBuffer();
1677 wa = addWorkArea(*newBuffer);
1678 // scroll to the position when the BufferView was last closed
1679 if (lyxrc.use_lastfilepos) {
1680 LastFilePosSection::FilePos filepos =
1681 theSession().lastFilePos().load(newBuffer->fileName());
1682 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1685 //Disconnect the old buffer...there's no new one.
1688 connectBuffer(*newBuffer);
1689 connectBufferView(wa->bufferView());
1691 setCurrentWorkArea(wa);
1695 void GuiView::connectBuffer(Buffer & buf)
1697 buf.setGuiDelegate(this);
1701 void GuiView::disconnectBuffer()
1703 if (d.current_work_area_)
1704 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1708 void GuiView::connectBufferView(BufferView & bv)
1710 bv.setGuiDelegate(this);
1714 void GuiView::disconnectBufferView()
1716 if (d.current_work_area_)
1717 d.current_work_area_->bufferView().setGuiDelegate(0);
1721 void GuiView::errors(string const & error_type, bool from_master)
1723 BufferView const * const bv = currentBufferView();
1727 ErrorList const & el = from_master ?
1728 bv->buffer().masterBuffer()->errorList(error_type) :
1729 bv->buffer().errorList(error_type);
1734 string err = error_type;
1736 err = "from_master|" + error_type;
1737 showDialog("errorlist", err);
1741 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1743 d.toc_models_.updateItem(toqstr(type), dit);
1747 void GuiView::structureChanged()
1749 // This is called from the Buffer, which has no way to ensure that cursors
1750 // in BufferView remain valid.
1751 if (documentBufferView())
1752 documentBufferView()->cursor().sanitize();
1753 // FIXME: This is slightly expensive, though less than the tocBackend update
1754 // (#9880). This also resets the view in the Toc Widget (#6675).
1755 d.toc_models_.reset(documentBufferView());
1756 // Navigator needs more than a simple update in this case. It needs to be
1758 updateDialog("toc", "");
1762 void GuiView::updateDialog(string const & name, string const & sdata)
1764 if (!isDialogVisible(name))
1767 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1768 if (it == d.dialogs_.end())
1771 Dialog * const dialog = it->second.get();
1772 if (dialog->isVisibleView())
1773 dialog->initialiseParams(sdata);
1777 BufferView * GuiView::documentBufferView()
1779 return currentMainWorkArea()
1780 ? ¤tMainWorkArea()->bufferView()
1785 BufferView const * GuiView::documentBufferView() const
1787 return currentMainWorkArea()
1788 ? ¤tMainWorkArea()->bufferView()
1793 BufferView * GuiView::currentBufferView()
1795 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1799 BufferView const * GuiView::currentBufferView() const
1801 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1805 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1806 Buffer const * orig, Buffer * clone)
1808 bool const success = clone->autoSave();
1810 busyBuffers.remove(orig);
1812 ? _("Automatic save done.")
1813 : _("Automatic save failed!");
1817 void GuiView::autoSave()
1819 LYXERR(Debug::INFO, "Running autoSave()");
1821 Buffer * buffer = documentBufferView()
1822 ? &documentBufferView()->buffer() : 0;
1824 resetAutosaveTimers();
1828 GuiViewPrivate::busyBuffers.insert(buffer);
1829 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1830 buffer, buffer->cloneBufferOnly());
1831 d.autosave_watcher_.setFuture(f);
1832 resetAutosaveTimers();
1836 void GuiView::resetAutosaveTimers()
1839 d.autosave_timeout_.restart();
1843 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1846 Buffer * buf = currentBufferView()
1847 ? ¤tBufferView()->buffer() : 0;
1848 Buffer * doc_buffer = documentBufferView()
1849 ? &(documentBufferView()->buffer()) : 0;
1852 /* In LyX/Mac, when a dialog is open, the menus of the
1853 application can still be accessed without giving focus to
1854 the main window. In this case, we want to disable the menu
1855 entries that are buffer-related.
1856 This code must not be used on Linux and Windows, since it
1857 would disable buffer-related entries when hovering over the
1858 menu (see bug #9574).
1860 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1866 // Check whether we need a buffer
1867 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1868 // no, exit directly
1869 flag.message(from_utf8(N_("Command not allowed with"
1870 "out any document open")));
1871 flag.setEnabled(false);
1875 if (cmd.origin() == FuncRequest::TOC) {
1876 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1877 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1878 flag.setEnabled(false);
1882 switch(cmd.action()) {
1883 case LFUN_BUFFER_IMPORT:
1886 case LFUN_MASTER_BUFFER_EXPORT:
1888 && (doc_buffer->parent() != 0
1889 || doc_buffer->hasChildren())
1890 && !d.processing_thread_watcher_.isRunning()
1891 // this launches a dialog, which would be in the wrong Buffer
1892 && !(::lyx::operator==(cmd.argument(), "custom"));
1895 case LFUN_MASTER_BUFFER_UPDATE:
1896 case LFUN_MASTER_BUFFER_VIEW:
1898 && (doc_buffer->parent() != 0
1899 || doc_buffer->hasChildren())
1900 && !d.processing_thread_watcher_.isRunning();
1903 case LFUN_BUFFER_UPDATE:
1904 case LFUN_BUFFER_VIEW: {
1905 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1909 string format = to_utf8(cmd.argument());
1910 if (cmd.argument().empty())
1911 format = doc_buffer->params().getDefaultOutputFormat();
1912 enable = doc_buffer->params().isExportable(format, true);
1916 case LFUN_BUFFER_RELOAD:
1917 enable = doc_buffer && !doc_buffer->isUnnamed()
1918 && doc_buffer->fileName().exists()
1919 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1922 case LFUN_BUFFER_CHILD_OPEN:
1923 enable = doc_buffer != 0;
1926 case LFUN_BUFFER_WRITE:
1927 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1930 //FIXME: This LFUN should be moved to GuiApplication.
1931 case LFUN_BUFFER_WRITE_ALL: {
1932 // We enable the command only if there are some modified buffers
1933 Buffer * first = theBufferList().first();
1938 // We cannot use a for loop as the buffer list is a cycle.
1940 if (!b->isClean()) {
1944 b = theBufferList().next(b);
1945 } while (b != first);
1949 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1950 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1953 case LFUN_BUFFER_EXPORT: {
1954 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1958 return doc_buffer->getStatus(cmd, flag);
1962 case LFUN_BUFFER_EXPORT_AS:
1963 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1968 case LFUN_BUFFER_WRITE_AS:
1969 enable = doc_buffer != 0;
1972 case LFUN_EXPORT_CANCEL:
1973 enable = d.processing_thread_watcher_.isRunning();
1976 case LFUN_BUFFER_CLOSE:
1977 case LFUN_VIEW_CLOSE:
1978 enable = doc_buffer != 0;
1981 case LFUN_BUFFER_CLOSE_ALL:
1982 enable = theBufferList().last() != theBufferList().first();
1985 case LFUN_BUFFER_CHKTEX: {
1986 // hide if we have no checktex command
1987 if (lyxrc.chktex_command.empty()) {
1988 flag.setUnknown(true);
1992 if (!doc_buffer || !doc_buffer->params().isLatex()
1993 || d.processing_thread_watcher_.isRunning()) {
1994 // grey out, don't hide
2002 case LFUN_VIEW_SPLIT:
2003 if (cmd.getArg(0) == "vertical")
2004 enable = doc_buffer && (d.splitter_->count() == 1 ||
2005 d.splitter_->orientation() == Qt::Vertical);
2007 enable = doc_buffer && (d.splitter_->count() == 1 ||
2008 d.splitter_->orientation() == Qt::Horizontal);
2011 case LFUN_TAB_GROUP_CLOSE:
2012 enable = d.tabWorkAreaCount() > 1;
2015 case LFUN_DEVEL_MODE_TOGGLE:
2016 flag.setOnOff(devel_mode_);
2019 case LFUN_TOOLBAR_TOGGLE: {
2020 string const name = cmd.getArg(0);
2021 if (GuiToolbar * t = toolbar(name))
2022 flag.setOnOff(t->isVisible());
2025 docstring const msg =
2026 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2032 case LFUN_TOOLBAR_MOVABLE: {
2033 string const name = cmd.getArg(0);
2034 // use negation since locked == !movable
2036 // toolbar name * locks all toolbars
2037 flag.setOnOff(!toolbarsMovable_);
2038 else if (GuiToolbar * t = toolbar(name))
2039 flag.setOnOff(!(t->isMovable()));
2042 docstring const msg =
2043 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2049 case LFUN_ICON_SIZE:
2050 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2053 case LFUN_DROP_LAYOUTS_CHOICE:
2057 case LFUN_UI_TOGGLE:
2058 flag.setOnOff(isFullScreen());
2061 case LFUN_DIALOG_DISCONNECT_INSET:
2064 case LFUN_DIALOG_HIDE:
2065 // FIXME: should we check if the dialog is shown?
2068 case LFUN_DIALOG_TOGGLE:
2069 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2072 case LFUN_DIALOG_SHOW: {
2073 string const name = cmd.getArg(0);
2075 enable = name == "aboutlyx"
2076 || name == "file" //FIXME: should be removed.
2078 || name == "texinfo"
2079 || name == "progress"
2080 || name == "compare";
2081 else if (name == "character" || name == "symbols"
2082 || name == "mathdelimiter" || name == "mathmatrix") {
2083 if (!buf || buf->isReadonly())
2086 Cursor const & cur = currentBufferView()->cursor();
2087 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2090 else if (name == "latexlog")
2091 enable = FileName(doc_buffer->logName()).isReadableFile();
2092 else if (name == "spellchecker")
2093 enable = theSpellChecker()
2094 && !doc_buffer->isReadonly()
2095 && !doc_buffer->text().empty();
2096 else if (name == "vclog")
2097 enable = doc_buffer->lyxvc().inUse();
2101 case LFUN_DIALOG_UPDATE: {
2102 string const name = cmd.getArg(0);
2104 enable = name == "prefs";
2108 case LFUN_COMMAND_EXECUTE:
2110 case LFUN_MENU_OPEN:
2111 // Nothing to check.
2114 case LFUN_COMPLETION_INLINE:
2115 if (!d.current_work_area_
2116 || !d.current_work_area_->completer().inlinePossible(
2117 currentBufferView()->cursor()))
2121 case LFUN_COMPLETION_POPUP:
2122 if (!d.current_work_area_
2123 || !d.current_work_area_->completer().popupPossible(
2124 currentBufferView()->cursor()))
2129 if (!d.current_work_area_
2130 || !d.current_work_area_->completer().inlinePossible(
2131 currentBufferView()->cursor()))
2135 case LFUN_COMPLETION_ACCEPT:
2136 if (!d.current_work_area_
2137 || (!d.current_work_area_->completer().popupVisible()
2138 && !d.current_work_area_->completer().inlineVisible()
2139 && !d.current_work_area_->completer().completionAvailable()))
2143 case LFUN_COMPLETION_CANCEL:
2144 if (!d.current_work_area_
2145 || (!d.current_work_area_->completer().popupVisible()
2146 && !d.current_work_area_->completer().inlineVisible()))
2150 case LFUN_BUFFER_ZOOM_OUT:
2151 case LFUN_BUFFER_ZOOM_IN: {
2152 // only diff between these two is that the default for ZOOM_OUT
2154 bool const neg_zoom =
2155 convert<int>(cmd.argument()) < 0 ||
2156 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2157 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2158 docstring const msg =
2159 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2163 enable = doc_buffer;
2167 case LFUN_BUFFER_ZOOM: {
2168 bool const less_than_min_zoom =
2169 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2170 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2171 docstring const msg =
2172 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2177 enable = doc_buffer;
2181 case LFUN_BUFFER_MOVE_NEXT:
2182 case LFUN_BUFFER_MOVE_PREVIOUS:
2183 // we do not cycle when moving
2184 case LFUN_BUFFER_NEXT:
2185 case LFUN_BUFFER_PREVIOUS:
2186 // because we cycle, it doesn't matter whether on first or last
2187 enable = (d.currentTabWorkArea()->count() > 1);
2189 case LFUN_BUFFER_SWITCH:
2190 // toggle on the current buffer, but do not toggle off
2191 // the other ones (is that a good idea?)
2193 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2194 flag.setOnOff(true);
2197 case LFUN_VC_REGISTER:
2198 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2200 case LFUN_VC_RENAME:
2201 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2204 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2206 case LFUN_VC_CHECK_IN:
2207 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2209 case LFUN_VC_CHECK_OUT:
2210 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2212 case LFUN_VC_LOCKING_TOGGLE:
2213 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2214 && doc_buffer->lyxvc().lockingToggleEnabled();
2215 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2217 case LFUN_VC_REVERT:
2218 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2219 && !doc_buffer->hasReadonlyFlag();
2221 case LFUN_VC_UNDO_LAST:
2222 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2224 case LFUN_VC_REPO_UPDATE:
2225 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2227 case LFUN_VC_COMMAND: {
2228 if (cmd.argument().empty())
2230 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2234 case LFUN_VC_COMPARE:
2235 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2238 case LFUN_SERVER_GOTO_FILE_ROW:
2239 case LFUN_LYX_ACTIVATE:
2241 case LFUN_FORWARD_SEARCH:
2242 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2245 case LFUN_FILE_INSERT_PLAINTEXT:
2246 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2247 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2250 case LFUN_SPELLING_CONTINUOUSLY:
2251 flag.setOnOff(lyxrc.spellcheck_continuously);
2259 flag.setEnabled(false);
2265 static FileName selectTemplateFile()
2267 FileDialog dlg(qt_("Select template file"));
2268 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2269 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2271 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2272 QStringList(qt_("LyX Documents (*.lyx)")));
2274 if (result.first == FileDialog::Later)
2276 if (result.second.isEmpty())
2278 return FileName(fromqstr(result.second));
2282 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2286 Buffer * newBuffer = 0;
2288 newBuffer = checkAndLoadLyXFile(filename);
2289 } catch (ExceptionMessage const & e) {
2296 message(_("Document not loaded."));
2300 setBuffer(newBuffer);
2301 newBuffer->errors("Parse");
2304 theSession().lastFiles().add(filename);
2305 theSession().writeFile();
2312 void GuiView::openDocument(string const & fname)
2314 string initpath = lyxrc.document_path;
2316 if (documentBufferView()) {
2317 string const trypath = documentBufferView()->buffer().filePath();
2318 // If directory is writeable, use this as default.
2319 if (FileName(trypath).isDirWritable())
2325 if (fname.empty()) {
2326 FileDialog dlg(qt_("Select document to open"));
2327 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2328 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2330 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2331 FileDialog::Result result =
2332 dlg.open(toqstr(initpath), filter);
2334 if (result.first == FileDialog::Later)
2337 filename = fromqstr(result.second);
2339 // check selected filename
2340 if (filename.empty()) {
2341 message(_("Canceled."));
2347 // get absolute path of file and add ".lyx" to the filename if
2349 FileName const fullname =
2350 fileSearch(string(), filename, "lyx", support::may_not_exist);
2351 if (!fullname.empty())
2352 filename = fullname.absFileName();
2354 if (!fullname.onlyPath().isDirectory()) {
2355 Alert::warning(_("Invalid filename"),
2356 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2357 from_utf8(fullname.absFileName())));
2361 // if the file doesn't exist and isn't already open (bug 6645),
2362 // let the user create one
2363 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2364 !LyXVC::file_not_found_hook(fullname)) {
2365 // the user specifically chose this name. Believe him.
2366 Buffer * const b = newFile(filename, string(), true);
2372 docstring const disp_fn = makeDisplayPath(filename);
2373 message(bformat(_("Opening document %1$s..."), disp_fn));
2376 Buffer * buf = loadDocument(fullname);
2378 str2 = bformat(_("Document %1$s opened."), disp_fn);
2379 if (buf->lyxvc().inUse())
2380 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2381 " " + _("Version control detected.");
2383 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2388 // FIXME: clean that
2389 static bool import(GuiView * lv, FileName const & filename,
2390 string const & format, ErrorList & errorList)
2392 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2394 string loader_format;
2395 vector<string> loaders = theConverters().loaders();
2396 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2397 vector<string>::const_iterator it = loaders.begin();
2398 vector<string>::const_iterator en = loaders.end();
2399 for (; it != en; ++it) {
2400 if (!theConverters().isReachable(format, *it))
2403 string const tofile =
2404 support::changeExtension(filename.absFileName(),
2405 theFormats().extension(*it));
2406 if (theConverters().convert(0, filename, FileName(tofile),
2407 filename, format, *it, errorList) != Converters::SUCCESS)
2409 loader_format = *it;
2412 if (loader_format.empty()) {
2413 frontend::Alert::error(_("Couldn't import file"),
2414 bformat(_("No information for importing the format %1$s."),
2415 theFormats().prettyName(format)));
2419 loader_format = format;
2421 if (loader_format == "lyx") {
2422 Buffer * buf = lv->loadDocument(lyxfile);
2426 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2430 bool as_paragraphs = loader_format == "textparagraph";
2431 string filename2 = (loader_format == format) ? filename.absFileName()
2432 : support::changeExtension(filename.absFileName(),
2433 theFormats().extension(loader_format));
2434 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2436 guiApp->setCurrentView(lv);
2437 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2444 void GuiView::importDocument(string const & argument)
2447 string filename = split(argument, format, ' ');
2449 LYXERR(Debug::INFO, format << " file: " << filename);
2451 // need user interaction
2452 if (filename.empty()) {
2453 string initpath = lyxrc.document_path;
2454 if (documentBufferView()) {
2455 string const trypath = documentBufferView()->buffer().filePath();
2456 // If directory is writeable, use this as default.
2457 if (FileName(trypath).isDirWritable())
2461 docstring const text = bformat(_("Select %1$s file to import"),
2462 theFormats().prettyName(format));
2464 FileDialog dlg(toqstr(text));
2465 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2466 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2468 docstring filter = theFormats().prettyName(format);
2471 filter += from_utf8(theFormats().extensions(format));
2474 FileDialog::Result result =
2475 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2477 if (result.first == FileDialog::Later)
2480 filename = fromqstr(result.second);
2482 // check selected filename
2483 if (filename.empty())
2484 message(_("Canceled."));
2487 if (filename.empty())
2490 // get absolute path of file
2491 FileName const fullname(support::makeAbsPath(filename));
2493 // Can happen if the user entered a path into the dialog
2495 if (fullname.onlyFileName().empty()) {
2496 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2497 "Aborting import."),
2498 from_utf8(fullname.absFileName()));
2499 frontend::Alert::error(_("File name error"), msg);
2500 message(_("Canceled."));
2505 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2507 // Check if the document already is open
2508 Buffer * buf = theBufferList().getBuffer(lyxfile);
2511 if (!closeBuffer()) {
2512 message(_("Canceled."));
2517 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2519 // if the file exists already, and we didn't do
2520 // -i lyx thefile.lyx, warn
2521 if (lyxfile.exists() && fullname != lyxfile) {
2523 docstring text = bformat(_("The document %1$s already exists.\n\n"
2524 "Do you want to overwrite that document?"), displaypath);
2525 int const ret = Alert::prompt(_("Overwrite document?"),
2526 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2529 message(_("Canceled."));
2534 message(bformat(_("Importing %1$s..."), displaypath));
2535 ErrorList errorList;
2536 if (import(this, fullname, format, errorList))
2537 message(_("imported."));
2539 message(_("file not imported!"));
2541 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2545 void GuiView::newDocument(string const & filename, bool from_template)
2547 FileName initpath(lyxrc.document_path);
2548 if (documentBufferView()) {
2549 FileName const trypath(documentBufferView()->buffer().filePath());
2550 // If directory is writeable, use this as default.
2551 if (trypath.isDirWritable())
2555 string templatefile;
2556 if (from_template) {
2557 templatefile = selectTemplateFile().absFileName();
2558 if (templatefile.empty())
2563 if (filename.empty())
2564 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2566 b = newFile(filename, templatefile, true);
2571 // If no new document could be created, it is unsure
2572 // whether there is a valid BufferView.
2573 if (currentBufferView())
2574 // Ensure the cursor is correctly positioned on screen.
2575 currentBufferView()->showCursor();
2579 void GuiView::insertLyXFile(docstring const & fname)
2581 BufferView * bv = documentBufferView();
2586 FileName filename(to_utf8(fname));
2587 if (filename.empty()) {
2588 // Launch a file browser
2590 string initpath = lyxrc.document_path;
2591 string const trypath = bv->buffer().filePath();
2592 // If directory is writeable, use this as default.
2593 if (FileName(trypath).isDirWritable())
2597 FileDialog dlg(qt_("Select LyX document to insert"));
2598 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2599 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2601 FileDialog::Result result = dlg.open(toqstr(initpath),
2602 QStringList(qt_("LyX Documents (*.lyx)")));
2604 if (result.first == FileDialog::Later)
2608 filename.set(fromqstr(result.second));
2610 // check selected filename
2611 if (filename.empty()) {
2612 // emit message signal.
2613 message(_("Canceled."));
2618 bv->insertLyXFile(filename);
2619 bv->buffer().errors("Parse");
2623 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2625 FileName fname = b.fileName();
2626 FileName const oldname = fname;
2628 if (!newname.empty()) {
2630 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2632 // Switch to this Buffer.
2635 // No argument? Ask user through dialog.
2637 FileDialog dlg(qt_("Choose a filename to save document as"));
2638 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2639 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2641 if (!isLyXFileName(fname.absFileName()))
2642 fname.changeExtension(".lyx");
2644 FileDialog::Result result =
2645 dlg.save(toqstr(fname.onlyPath().absFileName()),
2646 QStringList(qt_("LyX Documents (*.lyx)")),
2647 toqstr(fname.onlyFileName()));
2649 if (result.first == FileDialog::Later)
2652 fname.set(fromqstr(result.second));
2657 if (!isLyXFileName(fname.absFileName()))
2658 fname.changeExtension(".lyx");
2661 // fname is now the new Buffer location.
2663 // if there is already a Buffer open with this name, we do not want
2664 // to have another one. (the second test makes sure we're not just
2665 // trying to overwrite ourselves, which is fine.)
2666 if (theBufferList().exists(fname) && fname != oldname
2667 && theBufferList().getBuffer(fname) != &b) {
2668 docstring const text =
2669 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2670 "Please close it before attempting to overwrite it.\n"
2671 "Do you want to choose a new filename?"),
2672 from_utf8(fname.absFileName()));
2673 int const ret = Alert::prompt(_("Chosen File Already Open"),
2674 text, 0, 1, _("&Rename"), _("&Cancel"));
2676 case 0: return renameBuffer(b, docstring(), kind);
2677 case 1: return false;
2682 bool const existsLocal = fname.exists();
2683 bool const existsInVC = LyXVC::fileInVC(fname);
2684 if (existsLocal || existsInVC) {
2685 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2686 if (kind != LV_WRITE_AS && existsInVC) {
2687 // renaming to a name that is already in VC
2689 docstring text = bformat(_("The document %1$s "
2690 "is already registered.\n\n"
2691 "Do you want to choose a new name?"),
2693 docstring const title = (kind == LV_VC_RENAME) ?
2694 _("Rename document?") : _("Copy document?");
2695 docstring const button = (kind == LV_VC_RENAME) ?
2696 _("&Rename") : _("&Copy");
2697 int const ret = Alert::prompt(title, text, 0, 1,
2698 button, _("&Cancel"));
2700 case 0: return renameBuffer(b, docstring(), kind);
2701 case 1: return false;
2706 docstring text = bformat(_("The document %1$s "
2707 "already exists.\n\n"
2708 "Do you want to overwrite that document?"),
2710 int const ret = Alert::prompt(_("Overwrite document?"),
2711 text, 0, 2, _("&Overwrite"),
2712 _("&Rename"), _("&Cancel"));
2715 case 1: return renameBuffer(b, docstring(), kind);
2716 case 2: return false;
2722 case LV_VC_RENAME: {
2723 string msg = b.lyxvc().rename(fname);
2726 message(from_utf8(msg));
2730 string msg = b.lyxvc().copy(fname);
2733 message(from_utf8(msg));
2739 // LyXVC created the file already in case of LV_VC_RENAME or
2740 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2741 // relative paths of included stuff right if we moved e.g. from
2742 // /a/b.lyx to /a/c/b.lyx.
2744 bool const saved = saveBuffer(b, fname);
2751 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2753 FileName fname = b.fileName();
2755 FileDialog dlg(qt_("Choose a filename to export the document as"));
2756 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2759 QString const anyformat = qt_("Guess from extension (*.*)");
2762 vector<Format const *> export_formats;
2763 for (Format const & f : theFormats())
2764 if (f.documentFormat())
2765 export_formats.push_back(&f);
2766 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2767 map<QString, string> fmap;
2770 for (Format const * f : export_formats) {
2771 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2772 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2774 from_ascii(f->extension())));
2775 types << loc_filter;
2776 fmap[loc_filter] = f->name();
2777 if (from_ascii(f->name()) == iformat) {
2778 filter = loc_filter;
2779 ext = f->extension();
2782 string ofname = fname.onlyFileName();
2784 ofname = support::changeExtension(ofname, ext);
2785 FileDialog::Result result =
2786 dlg.save(toqstr(fname.onlyPath().absFileName()),
2790 if (result.first != FileDialog::Chosen)
2794 fname.set(fromqstr(result.second));
2795 if (filter == anyformat)
2796 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2798 fmt_name = fmap[filter];
2799 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2800 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2802 if (fmt_name.empty() || fname.empty())
2805 // fname is now the new Buffer location.
2806 if (FileName(fname).exists()) {
2807 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2808 docstring text = bformat(_("The document %1$s already "
2809 "exists.\n\nDo you want to "
2810 "overwrite that document?"),
2812 int const ret = Alert::prompt(_("Overwrite document?"),
2813 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2816 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2817 case 2: return false;
2821 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2824 return dr.dispatched();
2828 bool GuiView::saveBuffer(Buffer & b)
2830 return saveBuffer(b, FileName());
2834 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2836 if (workArea(b) && workArea(b)->inDialogMode())
2839 if (fn.empty() && b.isUnnamed())
2840 return renameBuffer(b, docstring());
2842 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2844 theSession().lastFiles().add(b.fileName());
2845 theSession().writeFile();
2849 // Switch to this Buffer.
2852 // FIXME: we don't tell the user *WHY* the save failed !!
2853 docstring const file = makeDisplayPath(b.absFileName(), 30);
2854 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2855 "Do you want to rename the document and "
2856 "try again?"), file);
2857 int const ret = Alert::prompt(_("Rename and save?"),
2858 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2861 if (!renameBuffer(b, docstring()))
2870 return saveBuffer(b, fn);
2874 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2876 return closeWorkArea(wa, false);
2880 // We only want to close the buffer if it is not visible in other workareas
2881 // of the same view, nor in other views, and if this is not a child
2882 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2884 Buffer & buf = wa->bufferView().buffer();
2886 bool last_wa = d.countWorkAreasOf(buf) == 1
2887 && !inOtherView(buf) && !buf.parent();
2889 bool close_buffer = last_wa;
2892 if (lyxrc.close_buffer_with_last_view == "yes")
2894 else if (lyxrc.close_buffer_with_last_view == "no")
2895 close_buffer = false;
2898 if (buf.isUnnamed())
2899 file = from_utf8(buf.fileName().onlyFileName());
2901 file = buf.fileName().displayName(30);
2902 docstring const text = bformat(
2903 _("Last view on document %1$s is being closed.\n"
2904 "Would you like to close or hide the document?\n"
2906 "Hidden documents can be displayed back through\n"
2907 "the menu: View->Hidden->...\n"
2909 "To remove this question, set your preference in:\n"
2910 " Tools->Preferences->Look&Feel->UserInterface\n"
2912 int ret = Alert::prompt(_("Close or hide document?"),
2913 text, 0, 1, _("&Close"), _("&Hide"));
2914 close_buffer = (ret == 0);
2918 return closeWorkArea(wa, close_buffer);
2922 bool GuiView::closeBuffer()
2924 GuiWorkArea * wa = currentMainWorkArea();
2925 // coverity complained about this
2926 // it seems unnecessary, but perhaps is worth the check
2927 LASSERT(wa, return false);
2929 setCurrentWorkArea(wa);
2930 Buffer & buf = wa->bufferView().buffer();
2931 return closeWorkArea(wa, !buf.parent());
2935 void GuiView::writeSession() const {
2936 GuiWorkArea const * active_wa = currentMainWorkArea();
2937 for (int i = 0; i < d.splitter_->count(); ++i) {
2938 TabWorkArea * twa = d.tabWorkArea(i);
2939 for (int j = 0; j < twa->count(); ++j) {
2940 GuiWorkArea * wa = twa->workArea(j);
2941 Buffer & buf = wa->bufferView().buffer();
2942 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2948 bool GuiView::closeBufferAll()
2950 // Close the workareas in all other views
2951 QList<int> const ids = guiApp->viewIds();
2952 for (int i = 0; i != ids.size(); ++i) {
2953 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2957 // Close our own workareas
2958 if (!closeWorkAreaAll())
2961 // Now close the hidden buffers. We prevent hidden buffers from being
2962 // dirty, so we can just close them.
2963 theBufferList().closeAll();
2968 bool GuiView::closeWorkAreaAll()
2970 setCurrentWorkArea(currentMainWorkArea());
2972 // We might be in a situation that there is still a tabWorkArea, but
2973 // there are no tabs anymore. This can happen when we get here after a
2974 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2975 // many TabWorkArea's have no documents anymore.
2978 // We have to call count() each time, because it can happen that
2979 // more than one splitter will disappear in one iteration (bug 5998).
2980 while (d.splitter_->count() > empty_twa) {
2981 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2983 if (twa->count() == 0)
2986 setCurrentWorkArea(twa->currentWorkArea());
2987 if (!closeTabWorkArea(twa))
2995 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3000 Buffer & buf = wa->bufferView().buffer();
3002 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3003 Alert::warning(_("Close document"),
3004 _("Document could not be closed because it is being processed by LyX."));
3009 return closeBuffer(buf);
3011 if (!inMultiTabs(wa))
3012 if (!saveBufferIfNeeded(buf, true))
3020 bool GuiView::closeBuffer(Buffer & buf)
3022 // If we are in a close_event all children will be closed in some time,
3023 // so no need to do it here. This will ensure that the children end up
3024 // in the session file in the correct order. If we close the master
3025 // buffer, we can close or release the child buffers here too.
3026 bool success = true;
3028 ListOfBuffers clist = buf.getChildren();
3029 ListOfBuffers::const_iterator it = clist.begin();
3030 ListOfBuffers::const_iterator const bend = clist.end();
3031 for (; it != bend; ++it) {
3032 Buffer * child_buf = *it;
3033 if (theBufferList().isOthersChild(&buf, child_buf)) {
3034 child_buf->setParent(0);
3038 // FIXME: should we look in other tabworkareas?
3039 // ANSWER: I don't think so. I've tested, and if the child is
3040 // open in some other window, it closes without a problem.
3041 GuiWorkArea * child_wa = workArea(*child_buf);
3043 success = closeWorkArea(child_wa, true);
3047 // In this case the child buffer is open but hidden.
3048 // It therefore should not (MUST NOT) be dirty!
3049 LATTEST(child_buf->isClean());
3050 theBufferList().release(child_buf);
3055 // goto bookmark to update bookmark pit.
3056 // FIXME: we should update only the bookmarks related to this buffer!
3057 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3058 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3059 guiApp->gotoBookmark(i+1, false, false);
3061 if (saveBufferIfNeeded(buf, false)) {
3062 buf.removeAutosaveFile();
3063 theBufferList().release(&buf);
3067 // open all children again to avoid a crash because of dangling
3068 // pointers (bug 6603)
3074 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3076 while (twa == d.currentTabWorkArea()) {
3077 twa->setCurrentIndex(twa->count() - 1);
3079 GuiWorkArea * wa = twa->currentWorkArea();
3080 Buffer & b = wa->bufferView().buffer();
3082 // We only want to close the buffer if the same buffer is not visible
3083 // in another view, and if this is not a child and if we are closing
3084 // a view (not a tabgroup).
3085 bool const close_buffer =
3086 !inOtherView(b) && !b.parent() && closing_;
3088 if (!closeWorkArea(wa, close_buffer))
3095 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3097 if (buf.isClean() || buf.paragraphs().empty())
3100 // Switch to this Buffer.
3106 if (buf.isUnnamed()) {
3107 file = from_utf8(buf.fileName().onlyFileName());
3110 FileName filename = buf.fileName();
3112 file = filename.displayName(30);
3113 exists = filename.exists();
3116 // Bring this window to top before asking questions.
3121 if (hiding && buf.isUnnamed()) {
3122 docstring const text = bformat(_("The document %1$s has not been "
3123 "saved yet.\n\nDo you want to save "
3124 "the document?"), file);
3125 ret = Alert::prompt(_("Save new document?"),
3126 text, 0, 1, _("&Save"), _("&Cancel"));
3130 docstring const text = exists ?
3131 bformat(_("The document %1$s has unsaved changes."
3132 "\n\nDo you want to save the document or "
3133 "discard the changes?"), file) :
3134 bformat(_("The document %1$s has not been saved yet."
3135 "\n\nDo you want to save the document or "
3136 "discard it entirely?"), file);
3137 docstring const title = exists ?
3138 _("Save changed document?") : _("Save document?");
3139 ret = Alert::prompt(title, text, 0, 2,
3140 _("&Save"), _("&Discard"), _("&Cancel"));
3145 if (!saveBuffer(buf))
3149 // If we crash after this we could have no autosave file
3150 // but I guess this is really improbable (Jug).
3151 // Sometimes improbable things happen:
3152 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3153 // buf.removeAutosaveFile();
3155 // revert all changes
3166 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3168 Buffer & buf = wa->bufferView().buffer();
3170 for (int i = 0; i != d.splitter_->count(); ++i) {
3171 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3172 if (wa_ && wa_ != wa)
3175 return inOtherView(buf);
3179 bool GuiView::inOtherView(Buffer & buf)
3181 QList<int> const ids = guiApp->viewIds();
3183 for (int i = 0; i != ids.size(); ++i) {
3187 if (guiApp->view(ids[i]).workArea(buf))
3194 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3196 if (!documentBufferView())
3199 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3200 Buffer * const curbuf = &documentBufferView()->buffer();
3201 int nwa = twa->count();
3202 for (int i = 0; i < nwa; ++i) {
3203 if (&workArea(i)->bufferView().buffer() == curbuf) {
3205 if (np == NEXTBUFFER)
3206 next_index = (i == nwa - 1 ? 0 : i + 1);
3208 next_index = (i == 0 ? nwa - 1 : i - 1);
3210 twa->moveTab(i, next_index);
3212 setBuffer(&workArea(next_index)->bufferView().buffer());
3220 /// make sure the document is saved
3221 static bool ensureBufferClean(Buffer * buffer)
3223 LASSERT(buffer, return false);
3224 if (buffer->isClean() && !buffer->isUnnamed())
3227 docstring const file = buffer->fileName().displayName(30);
3230 if (!buffer->isUnnamed()) {
3231 text = bformat(_("The document %1$s has unsaved "
3232 "changes.\n\nDo you want to save "
3233 "the document?"), file);
3234 title = _("Save changed document?");
3237 text = bformat(_("The document %1$s has not been "
3238 "saved yet.\n\nDo you want to save "
3239 "the document?"), file);
3240 title = _("Save new document?");
3242 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3245 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3247 return buffer->isClean() && !buffer->isUnnamed();
3251 bool GuiView::reloadBuffer(Buffer & buf)
3253 currentBufferView()->cursor().reset();
3254 Buffer::ReadStatus status = buf.reload();
3255 return status == Buffer::ReadSuccess;
3259 void GuiView::checkExternallyModifiedBuffers()
3261 BufferList::iterator bit = theBufferList().begin();
3262 BufferList::iterator const bend = theBufferList().end();
3263 for (; bit != bend; ++bit) {
3264 Buffer * buf = *bit;
3265 if (buf->fileName().exists() && buf->isChecksumModified()) {
3266 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3267 " Reload now? Any local changes will be lost."),
3268 from_utf8(buf->absFileName()));
3269 int const ret = Alert::prompt(_("Reload externally changed document?"),
3270 text, 0, 1, _("&Reload"), _("&Cancel"));
3278 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3280 Buffer * buffer = documentBufferView()
3281 ? &(documentBufferView()->buffer()) : 0;
3283 switch (cmd.action()) {
3284 case LFUN_VC_REGISTER:
3285 if (!buffer || !ensureBufferClean(buffer))
3287 if (!buffer->lyxvc().inUse()) {
3288 if (buffer->lyxvc().registrer()) {
3289 reloadBuffer(*buffer);
3290 dr.clearMessageUpdate();
3295 case LFUN_VC_RENAME:
3296 case LFUN_VC_COPY: {
3297 if (!buffer || !ensureBufferClean(buffer))
3299 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3300 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3301 // Some changes are not yet committed.
3302 // We test here and not in getStatus(), since
3303 // this test is expensive.
3305 LyXVC::CommandResult ret =
3306 buffer->lyxvc().checkIn(log);
3308 if (ret == LyXVC::ErrorCommand ||
3309 ret == LyXVC::VCSuccess)
3310 reloadBuffer(*buffer);
3311 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3312 frontend::Alert::error(
3313 _("Revision control error."),
3314 _("Document could not be checked in."));
3318 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3319 LV_VC_RENAME : LV_VC_COPY;
3320 renameBuffer(*buffer, cmd.argument(), kind);
3325 case LFUN_VC_CHECK_IN:
3326 if (!buffer || !ensureBufferClean(buffer))
3328 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3330 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3332 // Only skip reloading if the checkin was cancelled or
3333 // an error occurred before the real checkin VCS command
3334 // was executed, since the VCS might have changed the
3335 // file even if it could not checkin successfully.
3336 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3337 reloadBuffer(*buffer);
3341 case LFUN_VC_CHECK_OUT:
3342 if (!buffer || !ensureBufferClean(buffer))
3344 if (buffer->lyxvc().inUse()) {
3345 dr.setMessage(buffer->lyxvc().checkOut());
3346 reloadBuffer(*buffer);
3350 case LFUN_VC_LOCKING_TOGGLE:
3351 LASSERT(buffer, return);
3352 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3354 if (buffer->lyxvc().inUse()) {
3355 string res = buffer->lyxvc().lockingToggle();
3357 frontend::Alert::error(_("Revision control error."),
3358 _("Error when setting the locking property."));
3361 reloadBuffer(*buffer);
3366 case LFUN_VC_REVERT:
3367 LASSERT(buffer, return);
3368 if (buffer->lyxvc().revert()) {
3369 reloadBuffer(*buffer);
3370 dr.clearMessageUpdate();
3374 case LFUN_VC_UNDO_LAST:
3375 LASSERT(buffer, return);
3376 buffer->lyxvc().undoLast();
3377 reloadBuffer(*buffer);
3378 dr.clearMessageUpdate();
3381 case LFUN_VC_REPO_UPDATE:
3382 LASSERT(buffer, return);
3383 if (ensureBufferClean(buffer)) {
3384 dr.setMessage(buffer->lyxvc().repoUpdate());
3385 checkExternallyModifiedBuffers();
3389 case LFUN_VC_COMMAND: {
3390 string flag = cmd.getArg(0);
3391 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3394 if (contains(flag, 'M')) {
3395 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3398 string path = cmd.getArg(1);
3399 if (contains(path, "$$p") && buffer)
3400 path = subst(path, "$$p", buffer->filePath());
3401 LYXERR(Debug::LYXVC, "Directory: " << path);
3403 if (!pp.isReadableDirectory()) {
3404 lyxerr << _("Directory is not accessible.") << endl;
3407 support::PathChanger p(pp);
3409 string command = cmd.getArg(2);
3410 if (command.empty())
3413 command = subst(command, "$$i", buffer->absFileName());
3414 command = subst(command, "$$p", buffer->filePath());
3416 command = subst(command, "$$m", to_utf8(message));
3417 LYXERR(Debug::LYXVC, "Command: " << command);
3419 one.startscript(Systemcall::Wait, command);
3423 if (contains(flag, 'I'))
3424 buffer->markDirty();
3425 if (contains(flag, 'R'))
3426 reloadBuffer(*buffer);
3431 case LFUN_VC_COMPARE: {
3432 if (cmd.argument().empty()) {
3433 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3437 string rev1 = cmd.getArg(0);
3442 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3445 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3446 f2 = buffer->absFileName();
3448 string rev2 = cmd.getArg(1);
3452 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3456 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3457 f1 << "\n" << f2 << "\n" );
3458 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3459 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3469 void GuiView::openChildDocument(string const & fname)
3471 LASSERT(documentBufferView(), return);
3472 Buffer & buffer = documentBufferView()->buffer();
3473 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3474 documentBufferView()->saveBookmark(false);
3476 if (theBufferList().exists(filename)) {
3477 child = theBufferList().getBuffer(filename);
3480 message(bformat(_("Opening child document %1$s..."),
3481 makeDisplayPath(filename.absFileName())));
3482 child = loadDocument(filename, false);
3484 // Set the parent name of the child document.
3485 // This makes insertion of citations and references in the child work,
3486 // when the target is in the parent or another child document.
3488 child->setParent(&buffer);
3492 bool GuiView::goToFileRow(string const & argument)
3496 size_t i = argument.find_last_of(' ');
3497 if (i != string::npos) {
3498 file_name = os::internal_path(trim(argument.substr(0, i)));
3499 istringstream is(argument.substr(i + 1));
3504 if (i == string::npos) {
3505 LYXERR0("Wrong argument: " << argument);
3509 string const abstmp = package().temp_dir().absFileName();
3510 string const realtmp = package().temp_dir().realPath();
3511 // We have to use os::path_prefix_is() here, instead of
3512 // simply prefixIs(), because the file name comes from
3513 // an external application and may need case adjustment.
3514 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3515 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3516 // Needed by inverse dvi search. If it is a file
3517 // in tmpdir, call the apropriated function.
3518 // If tmpdir is a symlink, we may have the real
3519 // path passed back, so we correct for that.
3520 if (!prefixIs(file_name, abstmp))
3521 file_name = subst(file_name, realtmp, abstmp);
3522 buf = theBufferList().getBufferFromTmp(file_name);
3524 // Must replace extension of the file to be .lyx
3525 // and get full path
3526 FileName const s = fileSearch(string(),
3527 support::changeExtension(file_name, ".lyx"), "lyx");
3528 // Either change buffer or load the file
3529 if (theBufferList().exists(s))
3530 buf = theBufferList().getBuffer(s);
3531 else if (s.exists()) {
3532 buf = loadDocument(s);
3537 _("File does not exist: %1$s"),
3538 makeDisplayPath(file_name)));
3544 _("No buffer for file: %1$s."),
3545 makeDisplayPath(file_name))
3550 bool success = documentBufferView()->setCursorFromRow(row);
3552 LYXERR(Debug::LATEX,
3553 "setCursorFromRow: invalid position for row " << row);
3554 frontend::Alert::error(_("Inverse Search Failed"),
3555 _("Invalid position requested by inverse search.\n"
3556 "You may need to update the viewed document."));
3562 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3564 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3565 menu->exec(QCursor::pos());
3570 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3571 Buffer const * orig, Buffer * clone, string const & format)
3573 Buffer::ExportStatus const status = func(format);
3575 // the cloning operation will have produced a clone of the entire set of
3576 // documents, starting from the master. so we must delete those.
3577 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3579 busyBuffers.remove(orig);
3584 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3585 Buffer const * orig, Buffer * clone, string const & format)
3587 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3589 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3593 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3594 Buffer const * orig, Buffer * clone, string const & format)
3596 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3598 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3602 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3603 Buffer const * orig, Buffer * clone, string const & format)
3605 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3607 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3611 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3612 string const & argument,
3613 Buffer const * used_buffer,
3614 docstring const & msg,
3615 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3616 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3617 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3623 string format = argument;
3625 format = used_buffer->params().getDefaultOutputFormat();
3626 processing_format = format;
3628 progress_->clearMessages();
3631 #if EXPORT_in_THREAD
3633 GuiViewPrivate::busyBuffers.insert(used_buffer);
3634 Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3635 if (!cloned_buffer) {
3636 Alert::error(_("Export Error"),
3637 _("Error cloning the Buffer."));
3640 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3645 setPreviewFuture(f);
3646 last_export_format = used_buffer->params().bufferFormat();
3649 // We are asynchronous, so we don't know here anything about the success
3652 Buffer::ExportStatus status;
3654 status = (used_buffer->*syncFunc)(format, false);
3655 } else if (previewFunc) {
3656 status = (used_buffer->*previewFunc)(format);
3659 handleExportStatus(gv_, status, format);
3661 return (status == Buffer::ExportSuccess
3662 || status == Buffer::PreviewSuccess);
3666 Buffer::ExportStatus status;
3668 status = (used_buffer->*syncFunc)(format, true);
3669 } else if (previewFunc) {
3670 status = (used_buffer->*previewFunc)(format);
3673 handleExportStatus(gv_, status, format);
3675 return (status == Buffer::ExportSuccess
3676 || status == Buffer::PreviewSuccess);
3680 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3682 BufferView * bv = currentBufferView();
3683 LASSERT(bv, return);
3685 // Let the current BufferView dispatch its own actions.
3686 bv->dispatch(cmd, dr);
3687 if (dr.dispatched())
3690 // Try with the document BufferView dispatch if any.
3691 BufferView * doc_bv = documentBufferView();
3692 if (doc_bv && doc_bv != bv) {
3693 doc_bv->dispatch(cmd, dr);
3694 if (dr.dispatched())
3698 // Then let the current Cursor dispatch its own actions.
3699 bv->cursor().dispatch(cmd);
3701 // update completion. We do it here and not in
3702 // processKeySym to avoid another redraw just for a
3703 // changed inline completion
3704 if (cmd.origin() == FuncRequest::KEYBOARD) {
3705 if (cmd.action() == LFUN_SELF_INSERT
3706 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3707 updateCompletion(bv->cursor(), true, true);
3708 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3709 updateCompletion(bv->cursor(), false, true);
3711 updateCompletion(bv->cursor(), false, false);
3714 dr = bv->cursor().result();
3718 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3720 BufferView * bv = currentBufferView();
3721 // By default we won't need any update.
3722 dr.screenUpdate(Update::None);
3723 // assume cmd will be dispatched
3724 dr.dispatched(true);
3726 Buffer * doc_buffer = documentBufferView()
3727 ? &(documentBufferView()->buffer()) : 0;
3729 if (cmd.origin() == FuncRequest::TOC) {
3730 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3731 // FIXME: do we need to pass a DispatchResult object here?
3732 toc->doDispatch(bv->cursor(), cmd);
3736 string const argument = to_utf8(cmd.argument());
3738 switch(cmd.action()) {
3739 case LFUN_BUFFER_CHILD_OPEN:
3740 openChildDocument(to_utf8(cmd.argument()));
3743 case LFUN_BUFFER_IMPORT:
3744 importDocument(to_utf8(cmd.argument()));
3747 case LFUN_MASTER_BUFFER_EXPORT:
3749 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3751 case LFUN_BUFFER_EXPORT: {
3754 // GCC only sees strfwd.h when building merged
3755 if (::lyx::operator==(cmd.argument(), "custom")) {
3756 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3757 // so the following test should not be needed.
3758 // In principle, we could try to switch to such a view...
3759 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3760 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3764 string const dest = cmd.getArg(1);
3765 FileName target_dir;
3766 if (!dest.empty() && FileName::isAbsolute(dest))
3767 target_dir = FileName(support::onlyPath(dest));
3769 target_dir = doc_buffer->fileName().onlyPath();
3771 string const format = (argument.empty() || argument == "default") ?
3772 doc_buffer->params().getDefaultOutputFormat() : argument;
3774 if ((dest.empty() && doc_buffer->isUnnamed())
3775 || !target_dir.isDirWritable()) {
3776 exportBufferAs(*doc_buffer, from_utf8(format));
3779 /* TODO/Review: Is it a problem to also export the children?
3780 See the update_unincluded flag */
3781 d.asyncBufferProcessing(format,
3784 &GuiViewPrivate::exportAndDestroy,
3786 0, cmd.allowAsync());
3787 // TODO Inform user about success
3791 case LFUN_BUFFER_EXPORT_AS: {
3792 LASSERT(doc_buffer, break);
3793 docstring f = cmd.argument();
3795 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3796 exportBufferAs(*doc_buffer, f);
3800 case LFUN_BUFFER_UPDATE: {
3801 d.asyncBufferProcessing(argument,
3804 &GuiViewPrivate::compileAndDestroy,
3806 0, cmd.allowAsync());
3809 case LFUN_BUFFER_VIEW: {
3810 d.asyncBufferProcessing(argument,
3812 _("Previewing ..."),
3813 &GuiViewPrivate::previewAndDestroy,
3815 &Buffer::preview, cmd.allowAsync());
3818 case LFUN_MASTER_BUFFER_UPDATE: {
3819 d.asyncBufferProcessing(argument,
3820 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3822 &GuiViewPrivate::compileAndDestroy,
3824 0, cmd.allowAsync());
3827 case LFUN_MASTER_BUFFER_VIEW: {
3828 d.asyncBufferProcessing(argument,
3829 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3831 &GuiViewPrivate::previewAndDestroy,
3832 0, &Buffer::preview, cmd.allowAsync());
3835 case LFUN_EXPORT_CANCEL: {
3836 Systemcall::killscript();
3839 case LFUN_BUFFER_SWITCH: {
3840 string const file_name = to_utf8(cmd.argument());
3841 if (!FileName::isAbsolute(file_name)) {
3843 dr.setMessage(_("Absolute filename expected."));
3847 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3850 dr.setMessage(_("Document not loaded"));
3854 // Do we open or switch to the buffer in this view ?
3855 if (workArea(*buffer)
3856 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3861 // Look for the buffer in other views
3862 QList<int> const ids = guiApp->viewIds();
3864 for (; i != ids.size(); ++i) {
3865 GuiView & gv = guiApp->view(ids[i]);
3866 if (gv.workArea(*buffer)) {
3868 gv.activateWindow();
3870 gv.setBuffer(buffer);
3875 // If necessary, open a new window as a last resort
3876 if (i == ids.size()) {
3877 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3883 case LFUN_BUFFER_NEXT:
3884 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3887 case LFUN_BUFFER_MOVE_NEXT:
3888 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3891 case LFUN_BUFFER_PREVIOUS:
3892 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3895 case LFUN_BUFFER_MOVE_PREVIOUS:
3896 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3899 case LFUN_BUFFER_CHKTEX:
3900 LASSERT(doc_buffer, break);
3901 doc_buffer->runChktex();
3904 case LFUN_COMMAND_EXECUTE: {
3905 command_execute_ = true;
3906 minibuffer_focus_ = true;
3909 case LFUN_DROP_LAYOUTS_CHOICE:
3910 d.layout_->showPopup();
3913 case LFUN_MENU_OPEN:
3914 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3915 menu->exec(QCursor::pos());
3918 case LFUN_FILE_INSERT:
3919 insertLyXFile(cmd.argument());
3922 case LFUN_FILE_INSERT_PLAINTEXT:
3923 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3924 string const fname = to_utf8(cmd.argument());
3925 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3926 dr.setMessage(_("Absolute filename expected."));
3930 FileName filename(fname);
3931 if (fname.empty()) {
3932 FileDialog dlg(qt_("Select file to insert"));
3934 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3935 QStringList(qt_("All Files (*)")));
3937 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3938 dr.setMessage(_("Canceled."));
3942 filename.set(fromqstr(result.second));
3946 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3947 bv->dispatch(new_cmd, dr);
3952 case LFUN_BUFFER_RELOAD: {
3953 LASSERT(doc_buffer, break);
3956 bool drop = (cmd.argument() == "dump");
3959 if (!drop && !doc_buffer->isClean()) {
3960 docstring const file =
3961 makeDisplayPath(doc_buffer->absFileName(), 20);
3962 if (doc_buffer->notifiesExternalModification()) {
3963 docstring text = _("The current version will be lost. "
3964 "Are you sure you want to load the version on disk "
3965 "of the document %1$s?");
3966 ret = Alert::prompt(_("Reload saved document?"),
3967 bformat(text, file), 1, 1,
3968 _("&Reload"), _("&Cancel"));
3970 docstring text = _("Any changes will be lost. "
3971 "Are you sure you want to revert to the saved version "
3972 "of the document %1$s?");
3973 ret = Alert::prompt(_("Revert to saved document?"),
3974 bformat(text, file), 1, 1,
3975 _("&Revert"), _("&Cancel"));
3980 doc_buffer->markClean();
3981 reloadBuffer(*doc_buffer);
3982 dr.forceBufferUpdate();
3987 case LFUN_BUFFER_WRITE:
3988 LASSERT(doc_buffer, break);
3989 saveBuffer(*doc_buffer);
3992 case LFUN_BUFFER_WRITE_AS:
3993 LASSERT(doc_buffer, break);
3994 renameBuffer(*doc_buffer, cmd.argument());
3997 case LFUN_BUFFER_WRITE_ALL: {
3998 Buffer * first = theBufferList().first();
4001 message(_("Saving all documents..."));
4002 // We cannot use a for loop as the buffer list cycles.
4005 if (!b->isClean()) {
4007 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4009 b = theBufferList().next(b);
4010 } while (b != first);
4011 dr.setMessage(_("All documents saved."));
4015 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4016 LASSERT(doc_buffer, break);
4017 doc_buffer->clearExternalModification();
4020 case LFUN_BUFFER_CLOSE:
4024 case LFUN_BUFFER_CLOSE_ALL:
4028 case LFUN_DEVEL_MODE_TOGGLE:
4029 devel_mode_ = !devel_mode_;
4031 dr.setMessage(_("Developer mode is now enabled."));
4033 dr.setMessage(_("Developer mode is now disabled."));
4036 case LFUN_TOOLBAR_TOGGLE: {
4037 string const name = cmd.getArg(0);
4038 if (GuiToolbar * t = toolbar(name))
4043 case LFUN_TOOLBAR_MOVABLE: {
4044 string const name = cmd.getArg(0);
4046 // toggle (all) toolbars movablility
4047 toolbarsMovable_ = !toolbarsMovable_;
4048 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4049 GuiToolbar * tb = toolbar(ti.name);
4050 if (tb && tb->isMovable() != toolbarsMovable_)
4051 // toggle toolbar movablity if it does not fit lock
4052 // (all) toolbars positions state silent = true, since
4053 // status bar notifications are slow
4056 if (toolbarsMovable_)
4057 dr.setMessage(_("Toolbars unlocked."));
4059 dr.setMessage(_("Toolbars locked."));
4060 } else if (GuiToolbar * t = toolbar(name)) {
4061 // toggle current toolbar movablity
4063 // update lock (all) toolbars positions
4064 updateLockToolbars();
4069 case LFUN_ICON_SIZE: {
4070 QSize size = d.iconSize(cmd.argument());
4072 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4073 size.width(), size.height()));
4077 case LFUN_DIALOG_UPDATE: {
4078 string const name = to_utf8(cmd.argument());
4079 if (name == "prefs" || name == "document")
4080 updateDialog(name, string());
4081 else if (name == "paragraph")
4082 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4083 else if (currentBufferView()) {
4084 Inset * inset = currentBufferView()->editedInset(name);
4085 // Can only update a dialog connected to an existing inset
4087 // FIXME: get rid of this indirection; GuiView ask the inset
4088 // if he is kind enough to update itself...
4089 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4090 //FIXME: pass DispatchResult here?
4091 inset->dispatch(currentBufferView()->cursor(), fr);
4097 case LFUN_DIALOG_TOGGLE: {
4098 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4099 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4100 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4104 case LFUN_DIALOG_DISCONNECT_INSET:
4105 disconnectDialog(to_utf8(cmd.argument()));
4108 case LFUN_DIALOG_HIDE: {
4109 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4113 case LFUN_DIALOG_SHOW: {
4114 string const name = cmd.getArg(0);
4115 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4117 if (name == "character") {
4118 sdata = freefont2string();
4120 showDialog("character", sdata);
4121 } else if (name == "latexlog") {
4122 // gettatus checks that
4123 LATTEST(doc_buffer);
4124 Buffer::LogType type;
4125 string const logfile = doc_buffer->logName(&type);
4127 case Buffer::latexlog:
4130 case Buffer::buildlog:
4131 sdata = "literate ";
4134 sdata += Lexer::quoteString(logfile);
4135 showDialog("log", sdata);
4136 } else if (name == "vclog") {
4137 // getStatus checks that
4138 LATTEST(doc_buffer);
4139 string const sdata2 = "vc " +
4140 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4141 showDialog("log", sdata2);
4142 } else if (name == "symbols") {
4143 sdata = bv->cursor().getEncoding()->name();
4145 showDialog("symbols", sdata);
4147 } else if (name == "prefs" && isFullScreen()) {
4148 lfunUiToggle("fullscreen");
4149 showDialog("prefs", sdata);
4151 showDialog(name, sdata);
4156 dr.setMessage(cmd.argument());
4159 case LFUN_UI_TOGGLE: {
4160 string arg = cmd.getArg(0);
4161 if (!lfunUiToggle(arg)) {
4162 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4163 dr.setMessage(bformat(msg, from_utf8(arg)));
4165 // Make sure the keyboard focus stays in the work area.
4170 case LFUN_VIEW_SPLIT: {
4171 LASSERT(doc_buffer, break);
4172 string const orientation = cmd.getArg(0);
4173 d.splitter_->setOrientation(orientation == "vertical"
4174 ? Qt::Vertical : Qt::Horizontal);
4175 TabWorkArea * twa = addTabWorkArea();
4176 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4177 setCurrentWorkArea(wa);
4180 case LFUN_TAB_GROUP_CLOSE:
4181 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4182 closeTabWorkArea(twa);
4183 d.current_work_area_ = 0;
4184 twa = d.currentTabWorkArea();
4185 // Switch to the next GuiWorkArea in the found TabWorkArea.
4187 // Make sure the work area is up to date.
4188 setCurrentWorkArea(twa->currentWorkArea());
4190 setCurrentWorkArea(0);
4195 case LFUN_VIEW_CLOSE:
4196 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4197 closeWorkArea(twa->currentWorkArea());
4198 d.current_work_area_ = 0;
4199 twa = d.currentTabWorkArea();
4200 // Switch to the next GuiWorkArea in the found TabWorkArea.
4202 // Make sure the work area is up to date.
4203 setCurrentWorkArea(twa->currentWorkArea());
4205 setCurrentWorkArea(0);
4210 case LFUN_COMPLETION_INLINE:
4211 if (d.current_work_area_)
4212 d.current_work_area_->completer().showInline();
4215 case LFUN_COMPLETION_POPUP:
4216 if (d.current_work_area_)
4217 d.current_work_area_->completer().showPopup();
4222 if (d.current_work_area_)
4223 d.current_work_area_->completer().tab();
4226 case LFUN_COMPLETION_CANCEL:
4227 if (d.current_work_area_) {
4228 if (d.current_work_area_->completer().popupVisible())
4229 d.current_work_area_->completer().hidePopup();
4231 d.current_work_area_->completer().hideInline();
4235 case LFUN_COMPLETION_ACCEPT:
4236 if (d.current_work_area_)
4237 d.current_work_area_->completer().activate();
4240 case LFUN_BUFFER_ZOOM_IN:
4241 case LFUN_BUFFER_ZOOM_OUT:
4242 case LFUN_BUFFER_ZOOM: {
4243 if (cmd.argument().empty()) {
4244 if (cmd.action() == LFUN_BUFFER_ZOOM)
4246 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4251 if (cmd.action() == LFUN_BUFFER_ZOOM)
4252 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4253 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4254 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4256 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4259 // Actual zoom value: default zoom + fractional extra value
4260 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4261 if (zoom < static_cast<int>(zoom_min_))
4264 lyxrc.currentZoom = zoom;
4266 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4267 lyxrc.currentZoom, lyxrc.defaultZoom));
4269 // The global QPixmapCache is used in GuiPainter to cache text
4270 // painting so we must reset it.
4271 QPixmapCache::clear();
4272 guiApp->fontLoader().update();
4273 dr.screenUpdate(Update::Force | Update::FitCursor);
4277 case LFUN_VC_REGISTER:
4278 case LFUN_VC_RENAME:
4280 case LFUN_VC_CHECK_IN:
4281 case LFUN_VC_CHECK_OUT:
4282 case LFUN_VC_REPO_UPDATE:
4283 case LFUN_VC_LOCKING_TOGGLE:
4284 case LFUN_VC_REVERT:
4285 case LFUN_VC_UNDO_LAST:
4286 case LFUN_VC_COMMAND:
4287 case LFUN_VC_COMPARE:
4288 dispatchVC(cmd, dr);
4291 case LFUN_SERVER_GOTO_FILE_ROW:
4292 if(goToFileRow(to_utf8(cmd.argument())))
4293 dr.screenUpdate(Update::Force | Update::FitCursor);
4296 case LFUN_LYX_ACTIVATE:
4300 case LFUN_FORWARD_SEARCH: {
4301 // it seems safe to assume we have a document buffer, since
4302 // getStatus wants one.
4303 LATTEST(doc_buffer);
4304 Buffer const * doc_master = doc_buffer->masterBuffer();
4305 FileName const path(doc_master->temppath());
4306 string const texname = doc_master->isChild(doc_buffer)
4307 ? DocFileName(changeExtension(
4308 doc_buffer->absFileName(),
4309 "tex")).mangledFileName()
4310 : doc_buffer->latexName();
4311 string const fulltexname =
4312 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4313 string const mastername =
4314 removeExtension(doc_master->latexName());
4315 FileName const dviname(addName(path.absFileName(),
4316 addExtension(mastername, "dvi")));
4317 FileName const pdfname(addName(path.absFileName(),
4318 addExtension(mastername, "pdf")));
4319 bool const have_dvi = dviname.exists();
4320 bool const have_pdf = pdfname.exists();
4321 if (!have_dvi && !have_pdf) {
4322 dr.setMessage(_("Please, preview the document first."));
4325 string outname = dviname.onlyFileName();
4326 string command = lyxrc.forward_search_dvi;
4327 if (!have_dvi || (have_pdf &&
4328 pdfname.lastModified() > dviname.lastModified())) {
4329 outname = pdfname.onlyFileName();
4330 command = lyxrc.forward_search_pdf;
4333 DocIterator cur = bv->cursor();
4334 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4335 LYXERR(Debug::ACTION, "Forward search: row:" << row
4337 if (row == -1 || command.empty()) {
4338 dr.setMessage(_("Couldn't proceed."));
4341 string texrow = convert<string>(row);
4343 command = subst(command, "$$n", texrow);
4344 command = subst(command, "$$f", fulltexname);
4345 command = subst(command, "$$t", texname);
4346 command = subst(command, "$$o", outname);
4348 PathChanger p(path);
4350 one.startscript(Systemcall::DontWait, command);
4354 case LFUN_SPELLING_CONTINUOUSLY:
4355 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4356 dr.screenUpdate(Update::Force);
4360 // The LFUN must be for one of BufferView, Buffer or Cursor;
4362 dispatchToBufferView(cmd, dr);
4366 // Part of automatic menu appearance feature.
4367 if (isFullScreen()) {
4368 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4372 // Need to update bv because many LFUNs here might have destroyed it
4373 bv = currentBufferView();
4375 // Clear non-empty selections
4376 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4378 Cursor & cur = bv->cursor();
4379 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4380 cur.clearSelection();
4386 bool GuiView::lfunUiToggle(string const & ui_component)
4388 if (ui_component == "scrollbar") {
4389 // hide() is of no help
4390 if (d.current_work_area_->verticalScrollBarPolicy() ==
4391 Qt::ScrollBarAlwaysOff)
4393 d.current_work_area_->setVerticalScrollBarPolicy(
4394 Qt::ScrollBarAsNeeded);
4396 d.current_work_area_->setVerticalScrollBarPolicy(
4397 Qt::ScrollBarAlwaysOff);
4398 } else if (ui_component == "statusbar") {
4399 statusBar()->setVisible(!statusBar()->isVisible());
4400 } else if (ui_component == "menubar") {
4401 menuBar()->setVisible(!menuBar()->isVisible());
4403 if (ui_component == "frame") {
4405 getContentsMargins(&l, &t, &r, &b);
4406 //are the frames in default state?
4407 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4409 setContentsMargins(-2, -2, -2, -2);
4411 setContentsMargins(0, 0, 0, 0);
4414 if (ui_component == "fullscreen") {
4422 void GuiView::toggleFullScreen()
4424 if (isFullScreen()) {
4425 for (int i = 0; i != d.splitter_->count(); ++i)
4426 d.tabWorkArea(i)->setFullScreen(false);
4427 setContentsMargins(0, 0, 0, 0);
4428 setWindowState(windowState() ^ Qt::WindowFullScreen);
4431 statusBar()->show();
4434 hideDialogs("prefs", 0);
4435 for (int i = 0; i != d.splitter_->count(); ++i)
4436 d.tabWorkArea(i)->setFullScreen(true);
4437 setContentsMargins(-2, -2, -2, -2);
4439 setWindowState(windowState() ^ Qt::WindowFullScreen);
4440 if (lyxrc.full_screen_statusbar)
4441 statusBar()->hide();
4442 if (lyxrc.full_screen_menubar)
4444 if (lyxrc.full_screen_toolbars) {
4445 ToolbarMap::iterator end = d.toolbars_.end();
4446 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4451 // give dialogs like the TOC a chance to adapt
4456 Buffer const * GuiView::updateInset(Inset const * inset)
4461 Buffer const * inset_buffer = &(inset->buffer());
4463 for (int i = 0; i != d.splitter_->count(); ++i) {
4464 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4467 Buffer const * buffer = &(wa->bufferView().buffer());
4468 if (inset_buffer == buffer)
4469 wa->scheduleRedraw(true);
4471 return inset_buffer;
4475 void GuiView::restartCaret()
4477 /* When we move around, or type, it's nice to be able to see
4478 * the caret immediately after the keypress.
4480 if (d.current_work_area_)
4481 d.current_work_area_->startBlinkingCaret();
4483 // Take this occasion to update the other GUI elements.
4489 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4491 if (d.current_work_area_)
4492 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4497 // This list should be kept in sync with the list of insets in
4498 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4499 // dialog should have the same name as the inset.
4500 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4501 // docs in LyXAction.cpp.
4503 char const * const dialognames[] = {
4505 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4506 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4507 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4508 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4509 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4510 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4511 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4512 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4514 char const * const * const end_dialognames =
4515 dialognames + (sizeof(dialognames) / sizeof(char *));
4519 cmpCStr(char const * name) : name_(name) {}
4520 bool operator()(char const * other) {
4521 return strcmp(other, name_) == 0;
4528 bool isValidName(string const & name)
4530 return find_if(dialognames, end_dialognames,
4531 cmpCStr(name.c_str())) != end_dialognames;
4537 void GuiView::resetDialogs()
4539 // Make sure that no LFUN uses any GuiView.
4540 guiApp->setCurrentView(0);
4544 constructToolbars();
4545 guiApp->menus().fillMenuBar(menuBar(), this, false);
4546 d.layout_->updateContents(true);
4547 // Now update controls with current buffer.
4548 guiApp->setCurrentView(this);
4554 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4556 if (!isValidName(name))
4559 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4561 if (it != d.dialogs_.end()) {
4563 it->second->hideView();
4564 return it->second.get();
4567 Dialog * dialog = build(name);
4568 d.dialogs_[name].reset(dialog);
4569 if (lyxrc.allow_geometry_session)
4570 dialog->restoreSession();
4577 void GuiView::showDialog(string const & name, string const & sdata,
4580 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4584 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4590 const string name = fromqstr(qname);
4591 const string sdata = fromqstr(qdata);
4595 Dialog * dialog = findOrBuild(name, false);
4597 bool const visible = dialog->isVisibleView();
4598 dialog->showData(sdata);
4599 if (inset && currentBufferView())
4600 currentBufferView()->editInset(name, inset);
4601 // We only set the focus to the new dialog if it was not yet
4602 // visible in order not to change the existing previous behaviour
4604 // activateWindow is needed for floating dockviews
4605 dialog->asQWidget()->raise();
4606 dialog->asQWidget()->activateWindow();
4607 dialog->asQWidget()->setFocus();
4611 catch (ExceptionMessage const & ex) {
4619 bool GuiView::isDialogVisible(string const & name) const
4621 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4622 if (it == d.dialogs_.end())
4624 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4628 void GuiView::hideDialog(string const & name, Inset * inset)
4630 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4631 if (it == d.dialogs_.end())
4635 if (!currentBufferView())
4637 if (inset != currentBufferView()->editedInset(name))
4641 Dialog * const dialog = it->second.get();
4642 if (dialog->isVisibleView())
4644 if (currentBufferView())
4645 currentBufferView()->editInset(name, 0);
4649 void GuiView::disconnectDialog(string const & name)
4651 if (!isValidName(name))
4653 if (currentBufferView())
4654 currentBufferView()->editInset(name, 0);
4658 void GuiView::hideAll() const
4660 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4661 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4663 for(; it != end; ++it)
4664 it->second->hideView();
4668 void GuiView::updateDialogs()
4670 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4671 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4673 for(; it != end; ++it) {
4674 Dialog * dialog = it->second.get();
4676 if (dialog->needBufferOpen() && !documentBufferView())
4677 hideDialog(fromqstr(dialog->name()), 0);
4678 else if (dialog->isVisibleView())
4679 dialog->checkStatus();
4686 Dialog * createDialog(GuiView & lv, string const & name);
4688 // will be replaced by a proper factory...
4689 Dialog * createGuiAbout(GuiView & lv);
4690 Dialog * createGuiBibtex(GuiView & lv);
4691 Dialog * createGuiChanges(GuiView & lv);
4692 Dialog * createGuiCharacter(GuiView & lv);
4693 Dialog * createGuiCitation(GuiView & lv);
4694 Dialog * createGuiCompare(GuiView & lv);
4695 Dialog * createGuiCompareHistory(GuiView & lv);
4696 Dialog * createGuiDelimiter(GuiView & lv);
4697 Dialog * createGuiDocument(GuiView & lv);
4698 Dialog * createGuiErrorList(GuiView & lv);
4699 Dialog * createGuiExternal(GuiView & lv);
4700 Dialog * createGuiGraphics(GuiView & lv);
4701 Dialog * createGuiInclude(GuiView & lv);
4702 Dialog * createGuiIndex(GuiView & lv);
4703 Dialog * createGuiListings(GuiView & lv);
4704 Dialog * createGuiLog(GuiView & lv);
4705 Dialog * createGuiMathMatrix(GuiView & lv);
4706 Dialog * createGuiNote(GuiView & lv);
4707 Dialog * createGuiParagraph(GuiView & lv);
4708 Dialog * createGuiPhantom(GuiView & lv);
4709 Dialog * createGuiPreferences(GuiView & lv);
4710 Dialog * createGuiPrint(GuiView & lv);
4711 Dialog * createGuiPrintindex(GuiView & lv);
4712 Dialog * createGuiRef(GuiView & lv);
4713 Dialog * createGuiSearch(GuiView & lv);
4714 Dialog * createGuiSearchAdv(GuiView & lv);
4715 Dialog * createGuiSendTo(GuiView & lv);
4716 Dialog * createGuiShowFile(GuiView & lv);
4717 Dialog * createGuiSpellchecker(GuiView & lv);
4718 Dialog * createGuiSymbols(GuiView & lv);
4719 Dialog * createGuiTabularCreate(GuiView & lv);
4720 Dialog * createGuiTexInfo(GuiView & lv);
4721 Dialog * createGuiToc(GuiView & lv);
4722 Dialog * createGuiThesaurus(GuiView & lv);
4723 Dialog * createGuiViewSource(GuiView & lv);
4724 Dialog * createGuiWrap(GuiView & lv);
4725 Dialog * createGuiProgressView(GuiView & lv);
4729 Dialog * GuiView::build(string const & name)
4731 LASSERT(isValidName(name), return 0);
4733 Dialog * dialog = createDialog(*this, name);
4737 if (name == "aboutlyx")
4738 return createGuiAbout(*this);
4739 if (name == "bibtex")
4740 return createGuiBibtex(*this);
4741 if (name == "changes")
4742 return createGuiChanges(*this);
4743 if (name == "character")
4744 return createGuiCharacter(*this);
4745 if (name == "citation")
4746 return createGuiCitation(*this);
4747 if (name == "compare")
4748 return createGuiCompare(*this);
4749 if (name == "comparehistory")
4750 return createGuiCompareHistory(*this);
4751 if (name == "document")
4752 return createGuiDocument(*this);
4753 if (name == "errorlist")
4754 return createGuiErrorList(*this);
4755 if (name == "external")
4756 return createGuiExternal(*this);
4758 return createGuiShowFile(*this);
4759 if (name == "findreplace")
4760 return createGuiSearch(*this);
4761 if (name == "findreplaceadv")
4762 return createGuiSearchAdv(*this);
4763 if (name == "graphics")
4764 return createGuiGraphics(*this);
4765 if (name == "include")
4766 return createGuiInclude(*this);
4767 if (name == "index")
4768 return createGuiIndex(*this);
4769 if (name == "index_print")
4770 return createGuiPrintindex(*this);
4771 if (name == "listings")
4772 return createGuiListings(*this);
4774 return createGuiLog(*this);
4775 if (name == "mathdelimiter")
4776 return createGuiDelimiter(*this);
4777 if (name == "mathmatrix")
4778 return createGuiMathMatrix(*this);
4780 return createGuiNote(*this);
4781 if (name == "paragraph")
4782 return createGuiParagraph(*this);
4783 if (name == "phantom")
4784 return createGuiPhantom(*this);
4785 if (name == "prefs")
4786 return createGuiPreferences(*this);
4788 return createGuiRef(*this);
4789 if (name == "sendto")
4790 return createGuiSendTo(*this);
4791 if (name == "spellchecker")
4792 return createGuiSpellchecker(*this);
4793 if (name == "symbols")
4794 return createGuiSymbols(*this);
4795 if (name == "tabularcreate")
4796 return createGuiTabularCreate(*this);
4797 if (name == "texinfo")
4798 return createGuiTexInfo(*this);
4799 if (name == "thesaurus")
4800 return createGuiThesaurus(*this);
4802 return createGuiToc(*this);
4803 if (name == "view-source")
4804 return createGuiViewSource(*this);
4806 return createGuiWrap(*this);
4807 if (name == "progress")
4808 return createGuiProgressView(*this);
4814 SEMenu::SEMenu(QWidget * parent)
4816 QAction * action = addAction(qt_("Disable Shell Escape"));
4817 connect(action, SIGNAL(triggered()),
4818 parent, SLOT(disableShellEscape()));
4821 } // namespace frontend
4824 #include "moc_GuiView.cpp"