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 "LayoutFile.h"
58 #include "LyXAction.h"
62 #include "Paragraph.h"
63 #include "SpellChecker.h"
66 #include "TextClass.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/filetools.h"
76 #include "support/gettext.h"
77 #include "support/filetools.h"
78 #include "support/ForkedCalls.h"
79 #include "support/lassert.h"
80 #include "support/lstrings.h"
81 #include "support/os.h"
82 #include "support/Package.h"
83 #include "support/PathChanger.h"
84 #include "support/Systemcall.h"
85 #include "support/Timeout.h"
86 #include "support/ProgressInterface.h"
89 #include <QApplication>
90 #include <QCloseEvent>
92 #include <QDesktopWidget>
93 #include <QDragEnterEvent>
96 #include <QFutureWatcher>
106 #include <QPushButton>
107 #include <QScrollBar>
109 #include <QShowEvent>
111 #include <QStackedWidget>
112 #include <QStatusBar>
113 #include <QSvgRenderer>
114 #include <QtConcurrentRun>
122 // sync with GuiAlert.cpp
123 #define EXPORT_in_THREAD 1
126 #include "support/bind.h"
130 #ifdef HAVE_SYS_TIME_H
131 # include <sys/time.h>
139 using namespace lyx::support;
143 using support::addExtension;
144 using support::changeExtension;
145 using support::removeExtension;
151 class BackgroundWidget : public QWidget
154 BackgroundWidget(int width, int height)
155 : width_(width), height_(height)
157 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
158 if (!lyxrc.show_banner)
160 /// The text to be written on top of the pixmap
161 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
162 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
163 /// The text to be written on top of the pixmap
164 QString const text = lyx_version ?
165 qt_("version ") + lyx_version : qt_("unknown version");
166 #if QT_VERSION >= 0x050000
167 QString imagedir = "images/";
168 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
169 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
170 if (svgRenderer.isValid()) {
171 splash_ = QPixmap(splashSize());
172 QPainter painter(&splash_);
173 svgRenderer.render(&painter);
174 splash_.setDevicePixelRatio(pixelRatio());
176 splash_ = getPixmap("images/", "banner", "png");
179 splash_ = getPixmap("images/", "banner", "svgz,png");
182 QPainter pain(&splash_);
183 pain.setPen(QColor(0, 0, 0));
184 qreal const fsize = fontSize();
187 qreal locscale = htextsize.toFloat(&ok);
190 QPointF const position = textPosition(false);
191 QPointF const hposition = textPosition(true);
192 QRectF const hrect(hposition, splashSize());
194 "widget pixel ratio: " << pixelRatio() <<
195 " splash pixel ratio: " << splashPixelRatio() <<
196 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Bold);
201 font.setPointSizeF(fsize);
203 pain.drawText(position, text);
204 // The font used to display the version info
205 font.setStyleHint(QFont::SansSerif);
206 font.setWeight(QFont::Normal);
207 font.setPointSizeF(hfsize);
208 // Check how long the logo gets with the current font
209 // and adapt if the font is running wider than what
211 QFontMetrics fm(font);
212 // Split the title into lines to measure the longest line
213 // in the current l7n.
214 QStringList titlesegs = htext.split('\n');
216 int hline = fm.height();
217 QStringList::const_iterator sit;
218 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
219 if (fm.width(*sit) > wline)
220 wline = fm.width(*sit);
222 // The longest line in the reference font (for English)
223 // is 180. Calculate scale factor from that.
224 double const wscale = wline > 0 ? (180.0 / wline) : 1;
225 // Now do the same for the height (necessary for condensed fonts)
226 double const hscale = (34.0 / hline);
227 // take the lower of the two scale factors.
228 double const scale = min(wscale, hscale);
229 // Now rescale. Also consider l7n's offset factor.
230 font.setPointSizeF(hfsize * scale * locscale);
233 pain.drawText(hrect, Qt::AlignLeft, htext);
234 setFocusPolicy(Qt::StrongFocus);
237 void paintEvent(QPaintEvent *)
239 int const w = width_;
240 int const h = height_;
241 int const x = (width() - w) / 2;
242 int const y = (height() - h) / 2;
244 "widget pixel ratio: " << pixelRatio() <<
245 " splash pixel ratio: " << splashPixelRatio() <<
246 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
248 pain.drawPixmap(x, y, w, h, splash_);
251 void keyPressEvent(QKeyEvent * ev)
254 setKeySymbol(&sym, ev);
256 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
268 /// Current ratio between physical pixels and device-independent pixels
269 double pixelRatio() const {
270 #if QT_VERSION >= 0x050000
271 return qt_scale_factor * devicePixelRatio();
277 qreal fontSize() const {
278 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
281 QPointF textPosition(bool const heading) const {
282 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
283 : QPointF(width_/2 - 18, height_/2 + 45);
286 QSize splashSize() const {
288 static_cast<unsigned int>(width_ * pixelRatio()),
289 static_cast<unsigned int>(height_ * pixelRatio()));
292 /// Ratio between physical pixels and device-independent pixels of splash image
293 double splashPixelRatio() const {
294 #if QT_VERSION >= 0x050000
295 return splash_.devicePixelRatio();
303 /// Toolbar store providing access to individual toolbars by name.
304 typedef map<string, GuiToolbar *> ToolbarMap;
306 typedef shared_ptr<Dialog> DialogPtr;
309 // see https://wiki.qt.io/Clickable_QLabel
310 class QClickableLabel : public QLabel {
313 explicit QClickableLabel(QWidget * parent)
317 ~QClickableLabel() {}
323 void mousePressEvent(QMouseEvent *) {
331 class GuiView::GuiViewPrivate
334 GuiViewPrivate(GuiViewPrivate const &);
335 void operator=(GuiViewPrivate const &);
337 GuiViewPrivate(GuiView * gv)
338 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
339 layout_(0), autosave_timeout_(5000),
342 // hardcode here the platform specific icon size
343 smallIconSize = 16; // scaling problems
344 normalIconSize = 20; // ok, default if iconsize.png is missing
345 bigIconSize = 26; // better for some math icons
346 hugeIconSize = 32; // better for hires displays
349 // if it exists, use width of iconsize.png as normal size
350 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
351 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
353 QImage image(toqstr(fn.absFileName()));
354 if (image.width() < int(smallIconSize))
355 normalIconSize = smallIconSize;
356 else if (image.width() > int(giantIconSize))
357 normalIconSize = giantIconSize;
359 normalIconSize = image.width();
362 splitter_ = new QSplitter;
363 bg_widget_ = new BackgroundWidget(400, 250);
364 stack_widget_ = new QStackedWidget;
365 stack_widget_->addWidget(bg_widget_);
366 stack_widget_->addWidget(splitter_);
369 // TODO cleanup, remove the singleton, handle multiple Windows?
370 progress_ = ProgressInterface::instance();
371 if (!dynamic_cast<GuiProgress*>(progress_)) {
372 progress_ = new GuiProgress; // TODO who deletes it
373 ProgressInterface::setInstance(progress_);
376 dynamic_cast<GuiProgress*>(progress_),
377 SIGNAL(updateStatusBarMessage(QString const&)),
378 gv, SLOT(updateStatusBarMessage(QString const&)));
380 dynamic_cast<GuiProgress*>(progress_),
381 SIGNAL(clearMessageText()),
382 gv, SLOT(clearMessageText()));
389 delete stack_widget_;
394 stack_widget_->setCurrentWidget(bg_widget_);
395 bg_widget_->setUpdatesEnabled(true);
396 bg_widget_->setFocus();
399 int tabWorkAreaCount()
401 return splitter_->count();
404 TabWorkArea * tabWorkArea(int i)
406 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
409 TabWorkArea * currentTabWorkArea()
411 int areas = tabWorkAreaCount();
413 // The first TabWorkArea is always the first one, if any.
414 return tabWorkArea(0);
416 for (int i = 0; i != areas; ++i) {
417 TabWorkArea * twa = tabWorkArea(i);
418 if (current_main_work_area_ == twa->currentWorkArea())
422 // None has the focus so we just take the first one.
423 return tabWorkArea(0);
426 int countWorkAreasOf(Buffer & buf)
428 int areas = tabWorkAreaCount();
430 for (int i = 0; i != areas; ++i) {
431 TabWorkArea * twa = tabWorkArea(i);
432 if (twa->workArea(buf))
438 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
440 if (processing_thread_watcher_.isRunning()) {
441 // we prefer to cancel this preview in order to keep a snappy
445 processing_thread_watcher_.setFuture(f);
448 QSize iconSize(docstring const & icon_size)
451 if (icon_size == "small")
452 size = smallIconSize;
453 else if (icon_size == "normal")
454 size = normalIconSize;
455 else if (icon_size == "big")
457 else if (icon_size == "huge")
459 else if (icon_size == "giant")
460 size = giantIconSize;
462 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
464 if (size < smallIconSize)
465 size = smallIconSize;
467 return QSize(size, size);
470 QSize iconSize(QString const & icon_size)
472 return iconSize(qstring_to_ucs4(icon_size));
475 string & iconSize(QSize const & qsize)
477 LATTEST(qsize.width() == qsize.height());
479 static string icon_size;
481 unsigned int size = qsize.width();
483 if (size < smallIconSize)
484 size = smallIconSize;
486 if (size == smallIconSize)
488 else if (size == normalIconSize)
489 icon_size = "normal";
490 else if (size == bigIconSize)
492 else if (size == hugeIconSize)
494 else if (size == giantIconSize)
497 icon_size = convert<string>(size);
504 GuiWorkArea * current_work_area_;
505 GuiWorkArea * current_main_work_area_;
506 QSplitter * splitter_;
507 QStackedWidget * stack_widget_;
508 BackgroundWidget * bg_widget_;
510 ToolbarMap toolbars_;
511 ProgressInterface* progress_;
512 /// The main layout box.
514 * \warning Don't Delete! The layout box is actually owned by
515 * whichever toolbar contains it. All the GuiView class needs is a
516 * means of accessing it.
518 * FIXME: replace that with a proper model so that we are not limited
519 * to only one dialog.
524 map<string, DialogPtr> dialogs_;
526 unsigned int smallIconSize;
527 unsigned int normalIconSize;
528 unsigned int bigIconSize;
529 unsigned int hugeIconSize;
530 unsigned int giantIconSize;
532 QTimer statusbar_timer_;
533 /// auto-saving of buffers
534 Timeout autosave_timeout_;
535 /// flag against a race condition due to multiclicks, see bug #1119
539 TocModels toc_models_;
542 QFutureWatcher<docstring> autosave_watcher_;
543 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
545 string last_export_format;
546 string processing_format;
548 static QSet<Buffer const *> busyBuffers;
549 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
550 Buffer * buffer, string const & format);
551 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
552 Buffer * buffer, string const & format);
553 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
554 Buffer * buffer, string const & format);
555 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
558 static Buffer::ExportStatus runAndDestroy(const T& func,
559 Buffer const * orig, Buffer * buffer, string const & format);
561 // TODO syncFunc/previewFunc: use bind
562 bool asyncBufferProcessing(string const & argument,
563 Buffer const * used_buffer,
564 docstring const & msg,
565 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
566 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
567 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
570 QVector<GuiWorkArea*> guiWorkAreas();
573 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
576 GuiView::GuiView(int id)
577 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
578 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
581 connect(this, SIGNAL(bufferViewChanged()),
582 this, SLOT(onBufferViewChanged()));
584 // GuiToolbars *must* be initialised before the menu bar.
585 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
588 // set ourself as the current view. This is needed for the menu bar
589 // filling, at least for the static special menu item on Mac. Otherwise
590 // they are greyed out.
591 guiApp->setCurrentView(this);
593 // Fill up the menu bar.
594 guiApp->menus().fillMenuBar(menuBar(), this, true);
596 setCentralWidget(d.stack_widget_);
598 // Start autosave timer
599 if (lyxrc.autosave) {
600 // The connection is closed when this is destroyed.
601 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
602 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
603 d.autosave_timeout_.start();
605 connect(&d.statusbar_timer_, SIGNAL(timeout()),
606 this, SLOT(clearMessage()));
608 // We don't want to keep the window in memory if it is closed.
609 setAttribute(Qt::WA_DeleteOnClose, true);
611 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
612 // QIcon::fromTheme was introduced in Qt 4.6
613 #if (QT_VERSION >= 0x040600)
614 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
615 // since the icon is provided in the application bundle. We use a themed
616 // version when available and use the bundled one as fallback.
617 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
619 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
625 // use tabbed dock area for multiple docks
626 // (such as "source" and "messages")
627 setDockOptions(QMainWindow::ForceTabbedDocks);
630 setAcceptDrops(true);
632 // add busy indicator to statusbar
633 QClickableLabel * busylabel = new QClickableLabel(statusBar());
634 statusBar()->addPermanentWidget(busylabel);
635 search_mode mode = theGuiApp()->imageSearchMode();
636 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
637 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
638 busylabel->setMovie(busyanim);
642 connect(&d.processing_thread_watcher_, SIGNAL(started()),
643 busylabel, SLOT(show()));
644 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
645 busylabel, SLOT(hide()));
646 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkKillBackground()));
648 QFontMetrics const fm(statusBar()->fontMetrics());
649 int const iconheight = max(int(d.normalIconSize), fm.height());
650 QSize const iconsize(iconheight, iconheight);
652 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
653 shell_escape_ = new QLabel(statusBar());
654 shell_escape_->setPixmap(shellescape);
655 shell_escape_->setScaledContents(true);
656 shell_escape_->setAlignment(Qt::AlignCenter);
657 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
658 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
659 "external commands for this document. "
660 "Right click to change."));
661 SEMenu * menu = new SEMenu(this);
662 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
663 menu, SLOT(showMenu(QPoint)));
664 shell_escape_->hide();
665 statusBar()->addPermanentWidget(shell_escape_);
667 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
668 read_only_ = new QLabel(statusBar());
669 read_only_->setPixmap(readonly);
670 read_only_->setScaledContents(true);
671 read_only_->setAlignment(Qt::AlignCenter);
673 statusBar()->addPermanentWidget(read_only_);
675 version_control_ = new QLabel(statusBar());
676 version_control_->setAlignment(Qt::AlignCenter);
677 version_control_->setFrameStyle(QFrame::StyledPanel);
678 version_control_->hide();
679 statusBar()->addPermanentWidget(version_control_);
681 statusBar()->setSizeGripEnabled(true);
684 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
685 SLOT(autoSaveThreadFinished()));
687 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
688 SLOT(processingThreadStarted()));
689 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
690 SLOT(processingThreadFinished()));
692 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
693 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
695 // set custom application bars context menu, e.g. tool bar and menu bar
696 setContextMenuPolicy(Qt::CustomContextMenu);
697 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
698 SLOT(toolBarPopup(const QPoint &)));
700 // Forbid too small unresizable window because it can happen
701 // with some window manager under X11.
702 setMinimumSize(300, 200);
704 if (lyxrc.allow_geometry_session) {
705 // Now take care of session management.
710 // no session handling, default to a sane size.
711 setGeometry(50, 50, 690, 510);
714 // clear session data if any.
716 settings.remove("views");
726 void GuiView::disableShellEscape()
728 BufferView * bv = documentBufferView();
731 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
732 bv->buffer().params().shell_escape = false;
733 bv->processUpdateFlags(Update::Force);
737 void GuiView::checkKillBackground()
739 docstring const ttl = _("Cancel background process?");
740 docstring const msg = _("Do you want to cancel the background export process?");
742 Alert::prompt(ttl, msg, 1, 1, _("&Cancel Export"), _("Conti&nue"));
744 Systemcall::killscript();
748 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
750 QVector<GuiWorkArea*> areas;
751 for (int i = 0; i < tabWorkAreaCount(); i++) {
752 TabWorkArea* ta = tabWorkArea(i);
753 for (int u = 0; u < ta->count(); u++) {
754 areas << ta->workArea(u);
760 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
761 string const & format)
763 docstring const fmt = theFormats().prettyName(format);
766 case Buffer::ExportSuccess:
767 msg = bformat(_("Successful export to format: %1$s"), fmt);
769 case Buffer::ExportCancel:
770 msg = _("Document export cancelled.");
772 case Buffer::ExportError:
773 case Buffer::ExportNoPathToFormat:
774 case Buffer::ExportTexPathHasSpaces:
775 case Buffer::ExportConverterError:
776 msg = bformat(_("Error while exporting format: %1$s"), fmt);
778 case Buffer::PreviewSuccess:
779 msg = bformat(_("Successful preview of format: %1$s"), fmt);
781 case Buffer::PreviewError:
782 msg = bformat(_("Error while previewing format: %1$s"), fmt);
784 case Buffer::ExportKilled:
785 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
792 void GuiView::processingThreadStarted()
797 void GuiView::processingThreadFinished()
799 QFutureWatcher<Buffer::ExportStatus> const * watcher =
800 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
802 Buffer::ExportStatus const status = watcher->result();
803 handleExportStatus(this, status, d.processing_format);
806 BufferView const * const bv = currentBufferView();
807 if (bv && !bv->buffer().errorList("Export").empty()) {
812 bool const error = (status != Buffer::ExportSuccess &&
813 status != Buffer::PreviewSuccess &&
814 status != Buffer::ExportCancel);
816 ErrorList & el = bv->buffer().errorList(d.last_export_format);
817 // at this point, we do not know if buffer-view or
818 // master-buffer-view was called. If there was an export error,
819 // and the current buffer's error log is empty, we guess that
820 // it must be master-buffer-view that was called so we set
822 errors(d.last_export_format, el.empty());
827 void GuiView::autoSaveThreadFinished()
829 QFutureWatcher<docstring> const * watcher =
830 static_cast<QFutureWatcher<docstring> const *>(sender());
831 message(watcher->result());
836 void GuiView::saveLayout() const
839 settings.setValue("zoom_ratio", zoom_ratio_);
840 settings.setValue("devel_mode", devel_mode_);
841 settings.beginGroup("views");
842 settings.beginGroup(QString::number(id_));
843 #if defined(Q_WS_X11) || defined(QPA_XCB)
844 settings.setValue("pos", pos());
845 settings.setValue("size", size());
847 settings.setValue("geometry", saveGeometry());
849 settings.setValue("layout", saveState(0));
850 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
854 void GuiView::saveUISettings() const
858 // Save the toolbar private states
859 ToolbarMap::iterator end = d.toolbars_.end();
860 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
861 it->second->saveSession(settings);
862 // Now take care of all other dialogs
863 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
864 for (; it!= d.dialogs_.end(); ++it)
865 it->second->saveSession(settings);
869 bool GuiView::restoreLayout()
872 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
873 // Actual zoom value: default zoom + fractional offset
874 int zoom = lyxrc.defaultZoom * zoom_ratio_;
875 if (zoom < static_cast<int>(zoom_min_))
877 lyxrc.currentZoom = zoom;
878 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
879 settings.beginGroup("views");
880 settings.beginGroup(QString::number(id_));
881 QString const icon_key = "icon_size";
882 if (!settings.contains(icon_key))
885 //code below is skipped when when ~/.config/LyX is (re)created
886 setIconSize(d.iconSize(settings.value(icon_key).toString()));
888 #if defined(Q_WS_X11) || defined(QPA_XCB)
889 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
890 QSize size = settings.value("size", QSize(690, 510)).toSize();
894 // Work-around for bug #6034: the window ends up in an undetermined
895 // state when trying to restore a maximized window when it is
896 // already maximized.
897 if (!(windowState() & Qt::WindowMaximized))
898 if (!restoreGeometry(settings.value("geometry").toByteArray()))
899 setGeometry(50, 50, 690, 510);
901 // Make sure layout is correctly oriented.
902 setLayoutDirection(qApp->layoutDirection());
904 // Allow the toc and view-source dock widget to be restored if needed.
906 if ((dialog = findOrBuild("toc", true)))
907 // see bug 5082. At least setup title and enabled state.
908 // Visibility will be adjusted by restoreState below.
909 dialog->prepareView();
910 if ((dialog = findOrBuild("view-source", true)))
911 dialog->prepareView();
912 if ((dialog = findOrBuild("progress", true)))
913 dialog->prepareView();
915 if (!restoreState(settings.value("layout").toByteArray(), 0))
918 // init the toolbars that have not been restored
919 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
920 Toolbars::Infos::iterator end = guiApp->toolbars().end();
921 for (; cit != end; ++cit) {
922 GuiToolbar * tb = toolbar(cit->name);
923 if (tb && !tb->isRestored())
924 initToolbar(cit->name);
927 // update lock (all) toolbars positions
928 updateLockToolbars();
935 GuiToolbar * GuiView::toolbar(string const & name)
937 ToolbarMap::iterator it = d.toolbars_.find(name);
938 if (it != d.toolbars_.end())
941 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
946 void GuiView::updateLockToolbars()
948 toolbarsMovable_ = false;
949 for (ToolbarInfo const & info : guiApp->toolbars()) {
950 GuiToolbar * tb = toolbar(info.name);
951 if (tb && tb->isMovable())
952 toolbarsMovable_ = true;
957 void GuiView::constructToolbars()
959 ToolbarMap::iterator it = d.toolbars_.begin();
960 for (; it != d.toolbars_.end(); ++it)
964 // I don't like doing this here, but the standard toolbar
965 // destroys this object when it's destroyed itself (vfr)
966 d.layout_ = new LayoutBox(*this);
967 d.stack_widget_->addWidget(d.layout_);
968 d.layout_->move(0,0);
970 // extracts the toolbars from the backend
971 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
972 Toolbars::Infos::iterator end = guiApp->toolbars().end();
973 for (; cit != end; ++cit)
974 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
978 void GuiView::initToolbars()
980 // extracts the toolbars from the backend
981 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
982 Toolbars::Infos::iterator end = guiApp->toolbars().end();
983 for (; cit != end; ++cit)
984 initToolbar(cit->name);
988 void GuiView::initToolbar(string const & name)
990 GuiToolbar * tb = toolbar(name);
993 int const visibility = guiApp->toolbars().defaultVisibility(name);
994 bool newline = !(visibility & Toolbars::SAMEROW);
995 tb->setVisible(false);
996 tb->setVisibility(visibility);
998 if (visibility & Toolbars::TOP) {
1000 addToolBarBreak(Qt::TopToolBarArea);
1001 addToolBar(Qt::TopToolBarArea, tb);
1004 if (visibility & Toolbars::BOTTOM) {
1006 addToolBarBreak(Qt::BottomToolBarArea);
1007 addToolBar(Qt::BottomToolBarArea, tb);
1010 if (visibility & Toolbars::LEFT) {
1012 addToolBarBreak(Qt::LeftToolBarArea);
1013 addToolBar(Qt::LeftToolBarArea, tb);
1016 if (visibility & Toolbars::RIGHT) {
1018 addToolBarBreak(Qt::RightToolBarArea);
1019 addToolBar(Qt::RightToolBarArea, tb);
1022 if (visibility & Toolbars::ON)
1023 tb->setVisible(true);
1025 tb->setMovable(true);
1029 TocModels & GuiView::tocModels()
1031 return d.toc_models_;
1035 void GuiView::setFocus()
1037 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1038 QMainWindow::setFocus();
1042 bool GuiView::hasFocus() const
1044 if (currentWorkArea())
1045 return currentWorkArea()->hasFocus();
1046 if (currentMainWorkArea())
1047 return currentMainWorkArea()->hasFocus();
1048 return d.bg_widget_->hasFocus();
1052 void GuiView::focusInEvent(QFocusEvent * e)
1054 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1055 QMainWindow::focusInEvent(e);
1056 // Make sure guiApp points to the correct view.
1057 guiApp->setCurrentView(this);
1058 if (currentWorkArea())
1059 currentWorkArea()->setFocus();
1060 else if (currentMainWorkArea())
1061 currentMainWorkArea()->setFocus();
1063 d.bg_widget_->setFocus();
1067 void GuiView::showEvent(QShowEvent * e)
1069 LYXERR(Debug::GUI, "Passed Geometry "
1070 << size().height() << "x" << size().width()
1071 << "+" << pos().x() << "+" << pos().y());
1073 if (d.splitter_->count() == 0)
1074 // No work area, switch to the background widget.
1078 QMainWindow::showEvent(e);
1082 bool GuiView::closeScheduled()
1089 bool GuiView::prepareAllBuffersForLogout()
1091 Buffer * first = theBufferList().first();
1095 // First, iterate over all buffers and ask the users if unsaved
1096 // changes should be saved.
1097 // We cannot use a for loop as the buffer list cycles.
1100 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1102 b = theBufferList().next(b);
1103 } while (b != first);
1105 // Next, save session state
1106 // When a view/window was closed before without quitting LyX, there
1107 // are already entries in the lastOpened list.
1108 theSession().lastOpened().clear();
1115 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1116 ** is responsibility of the container (e.g., dialog)
1118 void GuiView::closeEvent(QCloseEvent * close_event)
1120 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1122 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1123 Alert::warning(_("Exit LyX"),
1124 _("LyX could not be closed because documents are being processed by LyX."));
1125 close_event->setAccepted(false);
1129 // If the user pressed the x (so we didn't call closeView
1130 // programmatically), we want to clear all existing entries.
1132 theSession().lastOpened().clear();
1137 // it can happen that this event arrives without selecting the view,
1138 // e.g. when clicking the close button on a background window.
1140 if (!closeWorkAreaAll()) {
1142 close_event->ignore();
1146 // Make sure that nothing will use this to be closed View.
1147 guiApp->unregisterView(this);
1149 if (isFullScreen()) {
1150 // Switch off fullscreen before closing.
1155 // Make sure the timer time out will not trigger a statusbar update.
1156 d.statusbar_timer_.stop();
1158 // Saving fullscreen requires additional tweaks in the toolbar code.
1159 // It wouldn't also work under linux natively.
1160 if (lyxrc.allow_geometry_session) {
1165 close_event->accept();
1169 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1171 if (event->mimeData()->hasUrls())
1173 /// \todo Ask lyx-devel is this is enough:
1174 /// if (event->mimeData()->hasFormat("text/plain"))
1175 /// event->acceptProposedAction();
1179 void GuiView::dropEvent(QDropEvent * event)
1181 QList<QUrl> files = event->mimeData()->urls();
1182 if (files.isEmpty())
1185 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1186 for (int i = 0; i != files.size(); ++i) {
1187 string const file = os::internal_path(fromqstr(
1188 files.at(i).toLocalFile()));
1192 string const ext = support::getExtension(file);
1193 vector<const Format *> found_formats;
1195 // Find all formats that have the correct extension.
1196 vector<const Format *> const & import_formats
1197 = theConverters().importableFormats();
1198 vector<const Format *>::const_iterator it = import_formats.begin();
1199 for (; it != import_formats.end(); ++it)
1200 if ((*it)->hasExtension(ext))
1201 found_formats.push_back(*it);
1204 if (found_formats.size() >= 1) {
1205 if (found_formats.size() > 1) {
1206 //FIXME: show a dialog to choose the correct importable format
1207 LYXERR(Debug::FILES,
1208 "Multiple importable formats found, selecting first");
1210 string const arg = found_formats[0]->name() + " " + file;
1211 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1214 //FIXME: do we have to explicitly check whether it's a lyx file?
1215 LYXERR(Debug::FILES,
1216 "No formats found, trying to open it as a lyx file");
1217 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1219 // add the functions to the queue
1220 guiApp->addToFuncRequestQueue(cmd);
1223 // now process the collected functions. We perform the events
1224 // asynchronously. This prevents potential problems in case the
1225 // BufferView is closed within an event.
1226 guiApp->processFuncRequestQueueAsync();
1230 void GuiView::message(docstring const & str)
1232 if (ForkedProcess::iAmAChild())
1235 // call is moved to GUI-thread by GuiProgress
1236 d.progress_->appendMessage(toqstr(str));
1240 void GuiView::clearMessageText()
1242 message(docstring());
1246 void GuiView::updateStatusBarMessage(QString const & str)
1248 statusBar()->showMessage(str);
1249 d.statusbar_timer_.stop();
1250 d.statusbar_timer_.start(3000);
1254 void GuiView::clearMessage()
1256 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1257 // the hasFocus function mostly returns false, even if the focus is on
1258 // a workarea in this view.
1262 d.statusbar_timer_.stop();
1266 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1268 if (wa != d.current_work_area_
1269 || wa->bufferView().buffer().isInternal())
1271 Buffer const & buf = wa->bufferView().buffer();
1272 // Set the windows title
1273 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1274 if (buf.notifiesExternalModification()) {
1275 title = bformat(_("%1$s (modified externally)"), title);
1276 // If the external modification status has changed, then maybe the status of
1277 // buffer-save has changed too.
1281 title += from_ascii(" - LyX");
1283 setWindowTitle(toqstr(title));
1284 // Sets the path for the window: this is used by OSX to
1285 // allow a context click on the title bar showing a menu
1286 // with the path up to the file
1287 setWindowFilePath(toqstr(buf.absFileName()));
1288 // Tell Qt whether the current document is changed
1289 setWindowModified(!buf.isClean());
1291 if (buf.params().shell_escape)
1292 shell_escape_->show();
1294 shell_escape_->hide();
1296 if (buf.hasReadonlyFlag())
1301 if (buf.lyxvc().inUse()) {
1302 version_control_->show();
1303 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1305 version_control_->hide();
1309 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1311 if (d.current_work_area_)
1312 // disconnect the current work area from all slots
1313 QObject::disconnect(d.current_work_area_, 0, this, 0);
1315 disconnectBufferView();
1316 connectBufferView(wa->bufferView());
1317 connectBuffer(wa->bufferView().buffer());
1318 d.current_work_area_ = wa;
1319 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1320 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1321 QObject::connect(wa, SIGNAL(busy(bool)),
1322 this, SLOT(setBusy(bool)));
1323 // connection of a signal to a signal
1324 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1325 this, SIGNAL(bufferViewChanged()));
1326 Q_EMIT updateWindowTitle(wa);
1327 Q_EMIT bufferViewChanged();
1331 void GuiView::onBufferViewChanged()
1334 // Buffer-dependent dialogs must be updated. This is done here because
1335 // some dialogs require buffer()->text.
1340 void GuiView::on_lastWorkAreaRemoved()
1343 // We already are in a close event. Nothing more to do.
1346 if (d.splitter_->count() > 1)
1347 // We have a splitter so don't close anything.
1350 // Reset and updates the dialogs.
1351 Q_EMIT bufferViewChanged();
1356 if (lyxrc.open_buffers_in_tabs)
1357 // Nothing more to do, the window should stay open.
1360 if (guiApp->viewIds().size() > 1) {
1366 // On Mac we also close the last window because the application stay
1367 // resident in memory. On other platforms we don't close the last
1368 // window because this would quit the application.
1374 void GuiView::updateStatusBar()
1376 // let the user see the explicit message
1377 if (d.statusbar_timer_.isActive())
1384 void GuiView::showMessage()
1388 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1389 if (msg.isEmpty()) {
1390 BufferView const * bv = currentBufferView();
1392 msg = toqstr(bv->cursor().currentState(devel_mode_));
1394 msg = qt_("Welcome to LyX!");
1396 statusBar()->showMessage(msg);
1400 bool GuiView::event(QEvent * e)
1404 // Useful debug code:
1405 //case QEvent::ActivationChange:
1406 //case QEvent::WindowDeactivate:
1407 //case QEvent::Paint:
1408 //case QEvent::Enter:
1409 //case QEvent::Leave:
1410 //case QEvent::HoverEnter:
1411 //case QEvent::HoverLeave:
1412 //case QEvent::HoverMove:
1413 //case QEvent::StatusTip:
1414 //case QEvent::DragEnter:
1415 //case QEvent::DragLeave:
1416 //case QEvent::Drop:
1419 case QEvent::WindowActivate: {
1420 GuiView * old_view = guiApp->currentView();
1421 if (this == old_view) {
1423 return QMainWindow::event(e);
1425 if (old_view && old_view->currentBufferView()) {
1426 // save current selection to the selection buffer to allow
1427 // middle-button paste in this window.
1428 cap::saveSelection(old_view->currentBufferView()->cursor());
1430 guiApp->setCurrentView(this);
1431 if (d.current_work_area_)
1432 on_currentWorkAreaChanged(d.current_work_area_);
1436 return QMainWindow::event(e);
1439 case QEvent::ShortcutOverride: {
1441 if (isFullScreen() && menuBar()->isHidden()) {
1442 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1443 // FIXME: we should also try to detect special LyX shortcut such as
1444 // Alt-P and Alt-M. Right now there is a hack in
1445 // GuiWorkArea::processKeySym() that hides again the menubar for
1447 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1449 return QMainWindow::event(e);
1452 return QMainWindow::event(e);
1456 return QMainWindow::event(e);
1460 void GuiView::resetWindowTitle()
1462 setWindowTitle(qt_("LyX"));
1465 bool GuiView::focusNextPrevChild(bool /*next*/)
1472 bool GuiView::busy() const
1478 void GuiView::setBusy(bool busy)
1480 bool const busy_before = busy_ > 0;
1481 busy ? ++busy_ : --busy_;
1482 if ((busy_ > 0) == busy_before)
1483 // busy state didn't change
1487 QApplication::setOverrideCursor(Qt::WaitCursor);
1490 QApplication::restoreOverrideCursor();
1495 void GuiView::resetCommandExecute()
1497 command_execute_ = false;
1502 double GuiView::pixelRatio() const
1504 #if QT_VERSION >= 0x050000
1505 return qt_scale_factor * devicePixelRatio();
1512 GuiWorkArea * GuiView::workArea(int index)
1514 if (TabWorkArea * twa = d.currentTabWorkArea())
1515 if (index < twa->count())
1516 return twa->workArea(index);
1521 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1523 if (currentWorkArea()
1524 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1525 return currentWorkArea();
1526 if (TabWorkArea * twa = d.currentTabWorkArea())
1527 return twa->workArea(buffer);
1532 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1534 // Automatically create a TabWorkArea if there are none yet.
1535 TabWorkArea * tab_widget = d.splitter_->count()
1536 ? d.currentTabWorkArea() : addTabWorkArea();
1537 return tab_widget->addWorkArea(buffer, *this);
1541 TabWorkArea * GuiView::addTabWorkArea()
1543 TabWorkArea * twa = new TabWorkArea;
1544 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1545 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1546 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1547 this, SLOT(on_lastWorkAreaRemoved()));
1549 d.splitter_->addWidget(twa);
1550 d.stack_widget_->setCurrentWidget(d.splitter_);
1555 GuiWorkArea const * GuiView::currentWorkArea() const
1557 return d.current_work_area_;
1561 GuiWorkArea * GuiView::currentWorkArea()
1563 return d.current_work_area_;
1567 GuiWorkArea const * GuiView::currentMainWorkArea() const
1569 if (!d.currentTabWorkArea())
1571 return d.currentTabWorkArea()->currentWorkArea();
1575 GuiWorkArea * GuiView::currentMainWorkArea()
1577 if (!d.currentTabWorkArea())
1579 return d.currentTabWorkArea()->currentWorkArea();
1583 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1585 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1587 d.current_work_area_ = 0;
1589 Q_EMIT bufferViewChanged();
1593 // FIXME: I've no clue why this is here and why it accesses
1594 // theGuiApp()->currentView, which might be 0 (bug 6464).
1595 // See also 27525 (vfr).
1596 if (theGuiApp()->currentView() == this
1597 && theGuiApp()->currentView()->currentWorkArea() == wa)
1600 if (currentBufferView())
1601 cap::saveSelection(currentBufferView()->cursor());
1603 theGuiApp()->setCurrentView(this);
1604 d.current_work_area_ = wa;
1606 // We need to reset this now, because it will need to be
1607 // right if the tabWorkArea gets reset in the for loop. We
1608 // will change it back if we aren't in that case.
1609 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1610 d.current_main_work_area_ = wa;
1612 for (int i = 0; i != d.splitter_->count(); ++i) {
1613 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1614 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1615 << ", Current main wa: " << currentMainWorkArea());
1620 d.current_main_work_area_ = old_cmwa;
1622 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1623 on_currentWorkAreaChanged(wa);
1624 BufferView & bv = wa->bufferView();
1625 bv.cursor().fixIfBroken();
1627 wa->setUpdatesEnabled(true);
1628 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1632 void GuiView::removeWorkArea(GuiWorkArea * wa)
1634 LASSERT(wa, return);
1635 if (wa == d.current_work_area_) {
1637 disconnectBufferView();
1638 d.current_work_area_ = 0;
1639 d.current_main_work_area_ = 0;
1642 bool found_twa = false;
1643 for (int i = 0; i != d.splitter_->count(); ++i) {
1644 TabWorkArea * twa = d.tabWorkArea(i);
1645 if (twa->removeWorkArea(wa)) {
1646 // Found in this tab group, and deleted the GuiWorkArea.
1648 if (twa->count() != 0) {
1649 if (d.current_work_area_ == 0)
1650 // This means that we are closing the current GuiWorkArea, so
1651 // switch to the next GuiWorkArea in the found TabWorkArea.
1652 setCurrentWorkArea(twa->currentWorkArea());
1654 // No more WorkAreas in this tab group, so delete it.
1661 // It is not a tabbed work area (i.e., the search work area), so it
1662 // should be deleted by other means.
1663 LASSERT(found_twa, return);
1665 if (d.current_work_area_ == 0) {
1666 if (d.splitter_->count() != 0) {
1667 TabWorkArea * twa = d.currentTabWorkArea();
1668 setCurrentWorkArea(twa->currentWorkArea());
1670 // No more work areas, switch to the background widget.
1671 setCurrentWorkArea(0);
1677 LayoutBox * GuiView::getLayoutDialog() const
1683 void GuiView::updateLayoutList()
1686 d.layout_->updateContents(false);
1690 void GuiView::updateToolbars()
1692 ToolbarMap::iterator end = d.toolbars_.end();
1693 if (d.current_work_area_) {
1695 if (d.current_work_area_->bufferView().cursor().inMathed()
1696 && !d.current_work_area_->bufferView().cursor().inRegexped())
1697 context |= Toolbars::MATH;
1698 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1699 context |= Toolbars::TABLE;
1700 if (currentBufferView()->buffer().areChangesPresent()
1701 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1702 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1703 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1704 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1705 context |= Toolbars::REVIEW;
1706 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1707 context |= Toolbars::MATHMACROTEMPLATE;
1708 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1709 context |= Toolbars::IPA;
1710 if (command_execute_)
1711 context |= Toolbars::MINIBUFFER;
1712 if (minibuffer_focus_) {
1713 context |= Toolbars::MINIBUFFER_FOCUS;
1714 minibuffer_focus_ = false;
1717 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1718 it->second->update(context);
1720 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1721 it->second->update();
1725 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1727 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1728 LASSERT(newBuffer, return);
1730 GuiWorkArea * wa = workArea(*newBuffer);
1733 newBuffer->masterBuffer()->updateBuffer();
1735 wa = addWorkArea(*newBuffer);
1736 // scroll to the position when the BufferView was last closed
1737 if (lyxrc.use_lastfilepos) {
1738 LastFilePosSection::FilePos filepos =
1739 theSession().lastFilePos().load(newBuffer->fileName());
1740 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1743 //Disconnect the old buffer...there's no new one.
1746 connectBuffer(*newBuffer);
1747 connectBufferView(wa->bufferView());
1749 setCurrentWorkArea(wa);
1753 void GuiView::connectBuffer(Buffer & buf)
1755 buf.setGuiDelegate(this);
1759 void GuiView::disconnectBuffer()
1761 if (d.current_work_area_)
1762 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1766 void GuiView::connectBufferView(BufferView & bv)
1768 bv.setGuiDelegate(this);
1772 void GuiView::disconnectBufferView()
1774 if (d.current_work_area_)
1775 d.current_work_area_->bufferView().setGuiDelegate(0);
1779 void GuiView::errors(string const & error_type, bool from_master)
1781 BufferView const * const bv = currentBufferView();
1785 ErrorList const & el = from_master ?
1786 bv->buffer().masterBuffer()->errorList(error_type) :
1787 bv->buffer().errorList(error_type);
1792 string err = error_type;
1794 err = "from_master|" + error_type;
1795 showDialog("errorlist", err);
1799 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1801 d.toc_models_.updateItem(toqstr(type), dit);
1805 void GuiView::structureChanged()
1807 // This is called from the Buffer, which has no way to ensure that cursors
1808 // in BufferView remain valid.
1809 if (documentBufferView())
1810 documentBufferView()->cursor().sanitize();
1811 // FIXME: This is slightly expensive, though less than the tocBackend update
1812 // (#9880). This also resets the view in the Toc Widget (#6675).
1813 d.toc_models_.reset(documentBufferView());
1814 // Navigator needs more than a simple update in this case. It needs to be
1816 updateDialog("toc", "");
1820 void GuiView::updateDialog(string const & name, string const & sdata)
1822 if (!isDialogVisible(name))
1825 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1826 if (it == d.dialogs_.end())
1829 Dialog * const dialog = it->second.get();
1830 if (dialog->isVisibleView())
1831 dialog->initialiseParams(sdata);
1835 BufferView * GuiView::documentBufferView()
1837 return currentMainWorkArea()
1838 ? ¤tMainWorkArea()->bufferView()
1843 BufferView const * GuiView::documentBufferView() const
1845 return currentMainWorkArea()
1846 ? ¤tMainWorkArea()->bufferView()
1851 BufferView * GuiView::currentBufferView()
1853 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1857 BufferView const * GuiView::currentBufferView() const
1859 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1863 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1864 Buffer const * orig, Buffer * clone)
1866 bool const success = clone->autoSave();
1868 busyBuffers.remove(orig);
1870 ? _("Automatic save done.")
1871 : _("Automatic save failed!");
1875 void GuiView::autoSave()
1877 LYXERR(Debug::INFO, "Running autoSave()");
1879 Buffer * buffer = documentBufferView()
1880 ? &documentBufferView()->buffer() : 0;
1882 resetAutosaveTimers();
1886 GuiViewPrivate::busyBuffers.insert(buffer);
1887 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1888 buffer, buffer->cloneBufferOnly());
1889 d.autosave_watcher_.setFuture(f);
1890 resetAutosaveTimers();
1894 void GuiView::resetAutosaveTimers()
1897 d.autosave_timeout_.restart();
1901 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1904 Buffer * buf = currentBufferView()
1905 ? ¤tBufferView()->buffer() : 0;
1906 Buffer * doc_buffer = documentBufferView()
1907 ? &(documentBufferView()->buffer()) : 0;
1910 /* In LyX/Mac, when a dialog is open, the menus of the
1911 application can still be accessed without giving focus to
1912 the main window. In this case, we want to disable the menu
1913 entries that are buffer-related.
1914 This code must not be used on Linux and Windows, since it
1915 would disable buffer-related entries when hovering over the
1916 menu (see bug #9574).
1918 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1924 // Check whether we need a buffer
1925 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1926 // no, exit directly
1927 flag.message(from_utf8(N_("Command not allowed with"
1928 "out any document open")));
1929 flag.setEnabled(false);
1933 if (cmd.origin() == FuncRequest::TOC) {
1934 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1935 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1936 flag.setEnabled(false);
1940 switch(cmd.action()) {
1941 case LFUN_BUFFER_IMPORT:
1944 case LFUN_MASTER_BUFFER_EXPORT:
1946 && (doc_buffer->parent() != 0
1947 || doc_buffer->hasChildren())
1948 && !d.processing_thread_watcher_.isRunning()
1949 // this launches a dialog, which would be in the wrong Buffer
1950 && !(::lyx::operator==(cmd.argument(), "custom"));
1953 case LFUN_MASTER_BUFFER_UPDATE:
1954 case LFUN_MASTER_BUFFER_VIEW:
1956 && (doc_buffer->parent() != 0
1957 || doc_buffer->hasChildren())
1958 && !d.processing_thread_watcher_.isRunning();
1961 case LFUN_BUFFER_UPDATE:
1962 case LFUN_BUFFER_VIEW: {
1963 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1967 string format = to_utf8(cmd.argument());
1968 if (cmd.argument().empty())
1969 format = doc_buffer->params().getDefaultOutputFormat();
1970 enable = doc_buffer->params().isExportable(format, true);
1974 case LFUN_BUFFER_RELOAD:
1975 enable = doc_buffer && !doc_buffer->isUnnamed()
1976 && doc_buffer->fileName().exists()
1977 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1980 case LFUN_BUFFER_CHILD_OPEN:
1981 enable = doc_buffer != 0;
1984 case LFUN_BUFFER_WRITE:
1985 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1988 //FIXME: This LFUN should be moved to GuiApplication.
1989 case LFUN_BUFFER_WRITE_ALL: {
1990 // We enable the command only if there are some modified buffers
1991 Buffer * first = theBufferList().first();
1996 // We cannot use a for loop as the buffer list is a cycle.
1998 if (!b->isClean()) {
2002 b = theBufferList().next(b);
2003 } while (b != first);
2007 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2008 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2011 case LFUN_BUFFER_EXPORT: {
2012 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2016 return doc_buffer->getStatus(cmd, flag);
2020 case LFUN_BUFFER_EXPORT_AS:
2021 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2026 case LFUN_BUFFER_WRITE_AS:
2027 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2028 enable = doc_buffer != 0;
2031 case LFUN_EXPORT_CANCEL:
2032 enable = d.processing_thread_watcher_.isRunning();
2035 case LFUN_BUFFER_CLOSE:
2036 case LFUN_VIEW_CLOSE:
2037 enable = doc_buffer != 0;
2040 case LFUN_BUFFER_CLOSE_ALL:
2041 enable = theBufferList().last() != theBufferList().first();
2044 case LFUN_BUFFER_CHKTEX: {
2045 // hide if we have no checktex command
2046 if (lyxrc.chktex_command.empty()) {
2047 flag.setUnknown(true);
2051 if (!doc_buffer || !doc_buffer->params().isLatex()
2052 || d.processing_thread_watcher_.isRunning()) {
2053 // grey out, don't hide
2061 case LFUN_VIEW_SPLIT:
2062 if (cmd.getArg(0) == "vertical")
2063 enable = doc_buffer && (d.splitter_->count() == 1 ||
2064 d.splitter_->orientation() == Qt::Vertical);
2066 enable = doc_buffer && (d.splitter_->count() == 1 ||
2067 d.splitter_->orientation() == Qt::Horizontal);
2070 case LFUN_TAB_GROUP_CLOSE:
2071 enable = d.tabWorkAreaCount() > 1;
2074 case LFUN_DEVEL_MODE_TOGGLE:
2075 flag.setOnOff(devel_mode_);
2078 case LFUN_TOOLBAR_TOGGLE: {
2079 string const name = cmd.getArg(0);
2080 if (GuiToolbar * t = toolbar(name))
2081 flag.setOnOff(t->isVisible());
2084 docstring const msg =
2085 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2091 case LFUN_TOOLBAR_MOVABLE: {
2092 string const name = cmd.getArg(0);
2093 // use negation since locked == !movable
2095 // toolbar name * locks all toolbars
2096 flag.setOnOff(!toolbarsMovable_);
2097 else if (GuiToolbar * t = toolbar(name))
2098 flag.setOnOff(!(t->isMovable()));
2101 docstring const msg =
2102 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2108 case LFUN_ICON_SIZE:
2109 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2112 case LFUN_DROP_LAYOUTS_CHOICE:
2116 case LFUN_UI_TOGGLE:
2117 flag.setOnOff(isFullScreen());
2120 case LFUN_DIALOG_DISCONNECT_INSET:
2123 case LFUN_DIALOG_HIDE:
2124 // FIXME: should we check if the dialog is shown?
2127 case LFUN_DIALOG_TOGGLE:
2128 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2131 case LFUN_DIALOG_SHOW: {
2132 string const name = cmd.getArg(0);
2134 enable = name == "aboutlyx"
2135 || name == "file" //FIXME: should be removed.
2136 || name == "lyxfiles"
2138 || name == "texinfo"
2139 || name == "progress"
2140 || name == "compare";
2141 else if (name == "character" || name == "symbols"
2142 || name == "mathdelimiter" || name == "mathmatrix") {
2143 if (!buf || buf->isReadonly())
2146 Cursor const & cur = currentBufferView()->cursor();
2147 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2150 else if (name == "latexlog")
2151 enable = FileName(doc_buffer->logName()).isReadableFile();
2152 else if (name == "spellchecker")
2153 enable = theSpellChecker()
2154 && !doc_buffer->isReadonly()
2155 && !doc_buffer->text().empty();
2156 else if (name == "vclog")
2157 enable = doc_buffer->lyxvc().inUse();
2161 case LFUN_DIALOG_UPDATE: {
2162 string const name = cmd.getArg(0);
2164 enable = name == "prefs";
2168 case LFUN_COMMAND_EXECUTE:
2170 case LFUN_MENU_OPEN:
2171 // Nothing to check.
2174 case LFUN_COMPLETION_INLINE:
2175 if (!d.current_work_area_
2176 || !d.current_work_area_->completer().inlinePossible(
2177 currentBufferView()->cursor()))
2181 case LFUN_COMPLETION_POPUP:
2182 if (!d.current_work_area_
2183 || !d.current_work_area_->completer().popupPossible(
2184 currentBufferView()->cursor()))
2189 if (!d.current_work_area_
2190 || !d.current_work_area_->completer().inlinePossible(
2191 currentBufferView()->cursor()))
2195 case LFUN_COMPLETION_ACCEPT:
2196 if (!d.current_work_area_
2197 || (!d.current_work_area_->completer().popupVisible()
2198 && !d.current_work_area_->completer().inlineVisible()
2199 && !d.current_work_area_->completer().completionAvailable()))
2203 case LFUN_COMPLETION_CANCEL:
2204 if (!d.current_work_area_
2205 || (!d.current_work_area_->completer().popupVisible()
2206 && !d.current_work_area_->completer().inlineVisible()))
2210 case LFUN_BUFFER_ZOOM_OUT:
2211 case LFUN_BUFFER_ZOOM_IN: {
2212 // only diff between these two is that the default for ZOOM_OUT
2214 bool const neg_zoom =
2215 convert<int>(cmd.argument()) < 0 ||
2216 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2217 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2218 docstring const msg =
2219 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2223 enable = doc_buffer;
2227 case LFUN_BUFFER_ZOOM: {
2228 bool const less_than_min_zoom =
2229 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2230 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2231 docstring const msg =
2232 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2237 enable = doc_buffer;
2241 case LFUN_BUFFER_MOVE_NEXT:
2242 case LFUN_BUFFER_MOVE_PREVIOUS:
2243 // we do not cycle when moving
2244 case LFUN_BUFFER_NEXT:
2245 case LFUN_BUFFER_PREVIOUS:
2246 // because we cycle, it doesn't matter whether on first or last
2247 enable = (d.currentTabWorkArea()->count() > 1);
2249 case LFUN_BUFFER_SWITCH:
2250 // toggle on the current buffer, but do not toggle off
2251 // the other ones (is that a good idea?)
2253 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2254 flag.setOnOff(true);
2257 case LFUN_VC_REGISTER:
2258 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2260 case LFUN_VC_RENAME:
2261 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2264 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2266 case LFUN_VC_CHECK_IN:
2267 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2269 case LFUN_VC_CHECK_OUT:
2270 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2272 case LFUN_VC_LOCKING_TOGGLE:
2273 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2274 && doc_buffer->lyxvc().lockingToggleEnabled();
2275 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2277 case LFUN_VC_REVERT:
2278 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2279 && !doc_buffer->hasReadonlyFlag();
2281 case LFUN_VC_UNDO_LAST:
2282 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2284 case LFUN_VC_REPO_UPDATE:
2285 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2287 case LFUN_VC_COMMAND: {
2288 if (cmd.argument().empty())
2290 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2294 case LFUN_VC_COMPARE:
2295 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2298 case LFUN_SERVER_GOTO_FILE_ROW:
2299 case LFUN_LYX_ACTIVATE:
2301 case LFUN_FORWARD_SEARCH:
2302 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2305 case LFUN_FILE_INSERT_PLAINTEXT:
2306 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2307 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2310 case LFUN_SPELLING_CONTINUOUSLY:
2311 flag.setOnOff(lyxrc.spellcheck_continuously);
2319 flag.setEnabled(false);
2325 static FileName selectTemplateFile()
2327 FileDialog dlg(qt_("Select template file"));
2328 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2329 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2331 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2332 QStringList(qt_("LyX Documents (*.lyx)")));
2334 if (result.first == FileDialog::Later)
2336 if (result.second.isEmpty())
2338 return FileName(fromqstr(result.second));
2342 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2346 Buffer * newBuffer = 0;
2348 newBuffer = checkAndLoadLyXFile(filename);
2349 } catch (ExceptionMessage const & e) {
2356 message(_("Document not loaded."));
2360 setBuffer(newBuffer);
2361 newBuffer->errors("Parse");
2364 theSession().lastFiles().add(filename);
2365 theSession().writeFile();
2372 void GuiView::openDocument(string const & fname)
2374 string initpath = lyxrc.document_path;
2376 if (documentBufferView()) {
2377 string const trypath = documentBufferView()->buffer().filePath();
2378 // If directory is writeable, use this as default.
2379 if (FileName(trypath).isDirWritable())
2385 if (fname.empty()) {
2386 FileDialog dlg(qt_("Select document to open"));
2387 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2388 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2390 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2391 FileDialog::Result result =
2392 dlg.open(toqstr(initpath), filter);
2394 if (result.first == FileDialog::Later)
2397 filename = fromqstr(result.second);
2399 // check selected filename
2400 if (filename.empty()) {
2401 message(_("Canceled."));
2407 // get absolute path of file and add ".lyx" to the filename if
2409 FileName const fullname =
2410 fileSearch(string(), filename, "lyx", support::may_not_exist);
2411 if (!fullname.empty())
2412 filename = fullname.absFileName();
2414 if (!fullname.onlyPath().isDirectory()) {
2415 Alert::warning(_("Invalid filename"),
2416 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2417 from_utf8(fullname.absFileName())));
2421 // if the file doesn't exist and isn't already open (bug 6645),
2422 // let the user create one
2423 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2424 !LyXVC::file_not_found_hook(fullname)) {
2425 // the user specifically chose this name. Believe him.
2426 Buffer * const b = newFile(filename, string(), true);
2432 docstring const disp_fn = makeDisplayPath(filename);
2433 message(bformat(_("Opening document %1$s..."), disp_fn));
2436 Buffer * buf = loadDocument(fullname);
2438 str2 = bformat(_("Document %1$s opened."), disp_fn);
2439 if (buf->lyxvc().inUse())
2440 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2441 " " + _("Version control detected.");
2443 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2448 // FIXME: clean that
2449 static bool import(GuiView * lv, FileName const & filename,
2450 string const & format, ErrorList & errorList)
2452 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2454 string loader_format;
2455 vector<string> loaders = theConverters().loaders();
2456 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2457 vector<string>::const_iterator it = loaders.begin();
2458 vector<string>::const_iterator en = loaders.end();
2459 for (; it != en; ++it) {
2460 if (!theConverters().isReachable(format, *it))
2463 string const tofile =
2464 support::changeExtension(filename.absFileName(),
2465 theFormats().extension(*it));
2466 if (theConverters().convert(0, filename, FileName(tofile),
2467 filename, format, *it, errorList) != Converters::SUCCESS)
2469 loader_format = *it;
2472 if (loader_format.empty()) {
2473 frontend::Alert::error(_("Couldn't import file"),
2474 bformat(_("No information for importing the format %1$s."),
2475 theFormats().prettyName(format)));
2479 loader_format = format;
2481 if (loader_format == "lyx") {
2482 Buffer * buf = lv->loadDocument(lyxfile);
2486 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2490 bool as_paragraphs = loader_format == "textparagraph";
2491 string filename2 = (loader_format == format) ? filename.absFileName()
2492 : support::changeExtension(filename.absFileName(),
2493 theFormats().extension(loader_format));
2494 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2496 guiApp->setCurrentView(lv);
2497 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2504 void GuiView::importDocument(string const & argument)
2507 string filename = split(argument, format, ' ');
2509 LYXERR(Debug::INFO, format << " file: " << filename);
2511 // need user interaction
2512 if (filename.empty()) {
2513 string initpath = lyxrc.document_path;
2514 if (documentBufferView()) {
2515 string const trypath = documentBufferView()->buffer().filePath();
2516 // If directory is writeable, use this as default.
2517 if (FileName(trypath).isDirWritable())
2521 docstring const text = bformat(_("Select %1$s file to import"),
2522 theFormats().prettyName(format));
2524 FileDialog dlg(toqstr(text));
2525 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2526 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2528 docstring filter = theFormats().prettyName(format);
2531 filter += from_utf8(theFormats().extensions(format));
2534 FileDialog::Result result =
2535 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2537 if (result.first == FileDialog::Later)
2540 filename = fromqstr(result.second);
2542 // check selected filename
2543 if (filename.empty())
2544 message(_("Canceled."));
2547 if (filename.empty())
2550 // get absolute path of file
2551 FileName const fullname(support::makeAbsPath(filename));
2553 // Can happen if the user entered a path into the dialog
2555 if (fullname.onlyFileName().empty()) {
2556 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2557 "Aborting import."),
2558 from_utf8(fullname.absFileName()));
2559 frontend::Alert::error(_("File name error"), msg);
2560 message(_("Canceled."));
2565 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2567 // Check if the document already is open
2568 Buffer * buf = theBufferList().getBuffer(lyxfile);
2571 if (!closeBuffer()) {
2572 message(_("Canceled."));
2577 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2579 // if the file exists already, and we didn't do
2580 // -i lyx thefile.lyx, warn
2581 if (lyxfile.exists() && fullname != lyxfile) {
2583 docstring text = bformat(_("The document %1$s already exists.\n\n"
2584 "Do you want to overwrite that document?"), displaypath);
2585 int const ret = Alert::prompt(_("Overwrite document?"),
2586 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2589 message(_("Canceled."));
2594 message(bformat(_("Importing %1$s..."), displaypath));
2595 ErrorList errorList;
2596 if (import(this, fullname, format, errorList))
2597 message(_("imported."));
2599 message(_("file not imported!"));
2601 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2605 void GuiView::newDocument(string const & filename, string templatefile,
2608 FileName initpath(lyxrc.document_path);
2609 if (documentBufferView()) {
2610 FileName const trypath(documentBufferView()->buffer().filePath());
2611 // If directory is writeable, use this as default.
2612 if (trypath.isDirWritable())
2616 if (from_template) {
2617 if (templatefile.empty())
2618 templatefile = selectTemplateFile().absFileName();
2619 if (templatefile.empty())
2624 if (filename.empty())
2625 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2627 b = newFile(filename, templatefile, true);
2632 // If no new document could be created, it is unsure
2633 // whether there is a valid BufferView.
2634 if (currentBufferView())
2635 // Ensure the cursor is correctly positioned on screen.
2636 currentBufferView()->showCursor();
2640 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2642 BufferView * bv = documentBufferView();
2647 FileName filename(to_utf8(fname));
2648 if (filename.empty()) {
2649 // Launch a file browser
2651 string initpath = lyxrc.document_path;
2652 string const trypath = bv->buffer().filePath();
2653 // If directory is writeable, use this as default.
2654 if (FileName(trypath).isDirWritable())
2658 FileDialog dlg(qt_("Select LyX document to insert"));
2659 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2660 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2662 FileDialog::Result result = dlg.open(toqstr(initpath),
2663 QStringList(qt_("LyX Documents (*.lyx)")));
2665 if (result.first == FileDialog::Later)
2669 filename.set(fromqstr(result.second));
2671 // check selected filename
2672 if (filename.empty()) {
2673 // emit message signal.
2674 message(_("Canceled."));
2679 bv->insertLyXFile(filename, ignorelang);
2680 bv->buffer().errors("Parse");
2684 string const GuiView::getTemplatesPath(Buffer & b)
2686 // We start off with the user's templates path
2687 string result = addPath(package().user_support().absFileName(), "templates");
2688 // Check for the document language
2689 string const langcode = b.params().language->code();
2690 string const shortcode = langcode.substr(0, 2);
2691 if (!langcode.empty() && shortcode != "en") {
2692 string subpath = addPath(result, shortcode);
2693 string subpath_long = addPath(result, langcode);
2694 // If we have a subdirectory for the language already,
2696 FileName sp = FileName(subpath);
2697 if (sp.isDirectory())
2699 else if (FileName(subpath_long).isDirectory())
2700 result = subpath_long;
2702 // Ask whether we should create such a subdirectory
2703 docstring const text =
2704 bformat(_("It is suggested to save the template in a subdirectory\n"
2705 "appropriate to the document language (%1$s).\n"
2706 "This subdirectory does not exists yet.\n"
2707 "Do you want to create it?"),
2708 _(b.params().language->display()));
2709 if (Alert::prompt(_("Create Language Directory?"),
2710 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2711 // If the user agreed, we try to create it and report if this failed.
2712 if (!sp.createDirectory(0777))
2713 Alert::error(_("Subdirectory creation failed!"),
2714 _("Could not create subdirectory.\n"
2715 "The template will be saved in the parent directory."));
2721 // Do we have a layout category?
2722 string const cat = b.params().baseClass() ?
2723 b.params().baseClass()->category()
2726 string subpath = addPath(result, cat);
2727 // If we have a subdirectory for the category already,
2729 FileName sp = FileName(subpath);
2730 if (sp.isDirectory())
2733 // Ask whether we should create such a subdirectory
2734 docstring const text =
2735 bformat(_("It is suggested to save the template in a subdirectory\n"
2736 "appropriate to the layout category (%1$s).\n"
2737 "This subdirectory does not exists yet.\n"
2738 "Do you want to create it?"),
2740 if (Alert::prompt(_("Create Category Directory?"),
2741 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2742 // If the user agreed, we try to create it and report if this failed.
2743 if (!sp.createDirectory(0777))
2744 Alert::error(_("Subdirectory creation failed!"),
2745 _("Could not create subdirectory.\n"
2746 "The template will be saved in the parent directory."));
2756 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2758 FileName fname = b.fileName();
2759 FileName const oldname = fname;
2760 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2762 if (!newname.empty()) {
2765 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2767 fname = support::makeAbsPath(to_utf8(newname),
2768 oldname.onlyPath().absFileName());
2770 // Switch to this Buffer.
2773 // No argument? Ask user through dialog.
2775 QString const title = as_template ? qt_("Choose a filename to save template as")
2776 : qt_("Choose a filename to save document as");
2777 FileDialog dlg(title);
2778 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2779 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2781 if (!isLyXFileName(fname.absFileName()))
2782 fname.changeExtension(".lyx");
2784 string const path = as_template ?
2786 : fname.onlyPath().absFileName();
2787 FileDialog::Result result =
2788 dlg.save(toqstr(path),
2789 QStringList(qt_("LyX Documents (*.lyx)")),
2790 toqstr(fname.onlyFileName()));
2792 if (result.first == FileDialog::Later)
2795 fname.set(fromqstr(result.second));
2800 if (!isLyXFileName(fname.absFileName()))
2801 fname.changeExtension(".lyx");
2804 // fname is now the new Buffer location.
2806 // if there is already a Buffer open with this name, we do not want
2807 // to have another one. (the second test makes sure we're not just
2808 // trying to overwrite ourselves, which is fine.)
2809 if (theBufferList().exists(fname) && fname != oldname
2810 && theBufferList().getBuffer(fname) != &b) {
2811 docstring const text =
2812 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2813 "Please close it before attempting to overwrite it.\n"
2814 "Do you want to choose a new filename?"),
2815 from_utf8(fname.absFileName()));
2816 int const ret = Alert::prompt(_("Chosen File Already Open"),
2817 text, 0, 1, _("&Rename"), _("&Cancel"));
2819 case 0: return renameBuffer(b, docstring(), kind);
2820 case 1: return false;
2825 bool const existsLocal = fname.exists();
2826 bool const existsInVC = LyXVC::fileInVC(fname);
2827 if (existsLocal || existsInVC) {
2828 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2829 if (kind != LV_WRITE_AS && existsInVC) {
2830 // renaming to a name that is already in VC
2832 docstring text = bformat(_("The document %1$s "
2833 "is already registered.\n\n"
2834 "Do you want to choose a new name?"),
2836 docstring const title = (kind == LV_VC_RENAME) ?
2837 _("Rename document?") : _("Copy document?");
2838 docstring const button = (kind == LV_VC_RENAME) ?
2839 _("&Rename") : _("&Copy");
2840 int const ret = Alert::prompt(title, text, 0, 1,
2841 button, _("&Cancel"));
2843 case 0: return renameBuffer(b, docstring(), kind);
2844 case 1: return false;
2849 docstring text = bformat(_("The document %1$s "
2850 "already exists.\n\n"
2851 "Do you want to overwrite that document?"),
2853 int const ret = Alert::prompt(_("Overwrite document?"),
2854 text, 0, 2, _("&Overwrite"),
2855 _("&Rename"), _("&Cancel"));
2858 case 1: return renameBuffer(b, docstring(), kind);
2859 case 2: return false;
2865 case LV_VC_RENAME: {
2866 string msg = b.lyxvc().rename(fname);
2869 message(from_utf8(msg));
2873 string msg = b.lyxvc().copy(fname);
2876 message(from_utf8(msg));
2880 case LV_WRITE_AS_TEMPLATE:
2883 // LyXVC created the file already in case of LV_VC_RENAME or
2884 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2885 // relative paths of included stuff right if we moved e.g. from
2886 // /a/b.lyx to /a/c/b.lyx.
2888 bool const saved = saveBuffer(b, fname);
2895 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2897 FileName fname = b.fileName();
2899 FileDialog dlg(qt_("Choose a filename to export the document as"));
2900 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2903 QString const anyformat = qt_("Guess from extension (*.*)");
2906 vector<Format const *> export_formats;
2907 for (Format const & f : theFormats())
2908 if (f.documentFormat())
2909 export_formats.push_back(&f);
2910 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2911 map<QString, string> fmap;
2914 for (Format const * f : export_formats) {
2915 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2916 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2918 from_ascii(f->extension())));
2919 types << loc_filter;
2920 fmap[loc_filter] = f->name();
2921 if (from_ascii(f->name()) == iformat) {
2922 filter = loc_filter;
2923 ext = f->extension();
2926 string ofname = fname.onlyFileName();
2928 ofname = support::changeExtension(ofname, ext);
2929 FileDialog::Result result =
2930 dlg.save(toqstr(fname.onlyPath().absFileName()),
2934 if (result.first != FileDialog::Chosen)
2938 fname.set(fromqstr(result.second));
2939 if (filter == anyformat)
2940 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2942 fmt_name = fmap[filter];
2943 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2944 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2946 if (fmt_name.empty() || fname.empty())
2949 // fname is now the new Buffer location.
2950 if (FileName(fname).exists()) {
2951 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2952 docstring text = bformat(_("The document %1$s already "
2953 "exists.\n\nDo you want to "
2954 "overwrite that document?"),
2956 int const ret = Alert::prompt(_("Overwrite document?"),
2957 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2960 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2961 case 2: return false;
2965 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2968 return dr.dispatched();
2972 bool GuiView::saveBuffer(Buffer & b)
2974 return saveBuffer(b, FileName());
2978 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2980 if (workArea(b) && workArea(b)->inDialogMode())
2983 if (fn.empty() && b.isUnnamed())
2984 return renameBuffer(b, docstring());
2986 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2988 theSession().lastFiles().add(b.fileName());
2989 theSession().writeFile();
2993 // Switch to this Buffer.
2996 // FIXME: we don't tell the user *WHY* the save failed !!
2997 docstring const file = makeDisplayPath(b.absFileName(), 30);
2998 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2999 "Do you want to rename the document and "
3000 "try again?"), file);
3001 int const ret = Alert::prompt(_("Rename and save?"),
3002 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3005 if (!renameBuffer(b, docstring()))
3014 return saveBuffer(b, fn);
3018 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3020 return closeWorkArea(wa, false);
3024 // We only want to close the buffer if it is not visible in other workareas
3025 // of the same view, nor in other views, and if this is not a child
3026 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3028 Buffer & buf = wa->bufferView().buffer();
3030 bool last_wa = d.countWorkAreasOf(buf) == 1
3031 && !inOtherView(buf) && !buf.parent();
3033 bool close_buffer = last_wa;
3036 if (lyxrc.close_buffer_with_last_view == "yes")
3038 else if (lyxrc.close_buffer_with_last_view == "no")
3039 close_buffer = false;
3042 if (buf.isUnnamed())
3043 file = from_utf8(buf.fileName().onlyFileName());
3045 file = buf.fileName().displayName(30);
3046 docstring const text = bformat(
3047 _("Last view on document %1$s is being closed.\n"
3048 "Would you like to close or hide the document?\n"
3050 "Hidden documents can be displayed back through\n"
3051 "the menu: View->Hidden->...\n"
3053 "To remove this question, set your preference in:\n"
3054 " Tools->Preferences->Look&Feel->UserInterface\n"
3056 int ret = Alert::prompt(_("Close or hide document?"),
3057 text, 0, 1, _("&Close"), _("&Hide"));
3058 close_buffer = (ret == 0);
3062 return closeWorkArea(wa, close_buffer);
3066 bool GuiView::closeBuffer()
3068 GuiWorkArea * wa = currentMainWorkArea();
3069 // coverity complained about this
3070 // it seems unnecessary, but perhaps is worth the check
3071 LASSERT(wa, return false);
3073 setCurrentWorkArea(wa);
3074 Buffer & buf = wa->bufferView().buffer();
3075 return closeWorkArea(wa, !buf.parent());
3079 void GuiView::writeSession() const {
3080 GuiWorkArea const * active_wa = currentMainWorkArea();
3081 for (int i = 0; i < d.splitter_->count(); ++i) {
3082 TabWorkArea * twa = d.tabWorkArea(i);
3083 for (int j = 0; j < twa->count(); ++j) {
3084 GuiWorkArea * wa = twa->workArea(j);
3085 Buffer & buf = wa->bufferView().buffer();
3086 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3092 bool GuiView::closeBufferAll()
3094 // Close the workareas in all other views
3095 QList<int> const ids = guiApp->viewIds();
3096 for (int i = 0; i != ids.size(); ++i) {
3097 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3101 // Close our own workareas
3102 if (!closeWorkAreaAll())
3105 // Now close the hidden buffers. We prevent hidden buffers from being
3106 // dirty, so we can just close them.
3107 theBufferList().closeAll();
3112 bool GuiView::closeWorkAreaAll()
3114 setCurrentWorkArea(currentMainWorkArea());
3116 // We might be in a situation that there is still a tabWorkArea, but
3117 // there are no tabs anymore. This can happen when we get here after a
3118 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3119 // many TabWorkArea's have no documents anymore.
3122 // We have to call count() each time, because it can happen that
3123 // more than one splitter will disappear in one iteration (bug 5998).
3124 while (d.splitter_->count() > empty_twa) {
3125 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3127 if (twa->count() == 0)
3130 setCurrentWorkArea(twa->currentWorkArea());
3131 if (!closeTabWorkArea(twa))
3139 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3144 Buffer & buf = wa->bufferView().buffer();
3146 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3147 Alert::warning(_("Close document"),
3148 _("Document could not be closed because it is being processed by LyX."));
3153 return closeBuffer(buf);
3155 if (!inMultiTabs(wa))
3156 if (!saveBufferIfNeeded(buf, true))
3164 bool GuiView::closeBuffer(Buffer & buf)
3166 bool success = true;
3167 ListOfBuffers clist = buf.getChildren();
3168 ListOfBuffers::const_iterator it = clist.begin();
3169 ListOfBuffers::const_iterator const bend = clist.end();
3170 for (; it != bend; ++it) {
3171 Buffer * child_buf = *it;
3172 if (theBufferList().isOthersChild(&buf, child_buf)) {
3173 child_buf->setParent(0);
3177 // FIXME: should we look in other tabworkareas?
3178 // ANSWER: I don't think so. I've tested, and if the child is
3179 // open in some other window, it closes without a problem.
3180 GuiWorkArea * child_wa = workArea(*child_buf);
3183 // If we are in a close_event all children will be closed in some time,
3184 // so no need to do it here. This will ensure that the children end up
3185 // in the session file in the correct order. If we close the master
3186 // buffer, we can close or release the child buffers here too.
3188 success = closeWorkArea(child_wa, true);
3192 // In this case the child buffer is open but hidden.
3193 // Even in this case, children can be dirty (e.g.,
3194 // after a label change in the master, see #11405).
3195 // Therefore, check this
3196 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3197 // If we are in a close_event all children will be closed in some time,
3198 // so no need to do it here. This will ensure that the children end up
3199 // in the session file in the correct order. If we close the master
3200 // buffer, we can close or release the child buffers here too.
3202 // Save dirty buffers also if closing_!
3203 if (saveBufferIfNeeded(*child_buf, false)) {
3204 child_buf->removeAutosaveFile();
3205 theBufferList().release(child_buf);
3207 // Saving of dirty children has been cancelled.
3208 // Cancel the whole process.
3215 // goto bookmark to update bookmark pit.
3216 // FIXME: we should update only the bookmarks related to this buffer!
3217 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3218 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3219 guiApp->gotoBookmark(i+1, false, false);
3221 if (saveBufferIfNeeded(buf, false)) {
3222 buf.removeAutosaveFile();
3223 theBufferList().release(&buf);
3227 // open all children again to avoid a crash because of dangling
3228 // pointers (bug 6603)
3234 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3236 while (twa == d.currentTabWorkArea()) {
3237 twa->setCurrentIndex(twa->count() - 1);
3239 GuiWorkArea * wa = twa->currentWorkArea();
3240 Buffer & b = wa->bufferView().buffer();
3242 // We only want to close the buffer if the same buffer is not visible
3243 // in another view, and if this is not a child and if we are closing
3244 // a view (not a tabgroup).
3245 bool const close_buffer =
3246 !inOtherView(b) && !b.parent() && closing_;
3248 if (!closeWorkArea(wa, close_buffer))
3255 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3257 if (buf.isClean() || buf.paragraphs().empty())
3260 // Switch to this Buffer.
3266 if (buf.isUnnamed()) {
3267 file = from_utf8(buf.fileName().onlyFileName());
3270 FileName filename = buf.fileName();
3272 file = filename.displayName(30);
3273 exists = filename.exists();
3276 // Bring this window to top before asking questions.
3281 if (hiding && buf.isUnnamed()) {
3282 docstring const text = bformat(_("The document %1$s has not been "
3283 "saved yet.\n\nDo you want to save "
3284 "the document?"), file);
3285 ret = Alert::prompt(_("Save new document?"),
3286 text, 0, 1, _("&Save"), _("&Cancel"));
3290 docstring const text = exists ?
3291 bformat(_("The document %1$s has unsaved changes."
3292 "\n\nDo you want to save the document or "
3293 "discard the changes?"), file) :
3294 bformat(_("The document %1$s has not been saved yet."
3295 "\n\nDo you want to save the document or "
3296 "discard it entirely?"), file);
3297 docstring const title = exists ?
3298 _("Save changed document?") : _("Save document?");
3299 ret = Alert::prompt(title, text, 0, 2,
3300 _("&Save"), _("&Discard"), _("&Cancel"));
3305 if (!saveBuffer(buf))
3309 // If we crash after this we could have no autosave file
3310 // but I guess this is really improbable (Jug).
3311 // Sometimes improbable things happen:
3312 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3313 // buf.removeAutosaveFile();
3315 // revert all changes
3326 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3328 Buffer & buf = wa->bufferView().buffer();
3330 for (int i = 0; i != d.splitter_->count(); ++i) {
3331 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3332 if (wa_ && wa_ != wa)
3335 return inOtherView(buf);
3339 bool GuiView::inOtherView(Buffer & buf)
3341 QList<int> const ids = guiApp->viewIds();
3343 for (int i = 0; i != ids.size(); ++i) {
3347 if (guiApp->view(ids[i]).workArea(buf))
3354 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3356 if (!documentBufferView())
3359 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3360 Buffer * const curbuf = &documentBufferView()->buffer();
3361 int nwa = twa->count();
3362 for (int i = 0; i < nwa; ++i) {
3363 if (&workArea(i)->bufferView().buffer() == curbuf) {
3365 if (np == NEXTBUFFER)
3366 next_index = (i == nwa - 1 ? 0 : i + 1);
3368 next_index = (i == 0 ? nwa - 1 : i - 1);
3370 twa->moveTab(i, next_index);
3372 setBuffer(&workArea(next_index)->bufferView().buffer());
3380 /// make sure the document is saved
3381 static bool ensureBufferClean(Buffer * buffer)
3383 LASSERT(buffer, return false);
3384 if (buffer->isClean() && !buffer->isUnnamed())
3387 docstring const file = buffer->fileName().displayName(30);
3390 if (!buffer->isUnnamed()) {
3391 text = bformat(_("The document %1$s has unsaved "
3392 "changes.\n\nDo you want to save "
3393 "the document?"), file);
3394 title = _("Save changed document?");
3397 text = bformat(_("The document %1$s has not been "
3398 "saved yet.\n\nDo you want to save "
3399 "the document?"), file);
3400 title = _("Save new document?");
3402 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3405 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3407 return buffer->isClean() && !buffer->isUnnamed();
3411 bool GuiView::reloadBuffer(Buffer & buf)
3413 currentBufferView()->cursor().reset();
3414 Buffer::ReadStatus status = buf.reload();
3415 return status == Buffer::ReadSuccess;
3419 void GuiView::checkExternallyModifiedBuffers()
3421 BufferList::iterator bit = theBufferList().begin();
3422 BufferList::iterator const bend = theBufferList().end();
3423 for (; bit != bend; ++bit) {
3424 Buffer * buf = *bit;
3425 if (buf->fileName().exists() && buf->isChecksumModified()) {
3426 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3427 " Reload now? Any local changes will be lost."),
3428 from_utf8(buf->absFileName()));
3429 int const ret = Alert::prompt(_("Reload externally changed document?"),
3430 text, 0, 1, _("&Reload"), _("&Cancel"));
3438 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3440 Buffer * buffer = documentBufferView()
3441 ? &(documentBufferView()->buffer()) : 0;
3443 switch (cmd.action()) {
3444 case LFUN_VC_REGISTER:
3445 if (!buffer || !ensureBufferClean(buffer))
3447 if (!buffer->lyxvc().inUse()) {
3448 if (buffer->lyxvc().registrer()) {
3449 reloadBuffer(*buffer);
3450 dr.clearMessageUpdate();
3455 case LFUN_VC_RENAME:
3456 case LFUN_VC_COPY: {
3457 if (!buffer || !ensureBufferClean(buffer))
3459 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3460 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3461 // Some changes are not yet committed.
3462 // We test here and not in getStatus(), since
3463 // this test is expensive.
3465 LyXVC::CommandResult ret =
3466 buffer->lyxvc().checkIn(log);
3468 if (ret == LyXVC::ErrorCommand ||
3469 ret == LyXVC::VCSuccess)
3470 reloadBuffer(*buffer);
3471 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3472 frontend::Alert::error(
3473 _("Revision control error."),
3474 _("Document could not be checked in."));
3478 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3479 LV_VC_RENAME : LV_VC_COPY;
3480 renameBuffer(*buffer, cmd.argument(), kind);
3485 case LFUN_VC_CHECK_IN:
3486 if (!buffer || !ensureBufferClean(buffer))
3488 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3490 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3492 // Only skip reloading if the checkin was cancelled or
3493 // an error occurred before the real checkin VCS command
3494 // was executed, since the VCS might have changed the
3495 // file even if it could not checkin successfully.
3496 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3497 reloadBuffer(*buffer);
3501 case LFUN_VC_CHECK_OUT:
3502 if (!buffer || !ensureBufferClean(buffer))
3504 if (buffer->lyxvc().inUse()) {
3505 dr.setMessage(buffer->lyxvc().checkOut());
3506 reloadBuffer(*buffer);
3510 case LFUN_VC_LOCKING_TOGGLE:
3511 LASSERT(buffer, return);
3512 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3514 if (buffer->lyxvc().inUse()) {
3515 string res = buffer->lyxvc().lockingToggle();
3517 frontend::Alert::error(_("Revision control error."),
3518 _("Error when setting the locking property."));
3521 reloadBuffer(*buffer);
3526 case LFUN_VC_REVERT:
3527 LASSERT(buffer, return);
3528 if (buffer->lyxvc().revert()) {
3529 reloadBuffer(*buffer);
3530 dr.clearMessageUpdate();
3534 case LFUN_VC_UNDO_LAST:
3535 LASSERT(buffer, return);
3536 buffer->lyxvc().undoLast();
3537 reloadBuffer(*buffer);
3538 dr.clearMessageUpdate();
3541 case LFUN_VC_REPO_UPDATE:
3542 LASSERT(buffer, return);
3543 if (ensureBufferClean(buffer)) {
3544 dr.setMessage(buffer->lyxvc().repoUpdate());
3545 checkExternallyModifiedBuffers();
3549 case LFUN_VC_COMMAND: {
3550 string flag = cmd.getArg(0);
3551 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3554 if (contains(flag, 'M')) {
3555 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3558 string path = cmd.getArg(1);
3559 if (contains(path, "$$p") && buffer)
3560 path = subst(path, "$$p", buffer->filePath());
3561 LYXERR(Debug::LYXVC, "Directory: " << path);
3563 if (!pp.isReadableDirectory()) {
3564 lyxerr << _("Directory is not accessible.") << endl;
3567 support::PathChanger p(pp);
3569 string command = cmd.getArg(2);
3570 if (command.empty())
3573 command = subst(command, "$$i", buffer->absFileName());
3574 command = subst(command, "$$p", buffer->filePath());
3576 command = subst(command, "$$m", to_utf8(message));
3577 LYXERR(Debug::LYXVC, "Command: " << command);
3579 one.startscript(Systemcall::Wait, command);
3583 if (contains(flag, 'I'))
3584 buffer->markDirty();
3585 if (contains(flag, 'R'))
3586 reloadBuffer(*buffer);
3591 case LFUN_VC_COMPARE: {
3592 if (cmd.argument().empty()) {
3593 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3597 string rev1 = cmd.getArg(0);
3602 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3605 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3606 f2 = buffer->absFileName();
3608 string rev2 = cmd.getArg(1);
3612 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3616 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3617 f1 << "\n" << f2 << "\n" );
3618 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3619 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3629 void GuiView::openChildDocument(string const & fname)
3631 LASSERT(documentBufferView(), return);
3632 Buffer & buffer = documentBufferView()->buffer();
3633 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3634 documentBufferView()->saveBookmark(false);
3636 if (theBufferList().exists(filename)) {
3637 child = theBufferList().getBuffer(filename);
3640 message(bformat(_("Opening child document %1$s..."),
3641 makeDisplayPath(filename.absFileName())));
3642 child = loadDocument(filename, false);
3644 // Set the parent name of the child document.
3645 // This makes insertion of citations and references in the child work,
3646 // when the target is in the parent or another child document.
3648 child->setParent(&buffer);
3652 bool GuiView::goToFileRow(string const & argument)
3656 size_t i = argument.find_last_of(' ');
3657 if (i != string::npos) {
3658 file_name = os::internal_path(trim(argument.substr(0, i)));
3659 istringstream is(argument.substr(i + 1));
3664 if (i == string::npos) {
3665 LYXERR0("Wrong argument: " << argument);
3669 string const abstmp = package().temp_dir().absFileName();
3670 string const realtmp = package().temp_dir().realPath();
3671 // We have to use os::path_prefix_is() here, instead of
3672 // simply prefixIs(), because the file name comes from
3673 // an external application and may need case adjustment.
3674 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3675 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3676 // Needed by inverse dvi search. If it is a file
3677 // in tmpdir, call the apropriated function.
3678 // If tmpdir is a symlink, we may have the real
3679 // path passed back, so we correct for that.
3680 if (!prefixIs(file_name, abstmp))
3681 file_name = subst(file_name, realtmp, abstmp);
3682 buf = theBufferList().getBufferFromTmp(file_name);
3684 // Must replace extension of the file to be .lyx
3685 // and get full path
3686 FileName const s = fileSearch(string(),
3687 support::changeExtension(file_name, ".lyx"), "lyx");
3688 // Either change buffer or load the file
3689 if (theBufferList().exists(s))
3690 buf = theBufferList().getBuffer(s);
3691 else if (s.exists()) {
3692 buf = loadDocument(s);
3697 _("File does not exist: %1$s"),
3698 makeDisplayPath(file_name)));
3704 _("No buffer for file: %1$s."),
3705 makeDisplayPath(file_name))
3710 bool success = documentBufferView()->setCursorFromRow(row);
3712 LYXERR(Debug::LATEX,
3713 "setCursorFromRow: invalid position for row " << row);
3714 frontend::Alert::error(_("Inverse Search Failed"),
3715 _("Invalid position requested by inverse search.\n"
3716 "You may need to update the viewed document."));
3722 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3724 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3725 menu->exec(QCursor::pos());
3730 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3731 Buffer const * orig, Buffer * clone, string const & format)
3733 Buffer::ExportStatus const status = func(format);
3735 // the cloning operation will have produced a clone of the entire set of
3736 // documents, starting from the master. so we must delete those.
3737 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3739 busyBuffers.remove(orig);
3744 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3745 Buffer const * orig, Buffer * clone, string const & format)
3747 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3749 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3753 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3754 Buffer const * orig, Buffer * clone, string const & format)
3756 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3758 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3762 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3763 Buffer const * orig, Buffer * clone, string const & format)
3765 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3767 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3771 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3772 string const & argument,
3773 Buffer const * used_buffer,
3774 docstring const & msg,
3775 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3776 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3777 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3783 string format = argument;
3785 format = used_buffer->params().getDefaultOutputFormat();
3786 processing_format = format;
3788 progress_->clearMessages();
3791 #if EXPORT_in_THREAD
3793 GuiViewPrivate::busyBuffers.insert(used_buffer);
3794 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3795 if (!cloned_buffer) {
3796 Alert::error(_("Export Error"),
3797 _("Error cloning the Buffer."));
3800 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3805 setPreviewFuture(f);
3806 last_export_format = used_buffer->params().bufferFormat();
3809 // We are asynchronous, so we don't know here anything about the success
3812 Buffer::ExportStatus status;
3814 status = (used_buffer->*syncFunc)(format, false);
3815 } else if (previewFunc) {
3816 status = (used_buffer->*previewFunc)(format);
3819 handleExportStatus(gv_, status, format);
3821 return (status == Buffer::ExportSuccess
3822 || status == Buffer::PreviewSuccess);
3826 Buffer::ExportStatus status;
3828 status = (used_buffer->*syncFunc)(format, true);
3829 } else if (previewFunc) {
3830 status = (used_buffer->*previewFunc)(format);
3833 handleExportStatus(gv_, status, format);
3835 return (status == Buffer::ExportSuccess
3836 || status == Buffer::PreviewSuccess);
3840 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3842 BufferView * bv = currentBufferView();
3843 LASSERT(bv, return);
3845 // Let the current BufferView dispatch its own actions.
3846 bv->dispatch(cmd, dr);
3847 if (dr.dispatched()) {
3848 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3849 updateDialog("document", "");
3853 // Try with the document BufferView dispatch if any.
3854 BufferView * doc_bv = documentBufferView();
3855 if (doc_bv && doc_bv != bv) {
3856 doc_bv->dispatch(cmd, dr);
3857 if (dr.dispatched()) {
3858 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3859 updateDialog("document", "");
3864 // Then let the current Cursor dispatch its own actions.
3865 bv->cursor().dispatch(cmd);
3867 // update completion. We do it here and not in
3868 // processKeySym to avoid another redraw just for a
3869 // changed inline completion
3870 if (cmd.origin() == FuncRequest::KEYBOARD) {
3871 if (cmd.action() == LFUN_SELF_INSERT
3872 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3873 updateCompletion(bv->cursor(), true, true);
3874 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3875 updateCompletion(bv->cursor(), false, true);
3877 updateCompletion(bv->cursor(), false, false);
3880 dr = bv->cursor().result();
3884 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3886 BufferView * bv = currentBufferView();
3887 // By default we won't need any update.
3888 dr.screenUpdate(Update::None);
3889 // assume cmd will be dispatched
3890 dr.dispatched(true);
3892 Buffer * doc_buffer = documentBufferView()
3893 ? &(documentBufferView()->buffer()) : 0;
3895 if (cmd.origin() == FuncRequest::TOC) {
3896 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3897 // FIXME: do we need to pass a DispatchResult object here?
3898 toc->doDispatch(bv->cursor(), cmd);
3902 string const argument = to_utf8(cmd.argument());
3904 switch(cmd.action()) {
3905 case LFUN_BUFFER_CHILD_OPEN:
3906 openChildDocument(to_utf8(cmd.argument()));
3909 case LFUN_BUFFER_IMPORT:
3910 importDocument(to_utf8(cmd.argument()));
3913 case LFUN_MASTER_BUFFER_EXPORT:
3915 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3917 case LFUN_BUFFER_EXPORT: {
3920 // GCC only sees strfwd.h when building merged
3921 if (::lyx::operator==(cmd.argument(), "custom")) {
3922 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3923 // so the following test should not be needed.
3924 // In principle, we could try to switch to such a view...
3925 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3926 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3930 string const dest = cmd.getArg(1);
3931 FileName target_dir;
3932 if (!dest.empty() && FileName::isAbsolute(dest))
3933 target_dir = FileName(support::onlyPath(dest));
3935 target_dir = doc_buffer->fileName().onlyPath();
3937 string const format = (argument.empty() || argument == "default") ?
3938 doc_buffer->params().getDefaultOutputFormat() : argument;
3940 if ((dest.empty() && doc_buffer->isUnnamed())
3941 || !target_dir.isDirWritable()) {
3942 exportBufferAs(*doc_buffer, from_utf8(format));
3945 /* TODO/Review: Is it a problem to also export the children?
3946 See the update_unincluded flag */
3947 d.asyncBufferProcessing(format,
3950 &GuiViewPrivate::exportAndDestroy,
3952 0, cmd.allowAsync());
3953 // TODO Inform user about success
3957 case LFUN_BUFFER_EXPORT_AS: {
3958 LASSERT(doc_buffer, break);
3959 docstring f = cmd.argument();
3961 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3962 exportBufferAs(*doc_buffer, f);
3966 case LFUN_BUFFER_UPDATE: {
3967 d.asyncBufferProcessing(argument,
3970 &GuiViewPrivate::compileAndDestroy,
3972 0, cmd.allowAsync());
3975 case LFUN_BUFFER_VIEW: {
3976 d.asyncBufferProcessing(argument,
3978 _("Previewing ..."),
3979 &GuiViewPrivate::previewAndDestroy,
3981 &Buffer::preview, cmd.allowAsync());
3984 case LFUN_MASTER_BUFFER_UPDATE: {
3985 d.asyncBufferProcessing(argument,
3986 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3988 &GuiViewPrivate::compileAndDestroy,
3990 0, cmd.allowAsync());
3993 case LFUN_MASTER_BUFFER_VIEW: {
3994 d.asyncBufferProcessing(argument,
3995 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3997 &GuiViewPrivate::previewAndDestroy,
3998 0, &Buffer::preview, cmd.allowAsync());
4001 case LFUN_EXPORT_CANCEL: {
4002 Systemcall::killscript();
4005 case LFUN_BUFFER_SWITCH: {
4006 string const file_name = to_utf8(cmd.argument());
4007 if (!FileName::isAbsolute(file_name)) {
4009 dr.setMessage(_("Absolute filename expected."));
4013 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4016 dr.setMessage(_("Document not loaded"));
4020 // Do we open or switch to the buffer in this view ?
4021 if (workArea(*buffer)
4022 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4027 // Look for the buffer in other views
4028 QList<int> const ids = guiApp->viewIds();
4030 for (; i != ids.size(); ++i) {
4031 GuiView & gv = guiApp->view(ids[i]);
4032 if (gv.workArea(*buffer)) {
4034 gv.activateWindow();
4036 gv.setBuffer(buffer);
4041 // If necessary, open a new window as a last resort
4042 if (i == ids.size()) {
4043 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4049 case LFUN_BUFFER_NEXT:
4050 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4053 case LFUN_BUFFER_MOVE_NEXT:
4054 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4057 case LFUN_BUFFER_PREVIOUS:
4058 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4061 case LFUN_BUFFER_MOVE_PREVIOUS:
4062 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4065 case LFUN_BUFFER_CHKTEX:
4066 LASSERT(doc_buffer, break);
4067 doc_buffer->runChktex();
4070 case LFUN_COMMAND_EXECUTE: {
4071 command_execute_ = true;
4072 minibuffer_focus_ = true;
4075 case LFUN_DROP_LAYOUTS_CHOICE:
4076 d.layout_->showPopup();
4079 case LFUN_MENU_OPEN:
4080 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4081 menu->exec(QCursor::pos());
4084 case LFUN_FILE_INSERT: {
4085 if (cmd.getArg(1) == "ignorelang")
4086 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4088 insertLyXFile(cmd.argument());
4092 case LFUN_FILE_INSERT_PLAINTEXT:
4093 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4094 string const fname = to_utf8(cmd.argument());
4095 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4096 dr.setMessage(_("Absolute filename expected."));
4100 FileName filename(fname);
4101 if (fname.empty()) {
4102 FileDialog dlg(qt_("Select file to insert"));
4104 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4105 QStringList(qt_("All Files (*)")));
4107 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4108 dr.setMessage(_("Canceled."));
4112 filename.set(fromqstr(result.second));
4116 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4117 bv->dispatch(new_cmd, dr);
4122 case LFUN_BUFFER_RELOAD: {
4123 LASSERT(doc_buffer, break);
4126 bool drop = (cmd.argument() == "dump");
4129 if (!drop && !doc_buffer->isClean()) {
4130 docstring const file =
4131 makeDisplayPath(doc_buffer->absFileName(), 20);
4132 if (doc_buffer->notifiesExternalModification()) {
4133 docstring text = _("The current version will be lost. "
4134 "Are you sure you want to load the version on disk "
4135 "of the document %1$s?");
4136 ret = Alert::prompt(_("Reload saved document?"),
4137 bformat(text, file), 1, 1,
4138 _("&Reload"), _("&Cancel"));
4140 docstring text = _("Any changes will be lost. "
4141 "Are you sure you want to revert to the saved version "
4142 "of the document %1$s?");
4143 ret = Alert::prompt(_("Revert to saved document?"),
4144 bformat(text, file), 1, 1,
4145 _("&Revert"), _("&Cancel"));
4150 doc_buffer->markClean();
4151 reloadBuffer(*doc_buffer);
4152 dr.forceBufferUpdate();
4157 case LFUN_BUFFER_WRITE:
4158 LASSERT(doc_buffer, break);
4159 saveBuffer(*doc_buffer);
4162 case LFUN_BUFFER_WRITE_AS:
4163 LASSERT(doc_buffer, break);
4164 renameBuffer(*doc_buffer, cmd.argument());
4167 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4168 LASSERT(doc_buffer, break);
4169 renameBuffer(*doc_buffer, cmd.argument(),
4170 LV_WRITE_AS_TEMPLATE);
4173 case LFUN_BUFFER_WRITE_ALL: {
4174 Buffer * first = theBufferList().first();
4177 message(_("Saving all documents..."));
4178 // We cannot use a for loop as the buffer list cycles.
4181 if (!b->isClean()) {
4183 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4185 b = theBufferList().next(b);
4186 } while (b != first);
4187 dr.setMessage(_("All documents saved."));
4191 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4192 LASSERT(doc_buffer, break);
4193 doc_buffer->clearExternalModification();
4196 case LFUN_BUFFER_CLOSE:
4200 case LFUN_BUFFER_CLOSE_ALL:
4204 case LFUN_DEVEL_MODE_TOGGLE:
4205 devel_mode_ = !devel_mode_;
4207 dr.setMessage(_("Developer mode is now enabled."));
4209 dr.setMessage(_("Developer mode is now disabled."));
4212 case LFUN_TOOLBAR_TOGGLE: {
4213 string const name = cmd.getArg(0);
4214 if (GuiToolbar * t = toolbar(name))
4219 case LFUN_TOOLBAR_MOVABLE: {
4220 string const name = cmd.getArg(0);
4222 // toggle (all) toolbars movablility
4223 toolbarsMovable_ = !toolbarsMovable_;
4224 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4225 GuiToolbar * tb = toolbar(ti.name);
4226 if (tb && tb->isMovable() != toolbarsMovable_)
4227 // toggle toolbar movablity if it does not fit lock
4228 // (all) toolbars positions state silent = true, since
4229 // status bar notifications are slow
4232 if (toolbarsMovable_)
4233 dr.setMessage(_("Toolbars unlocked."));
4235 dr.setMessage(_("Toolbars locked."));
4236 } else if (GuiToolbar * t = toolbar(name)) {
4237 // toggle current toolbar movablity
4239 // update lock (all) toolbars positions
4240 updateLockToolbars();
4245 case LFUN_ICON_SIZE: {
4246 QSize size = d.iconSize(cmd.argument());
4248 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4249 size.width(), size.height()));
4253 case LFUN_DIALOG_UPDATE: {
4254 string const name = to_utf8(cmd.argument());
4255 if (name == "prefs" || name == "document")
4256 updateDialog(name, string());
4257 else if (name == "paragraph")
4258 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4259 else if (currentBufferView()) {
4260 Inset * inset = currentBufferView()->editedInset(name);
4261 // Can only update a dialog connected to an existing inset
4263 // FIXME: get rid of this indirection; GuiView ask the inset
4264 // if he is kind enough to update itself...
4265 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4266 //FIXME: pass DispatchResult here?
4267 inset->dispatch(currentBufferView()->cursor(), fr);
4273 case LFUN_DIALOG_TOGGLE: {
4274 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4275 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4276 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4280 case LFUN_DIALOG_DISCONNECT_INSET:
4281 disconnectDialog(to_utf8(cmd.argument()));
4284 case LFUN_DIALOG_HIDE: {
4285 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4289 case LFUN_DIALOG_SHOW: {
4290 string const name = cmd.getArg(0);
4291 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4293 if (name == "latexlog") {
4294 // gettatus checks that
4295 LATTEST(doc_buffer);
4296 Buffer::LogType type;
4297 string const logfile = doc_buffer->logName(&type);
4299 case Buffer::latexlog:
4302 case Buffer::buildlog:
4303 sdata = "literate ";
4306 sdata += Lexer::quoteString(logfile);
4307 showDialog("log", sdata);
4308 } else if (name == "vclog") {
4309 // getStatus checks that
4310 LATTEST(doc_buffer);
4311 string const sdata2 = "vc " +
4312 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4313 showDialog("log", sdata2);
4314 } else if (name == "symbols") {
4315 sdata = bv->cursor().getEncoding()->name();
4317 showDialog("symbols", sdata);
4319 } else if (name == "prefs" && isFullScreen()) {
4320 lfunUiToggle("fullscreen");
4321 showDialog("prefs", sdata);
4323 showDialog(name, sdata);
4328 dr.setMessage(cmd.argument());
4331 case LFUN_UI_TOGGLE: {
4332 string arg = cmd.getArg(0);
4333 if (!lfunUiToggle(arg)) {
4334 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4335 dr.setMessage(bformat(msg, from_utf8(arg)));
4337 // Make sure the keyboard focus stays in the work area.
4342 case LFUN_VIEW_SPLIT: {
4343 LASSERT(doc_buffer, break);
4344 string const orientation = cmd.getArg(0);
4345 d.splitter_->setOrientation(orientation == "vertical"
4346 ? Qt::Vertical : Qt::Horizontal);
4347 TabWorkArea * twa = addTabWorkArea();
4348 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4349 setCurrentWorkArea(wa);
4352 case LFUN_TAB_GROUP_CLOSE:
4353 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4354 closeTabWorkArea(twa);
4355 d.current_work_area_ = 0;
4356 twa = d.currentTabWorkArea();
4357 // Switch to the next GuiWorkArea in the found TabWorkArea.
4359 // Make sure the work area is up to date.
4360 setCurrentWorkArea(twa->currentWorkArea());
4362 setCurrentWorkArea(0);
4367 case LFUN_VIEW_CLOSE:
4368 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4369 closeWorkArea(twa->currentWorkArea());
4370 d.current_work_area_ = 0;
4371 twa = d.currentTabWorkArea();
4372 // Switch to the next GuiWorkArea in the found TabWorkArea.
4374 // Make sure the work area is up to date.
4375 setCurrentWorkArea(twa->currentWorkArea());
4377 setCurrentWorkArea(0);
4382 case LFUN_COMPLETION_INLINE:
4383 if (d.current_work_area_)
4384 d.current_work_area_->completer().showInline();
4387 case LFUN_COMPLETION_POPUP:
4388 if (d.current_work_area_)
4389 d.current_work_area_->completer().showPopup();
4394 if (d.current_work_area_)
4395 d.current_work_area_->completer().tab();
4398 case LFUN_COMPLETION_CANCEL:
4399 if (d.current_work_area_) {
4400 if (d.current_work_area_->completer().popupVisible())
4401 d.current_work_area_->completer().hidePopup();
4403 d.current_work_area_->completer().hideInline();
4407 case LFUN_COMPLETION_ACCEPT:
4408 if (d.current_work_area_)
4409 d.current_work_area_->completer().activate();
4412 case LFUN_BUFFER_ZOOM_IN:
4413 case LFUN_BUFFER_ZOOM_OUT:
4414 case LFUN_BUFFER_ZOOM: {
4415 if (cmd.argument().empty()) {
4416 if (cmd.action() == LFUN_BUFFER_ZOOM)
4418 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4423 if (cmd.action() == LFUN_BUFFER_ZOOM)
4424 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4425 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4426 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4428 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4431 // Actual zoom value: default zoom + fractional extra value
4432 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4433 if (zoom < static_cast<int>(zoom_min_))
4436 lyxrc.currentZoom = zoom;
4438 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4439 lyxrc.currentZoom, lyxrc.defaultZoom));
4441 guiApp->fontLoader().update();
4442 dr.screenUpdate(Update::Force | Update::FitCursor);
4446 case LFUN_VC_REGISTER:
4447 case LFUN_VC_RENAME:
4449 case LFUN_VC_CHECK_IN:
4450 case LFUN_VC_CHECK_OUT:
4451 case LFUN_VC_REPO_UPDATE:
4452 case LFUN_VC_LOCKING_TOGGLE:
4453 case LFUN_VC_REVERT:
4454 case LFUN_VC_UNDO_LAST:
4455 case LFUN_VC_COMMAND:
4456 case LFUN_VC_COMPARE:
4457 dispatchVC(cmd, dr);
4460 case LFUN_SERVER_GOTO_FILE_ROW:
4461 if(goToFileRow(to_utf8(cmd.argument())))
4462 dr.screenUpdate(Update::Force | Update::FitCursor);
4465 case LFUN_LYX_ACTIVATE:
4469 case LFUN_FORWARD_SEARCH: {
4470 // it seems safe to assume we have a document buffer, since
4471 // getStatus wants one.
4472 LATTEST(doc_buffer);
4473 Buffer const * doc_master = doc_buffer->masterBuffer();
4474 FileName const path(doc_master->temppath());
4475 string const texname = doc_master->isChild(doc_buffer)
4476 ? DocFileName(changeExtension(
4477 doc_buffer->absFileName(),
4478 "tex")).mangledFileName()
4479 : doc_buffer->latexName();
4480 string const fulltexname =
4481 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4482 string const mastername =
4483 removeExtension(doc_master->latexName());
4484 FileName const dviname(addName(path.absFileName(),
4485 addExtension(mastername, "dvi")));
4486 FileName const pdfname(addName(path.absFileName(),
4487 addExtension(mastername, "pdf")));
4488 bool const have_dvi = dviname.exists();
4489 bool const have_pdf = pdfname.exists();
4490 if (!have_dvi && !have_pdf) {
4491 dr.setMessage(_("Please, preview the document first."));
4494 string outname = dviname.onlyFileName();
4495 string command = lyxrc.forward_search_dvi;
4496 if (!have_dvi || (have_pdf &&
4497 pdfname.lastModified() > dviname.lastModified())) {
4498 outname = pdfname.onlyFileName();
4499 command = lyxrc.forward_search_pdf;
4502 DocIterator cur = bv->cursor();
4503 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4504 LYXERR(Debug::ACTION, "Forward search: row:" << row
4506 if (row == -1 || command.empty()) {
4507 dr.setMessage(_("Couldn't proceed."));
4510 string texrow = convert<string>(row);
4512 command = subst(command, "$$n", texrow);
4513 command = subst(command, "$$f", fulltexname);
4514 command = subst(command, "$$t", texname);
4515 command = subst(command, "$$o", outname);
4517 PathChanger p(path);
4519 one.startscript(Systemcall::DontWait, command);
4523 case LFUN_SPELLING_CONTINUOUSLY:
4524 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4525 dr.screenUpdate(Update::Force);
4529 // The LFUN must be for one of BufferView, Buffer or Cursor;
4531 dispatchToBufferView(cmd, dr);
4535 // Part of automatic menu appearance feature.
4536 if (isFullScreen()) {
4537 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4541 // Need to update bv because many LFUNs here might have destroyed it
4542 bv = currentBufferView();
4544 // Clear non-empty selections
4545 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4547 Cursor & cur = bv->cursor();
4548 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4549 cur.clearSelection();
4555 bool GuiView::lfunUiToggle(string const & ui_component)
4557 if (ui_component == "scrollbar") {
4558 // hide() is of no help
4559 if (d.current_work_area_->verticalScrollBarPolicy() ==
4560 Qt::ScrollBarAlwaysOff)
4562 d.current_work_area_->setVerticalScrollBarPolicy(
4563 Qt::ScrollBarAsNeeded);
4565 d.current_work_area_->setVerticalScrollBarPolicy(
4566 Qt::ScrollBarAlwaysOff);
4567 } else if (ui_component == "statusbar") {
4568 statusBar()->setVisible(!statusBar()->isVisible());
4569 } else if (ui_component == "menubar") {
4570 menuBar()->setVisible(!menuBar()->isVisible());
4572 if (ui_component == "frame") {
4574 getContentsMargins(&l, &t, &r, &b);
4575 //are the frames in default state?
4576 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4578 setContentsMargins(-2, -2, -2, -2);
4580 setContentsMargins(0, 0, 0, 0);
4583 if (ui_component == "fullscreen") {
4591 void GuiView::toggleFullScreen()
4593 if (isFullScreen()) {
4594 for (int i = 0; i != d.splitter_->count(); ++i)
4595 d.tabWorkArea(i)->setFullScreen(false);
4596 setContentsMargins(0, 0, 0, 0);
4597 setWindowState(windowState() ^ Qt::WindowFullScreen);
4600 statusBar()->show();
4603 hideDialogs("prefs", 0);
4604 for (int i = 0; i != d.splitter_->count(); ++i)
4605 d.tabWorkArea(i)->setFullScreen(true);
4606 setContentsMargins(-2, -2, -2, -2);
4608 setWindowState(windowState() ^ Qt::WindowFullScreen);
4609 if (lyxrc.full_screen_statusbar)
4610 statusBar()->hide();
4611 if (lyxrc.full_screen_menubar)
4613 if (lyxrc.full_screen_toolbars) {
4614 ToolbarMap::iterator end = d.toolbars_.end();
4615 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4620 // give dialogs like the TOC a chance to adapt
4625 Buffer const * GuiView::updateInset(Inset const * inset)
4630 Buffer const * inset_buffer = &(inset->buffer());
4632 for (int i = 0; i != d.splitter_->count(); ++i) {
4633 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4636 Buffer const * buffer = &(wa->bufferView().buffer());
4637 if (inset_buffer == buffer)
4638 wa->scheduleRedraw(true);
4640 return inset_buffer;
4644 void GuiView::restartCaret()
4646 /* When we move around, or type, it's nice to be able to see
4647 * the caret immediately after the keypress.
4649 if (d.current_work_area_)
4650 d.current_work_area_->startBlinkingCaret();
4652 // Take this occasion to update the other GUI elements.
4658 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4660 if (d.current_work_area_)
4661 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4666 // This list should be kept in sync with the list of insets in
4667 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4668 // dialog should have the same name as the inset.
4669 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4670 // docs in LyXAction.cpp.
4672 char const * const dialognames[] = {
4674 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4675 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4676 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4677 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4678 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4679 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4680 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4681 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4683 char const * const * const end_dialognames =
4684 dialognames + (sizeof(dialognames) / sizeof(char *));
4688 cmpCStr(char const * name) : name_(name) {}
4689 bool operator()(char const * other) {
4690 return strcmp(other, name_) == 0;
4697 bool isValidName(string const & name)
4699 return find_if(dialognames, end_dialognames,
4700 cmpCStr(name.c_str())) != end_dialognames;
4706 void GuiView::resetDialogs()
4708 // Make sure that no LFUN uses any GuiView.
4709 guiApp->setCurrentView(0);
4713 constructToolbars();
4714 guiApp->menus().fillMenuBar(menuBar(), this, false);
4715 d.layout_->updateContents(true);
4716 // Now update controls with current buffer.
4717 guiApp->setCurrentView(this);
4723 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4725 if (!isValidName(name))
4728 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4730 if (it != d.dialogs_.end()) {
4732 it->second->hideView();
4733 return it->second.get();
4736 Dialog * dialog = build(name);
4737 d.dialogs_[name].reset(dialog);
4738 if (lyxrc.allow_geometry_session)
4739 dialog->restoreSession();
4746 void GuiView::showDialog(string const & name, string const & sdata,
4749 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4753 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4759 const string name = fromqstr(qname);
4760 const string sdata = fromqstr(qdata);
4764 Dialog * dialog = findOrBuild(name, false);
4766 bool const visible = dialog->isVisibleView();
4767 dialog->showData(sdata);
4768 if (currentBufferView())
4769 currentBufferView()->editInset(name, inset);
4770 // We only set the focus to the new dialog if it was not yet
4771 // visible in order not to change the existing previous behaviour
4773 // activateWindow is needed for floating dockviews
4774 dialog->asQWidget()->raise();
4775 dialog->asQWidget()->activateWindow();
4776 dialog->asQWidget()->setFocus();
4780 catch (ExceptionMessage const & ex) {
4788 bool GuiView::isDialogVisible(string const & name) const
4790 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4791 if (it == d.dialogs_.end())
4793 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4797 void GuiView::hideDialog(string const & name, Inset * inset)
4799 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4800 if (it == d.dialogs_.end())
4804 if (!currentBufferView())
4806 if (inset != currentBufferView()->editedInset(name))
4810 Dialog * const dialog = it->second.get();
4811 if (dialog->isVisibleView())
4813 if (currentBufferView())
4814 currentBufferView()->editInset(name, 0);
4818 void GuiView::disconnectDialog(string const & name)
4820 if (!isValidName(name))
4822 if (currentBufferView())
4823 currentBufferView()->editInset(name, 0);
4827 void GuiView::hideAll() const
4829 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4830 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4832 for(; it != end; ++it)
4833 it->second->hideView();
4837 void GuiView::updateDialogs()
4839 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4840 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4842 for(; it != end; ++it) {
4843 Dialog * dialog = it->second.get();
4845 if (dialog->needBufferOpen() && !documentBufferView())
4846 hideDialog(fromqstr(dialog->name()), 0);
4847 else if (dialog->isVisibleView())
4848 dialog->checkStatus();
4855 Dialog * createDialog(GuiView & lv, string const & name);
4857 // will be replaced by a proper factory...
4858 Dialog * createGuiAbout(GuiView & lv);
4859 Dialog * createGuiBibtex(GuiView & lv);
4860 Dialog * createGuiChanges(GuiView & lv);
4861 Dialog * createGuiCharacter(GuiView & lv);
4862 Dialog * createGuiCitation(GuiView & lv);
4863 Dialog * createGuiCompare(GuiView & lv);
4864 Dialog * createGuiCompareHistory(GuiView & lv);
4865 Dialog * createGuiDelimiter(GuiView & lv);
4866 Dialog * createGuiDocument(GuiView & lv);
4867 Dialog * createGuiErrorList(GuiView & lv);
4868 Dialog * createGuiExternal(GuiView & lv);
4869 Dialog * createGuiGraphics(GuiView & lv);
4870 Dialog * createGuiInclude(GuiView & lv);
4871 Dialog * createGuiIndex(GuiView & lv);
4872 Dialog * createGuiListings(GuiView & lv);
4873 Dialog * createGuiLog(GuiView & lv);
4874 Dialog * createGuiLyXFiles(GuiView & lv);
4875 Dialog * createGuiMathMatrix(GuiView & lv);
4876 Dialog * createGuiNote(GuiView & lv);
4877 Dialog * createGuiParagraph(GuiView & lv);
4878 Dialog * createGuiPhantom(GuiView & lv);
4879 Dialog * createGuiPreferences(GuiView & lv);
4880 Dialog * createGuiPrint(GuiView & lv);
4881 Dialog * createGuiPrintindex(GuiView & lv);
4882 Dialog * createGuiRef(GuiView & lv);
4883 Dialog * createGuiSearch(GuiView & lv);
4884 Dialog * createGuiSearchAdv(GuiView & lv);
4885 Dialog * createGuiSendTo(GuiView & lv);
4886 Dialog * createGuiShowFile(GuiView & lv);
4887 Dialog * createGuiSpellchecker(GuiView & lv);
4888 Dialog * createGuiSymbols(GuiView & lv);
4889 Dialog * createGuiTabularCreate(GuiView & lv);
4890 Dialog * createGuiTexInfo(GuiView & lv);
4891 Dialog * createGuiToc(GuiView & lv);
4892 Dialog * createGuiThesaurus(GuiView & lv);
4893 Dialog * createGuiViewSource(GuiView & lv);
4894 Dialog * createGuiWrap(GuiView & lv);
4895 Dialog * createGuiProgressView(GuiView & lv);
4899 Dialog * GuiView::build(string const & name)
4901 LASSERT(isValidName(name), return 0);
4903 Dialog * dialog = createDialog(*this, name);
4907 if (name == "aboutlyx")
4908 return createGuiAbout(*this);
4909 if (name == "bibtex")
4910 return createGuiBibtex(*this);
4911 if (name == "changes")
4912 return createGuiChanges(*this);
4913 if (name == "character")
4914 return createGuiCharacter(*this);
4915 if (name == "citation")
4916 return createGuiCitation(*this);
4917 if (name == "compare")
4918 return createGuiCompare(*this);
4919 if (name == "comparehistory")
4920 return createGuiCompareHistory(*this);
4921 if (name == "document")
4922 return createGuiDocument(*this);
4923 if (name == "errorlist")
4924 return createGuiErrorList(*this);
4925 if (name == "external")
4926 return createGuiExternal(*this);
4928 return createGuiShowFile(*this);
4929 if (name == "findreplace")
4930 return createGuiSearch(*this);
4931 if (name == "findreplaceadv")
4932 return createGuiSearchAdv(*this);
4933 if (name == "graphics")
4934 return createGuiGraphics(*this);
4935 if (name == "include")
4936 return createGuiInclude(*this);
4937 if (name == "index")
4938 return createGuiIndex(*this);
4939 if (name == "index_print")
4940 return createGuiPrintindex(*this);
4941 if (name == "listings")
4942 return createGuiListings(*this);
4944 return createGuiLog(*this);
4945 if (name == "lyxfiles")
4946 return createGuiLyXFiles(*this);
4947 if (name == "mathdelimiter")
4948 return createGuiDelimiter(*this);
4949 if (name == "mathmatrix")
4950 return createGuiMathMatrix(*this);
4952 return createGuiNote(*this);
4953 if (name == "paragraph")
4954 return createGuiParagraph(*this);
4955 if (name == "phantom")
4956 return createGuiPhantom(*this);
4957 if (name == "prefs")
4958 return createGuiPreferences(*this);
4960 return createGuiRef(*this);
4961 if (name == "sendto")
4962 return createGuiSendTo(*this);
4963 if (name == "spellchecker")
4964 return createGuiSpellchecker(*this);
4965 if (name == "symbols")
4966 return createGuiSymbols(*this);
4967 if (name == "tabularcreate")
4968 return createGuiTabularCreate(*this);
4969 if (name == "texinfo")
4970 return createGuiTexInfo(*this);
4971 if (name == "thesaurus")
4972 return createGuiThesaurus(*this);
4974 return createGuiToc(*this);
4975 if (name == "view-source")
4976 return createGuiViewSource(*this);
4978 return createGuiWrap(*this);
4979 if (name == "progress")
4980 return createGuiProgressView(*this);
4986 SEMenu::SEMenu(QWidget * parent)
4988 QAction * action = addAction(qt_("Disable Shell Escape"));
4989 connect(action, SIGNAL(triggered()),
4990 parent, SLOT(disableShellEscape()));
4993 } // namespace frontend
4996 #include "moc_GuiView.cpp"